docs: def _do_failover_or_switchover(obj, action, cluster_name, master, candidate, force, scheduled=None):
      """
          We want to trigger a failover or switchover for the specified cluster name.
  
          We verify that the cluster name, master name and candidate name are correct.
          If so, we trigger an action and keep the client up to date.
      """
  
      dcs = get_dcs(obj, cluster_name)
      cluster = dcs.get_cluster()
  
      if action == 'switchover' and cluster.leader is None:
          raise PatroniCtlException('This cluster has no master')
  
      if master is None:
          if force or action == 'failover':
              master = cluster.leader and cluster.leader.name
          else:
              master = click.prompt('Master', type=str, default=cluster.leader.member.name)
  
      if master is not None and cluster.leader and cluster.leader.member.name != master:
          raise PatroniCtlException('Member {0} is not the leader of cluster {1}'.format(master, cluster_name))
  
      # excluding members with nofailover tag
      candidate_names = [str(m.name) for m in cluster.members if m.name != master and not m.nofailover]
      # We sort the names for consistent output to the client
      candidate_names.sort()
  
      if not candidate_names:
          raise PatroniCtlException('No candidates found to {0} to'.format(action))
  
      if candidate is None and not force:
          candidate = click.prompt('Candidate ' + str(candidate_names), type=str, default='')
  
      if action == 'failover' and not candidate:
          raise PatroniCtlException('Failover could be performed only to a specific candidate')
  
      if candidate == master:
          raise PatroniCtlException(action.title() + ' target and source are the same.')
  
      if candidate and candidate not in candidate_names:
          raise PatroniCtlException('Member {0} does not exist in cluster {1}'.format(candidate, cluster_name))
  
      scheduled_at_str = None
      scheduled_at = None
  
      if action == 'switchover':
          if scheduled is None and not force:
              scheduled = click.prompt('When should the switchover take place (e.g. 2015-10-01T14:30) ',
                                       type=str, default='now')
  
          scheduled_at = parse_scheduled(scheduled)
          if scheduled_at:
              if cluster.is_paused():
                  raise PatroniCtlException("Can't schedule switchover in the paused state")
              scheduled_at_str = scheduled_at.isoformat()
  
      failover_value = {'leader': master, 'candidate': candidate, 'scheduled_at': scheduled_at_str}
  
      logging.debug(failover_value)
  
      # By now we have established that the leader exists and the candidate exists
      click.echo('Current cluster topology')
      output_members(dcs.get_cluster(), cluster_name)
  
      if not force:
          demote_msg = ', demoting current master ' + master if master else ''
  
          if not click.confirm('Are you sure you want to {0} cluster {1}{2}?'.format(action, cluster_name, demote_msg)):
              raise PatroniCtlException('Aborting ' + action)
  
      r = None
      try:
          member = cluster.leader.member if cluster.leader else cluster.get_member(candidate, False)
  
          r = request_patroni(member, 'post', action, failover_value, auth_header(obj))
  
          # probably old patroni, which doesn't support switchover yet
          if r.status_code == 501 and action == 'switchover' and 'Server does not support this operation' in r.text:
              r = request_patroni(member, 'post', 'failover', failover_value, auth_header(obj))
  
          if r.status_code in (200, 202):
              logging.debug(r)
              cluster = dcs.get_cluster()
              logging.debug(cluster)
              click.echo('{0} {1}'.format(timestamp(), r.text))
          else:
              click.echo('{0} failed, details: {1}, {2}'.format(action.title(), r.status_code, r.text))
              return
      except Exception:
          logging.exception(r)
          logging.warning('Failing over to DCS')
          click.echo('{0} Could not {1} using Patroni api, falling back to DCS'.format(timestamp(), action))
          dcs.manual_failover(master, candidate, scheduled_at=scheduled_at)
  
      output_members(cluster, cluster_name)