docs: def start(self,
                join_ring=True,
                no_wait=False,
                verbose=False,
                update_pid=True,
                wait_other_notice=True,
                replace_token=None,
                replace_address=None,
                jvm_args=None,
                wait_for_binary_proto=False,
                profile_options=None,
                use_jna=False,
                quiet_start=False,
                allow_root=False,
                set_migration_task=True):
          """
          Start the node. Options includes:
            - join_ring: if false, start the node with -Dcassandra.join_ring=False
            - no_wait: by default, this method returns when the node is started and listening to clients.
              If no_wait=True, the method returns sooner.
            - wait_other_notice: if truthy, this method returns only when all other live node of the cluster
              have marked this node UP. if an integer, sets the timeout for how long to wait
            - replace_token: start the node with the -Dcassandra.replace_token option.
            - replace_address: start the node with the -Dcassandra.replace_address option.
          """
          if jvm_args is None:
              jvm_args = []
  
          if set_migration_task and self.cluster.cassandra_version() >= '3.0.1':
              jvm_args += ['-Dcassandra.migration_task_wait_in_seconds={}'.format(len(self.cluster.nodes) * 2)]
  
          # Validate Windows env
          if common.is_modern_windows_install(self.cluster.version()) and not common.is_ps_unrestricted():
              raise NodeError("PS Execution Policy must be unrestricted when running C* 2.1+")
  
          if not common.is_win() and quiet_start:
              common.warning("Tried to set Windows quiet start behavior, but we're not running on Windows.")
  
          if self.is_running():
              raise NodeError("{} is already running".format(self.name))
  
          for itf in list(self.network_interfaces.values()):
              if itf is not None and replace_address is None:
                  common.assert_socket_available(itf)
  
          if wait_other_notice:
              marks = [(node, node.mark_log()) for node in list(self.cluster.nodes.values()) if node.is_live()]
  
          self.mark = self.mark_log()
  
          launch_bin = self.get_launch_bin()
  
          # If Windows, change entries in .bat file to split conf from binaries
          if common.is_win():
              self.__clean_bat()
  
          if profile_options is not None:
              config = common.get_config()
              if 'yourkit_agent' not in config:
                  raise NodeError("Cannot enable profile. You need to set 'yourkit_agent' to the path of your agent in a ~/.ccm/config")
              cmd = '-agentpath:{}'.format(config['yourkit_agent'])
              if 'options' in profile_options:
                  cmd = cmd + '=' + profile_options['options']
              print_(cmd)
              # Yes, it's fragile as shit
              pattern = r'cassandra_parms="-Dlog4j.configuration=log4j-server.properties -Dlog4j.defaultInitOverride=true'
              common.replace_in_file(launch_bin, pattern, '    ' + pattern + ' ' + cmd + '"')
  
          os.chmod(launch_bin, os.stat(launch_bin).st_mode | stat.S_IEXEC)
  
          env = self.get_env()
  
          extension.append_to_server_env(self, env)
  
          if common.is_win():
              self._clean_win_jmx()
  
          pidfile = os.path.join(self.get_path(), 'cassandra.pid')
          args = [launch_bin]
  
          self.add_custom_launch_arguments(args)
  
          args = args + ['-p', pidfile, '-Dcassandra.join_ring=%s' % str(join_ring)]
  
          args.append('-Dcassandra.logdir=%s' % os.path.join(self.get_path(), 'logs'))
          if replace_token is not None:
              args.append('-Dcassandra.replace_token=%s' % str(replace_token))
          if replace_address is not None:
              args.append('-Dcassandra.replace_address=%s' % str(replace_address))
          if use_jna is False:
              args.append('-Dcassandra.boot_without_jna=true')
          if allow_root:
              args.append('-R')
          env['JVM_EXTRA_OPTS'] = env.get('JVM_EXTRA_OPTS', "") + " " + " ".join(jvm_args)
  
          # In case we are restarting a node
          # we risk reading the old cassandra.pid file
          self._delete_old_pid()
  
          process = None
          FNULL = open(os.devnull, 'w')
          stdout_sink = subprocess.PIPE if verbose else FNULL
          # write stderr to a temporary file to prevent overwhelming pipe (> 65K data).
          stderr_sink = tempfile.SpooledTemporaryFile(max_size=0xFFFF)
  
          if common.is_win():
              # clean up any old dirty_pid files from prior runs
              if (os.path.isfile(self.get_path() + "/dirty_pid.tmp")):
                  os.remove(self.get_path() + "/dirty_pid.tmp")
  
              if quiet_start and self.cluster.version() >= '2.2.4':
                  args.append('-q')
  
              process = subprocess.Popen(args, cwd=self.get_bin_dir(), env=env, stdout=stdout_sink, stderr=stderr_sink)
          else:
              process = subprocess.Popen(args, env=env, stdout=stdout_sink, stderr=stderr_sink)
  
          process.stderr_file = stderr_sink
  
          # Our modified batch file writes a dirty output with more than just the pid - clean it to get in parity
          # with *nix operation here.
          if verbose:
              stdout = process.communicate()[0]
              print_(stdout)
  
          if common.is_win():
              self.__clean_win_pid()
              self._update_pid(process)
              print_("Started: {0} with pid: {1}".format(self.name, self.pid), file=sys.stderr, flush=True)
          elif update_pid:
              self._update_pid(process)
  
              if not self.is_running():
                  raise NodeError("Error starting node %s" % self.name, process)
  
          # If wait_other_notice is a bool, we don't want to treat it as a
          # timeout. Other intlike types, though, we want to use.
          if common.is_intlike(wait_other_notice) and not isinstance(wait_other_notice, bool):
              for node, mark in marks:
                  node.watch_log_for_alive(self, from_mark=mark, timeout=wait_other_notice)
          elif wait_other_notice:
              for node, mark in marks:
                  node.watch_log_for_alive(self, from_mark=mark)
  
          # If wait_for_binary_proto is a bool, we don't want to treat it as a
          # timeout. Other intlike types, though, we want to use.
          if common.is_intlike(wait_for_binary_proto) and not isinstance(wait_for_binary_proto, bool):
              self.wait_for_binary_interface(from_mark=self.mark, timeout=wait_for_binary_proto)
          elif wait_for_binary_proto:
              self.wait_for_binary_interface(from_mark=self.mark)
  
          return process