Example #1
0
    def ShutdownAll(minor):
        """Deactivate the device.

    This will, of course, fail if the device is in use.

    @type minor: int
    @param minor: the minor to shut down

    """
        info = DRBD8.GetProcInfo()
        cmd_gen = DRBD8.GetCmdGenerator(info)

        cmd = cmd_gen.GenDownCmd(minor)
        result = utils.RunCmd(cmd)
        if result.failed:
            base.ThrowError("drbd%d: can't shutdown drbd device: %s", minor,
                            result.output)
Example #2
0
    def isPool(self, pool='rbd'):
        if pool is None:
            sys.stderr.write('no poolname is given')
            return False

        result = utils.RunCmd(["rados", "lspools"])
        if result.failed:
            sys.stderr.write("rados lspools (%s): %s" %
                             (result.fail_reason, result.output))
            return False

        res = re.search('(' + pool + ')', result.output)
        if res is None:
            sys.stderr.write('no result\n')
            return False

        return pool in res.groups()
Example #3
0
def _IsUidUsed(uid):
    """Check if there is any process in the system running with the given user-id

  @type uid: integer
  @param uid: the user-id to be checked.

  """
    pgrep_command = [constants.PGREP, "-u", uid]
    result = utils.RunCmd(pgrep_command)

    if result.exit_code == 0:
        return True
    elif result.exit_code == 1:
        return False
    else:
        raise errors.CommandError("Running pgrep failed. exit code: %s" %
                                  result.exit_code)
Example #4
0
    def _ShutdownNet(self, minor):
        """Disconnect from the remote peer.

    This fails if we don't have a local device.

    @type minor: boolean
    @param minor: the device to disconnect from the remote peer

    """
        family = self._GetNetFamily(minor, self._lhost, self._rhost)
        cmd = self._cmd_gen.GenDisconnectCmd(minor, family, self._lhost,
                                             self._lport, self._rhost,
                                             self._rport)
        result = utils.RunCmd(cmd)
        if result.failed:
            base.ThrowError("drbd%d: can't shutdown network: %s", minor,
                            result.output)
Example #5
0
def _RunUpgrade(path, dry_run, no_verify, ignore_hostname=True,
                downgrade=False):
  cmd = [sys.executable, "%s/tools/cfgupgrade" % testutils.GetSourceDir(),
         "--debug", "--force", "--path=%s" % path, "--confdir=%s" % path]

  if ignore_hostname:
    cmd.append("--ignore-hostname")
  if dry_run:
    cmd.append("--dry-run")
  if no_verify:
    cmd.append("--no-verify")
  if downgrade:
    cmd.append("--downgrade")

  result = utils.RunCmd(cmd, cwd=os.getcwd())
  if result.failed:
    raise Exception("cfgupgrade failed: %s, output %r" %
                    (result.fail_reason, result.output))
Example #6
0
    def Rename(self, new_id):
        """Rename this logical volume.

    """
        if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
            raise errors.ProgrammerError("Invalid new logical id '%s'" %
                                         new_id)
        new_vg, new_name = new_id
        if new_vg != self._vg_name:
            raise errors.ProgrammerError("Can't move a logical volume across"
                                         " volume groups (from %s to to %s)" %
                                         (self._vg_name, new_vg))
        result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
        if result.failed:
            base.ThrowError("Failed to rename the logical volume: %s",
                            result.output)
        self._lv_name = new_name
        self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
Example #7
0
 def testLang(self):
     """Test locale environment"""
     old_env = os.environ.copy()
     try:
         os.environ["LANG"] = "en_US.UTF-8"
         os.environ["LC_ALL"] = "en_US.UTF-8"
         result = utils.RunCmd(["locale"])
         for line in result.output.splitlines():
             key, value = line.split("=", 1)
             # Ignore these variables, they're overridden by LC_ALL
             if key == "LANG" or key == "LANGUAGE":
                 continue
             self.failIf(
                 value and value != "C" and value != '"C"',
                 "Variable %s is set to the invalid value '%s'" %
                 (key, value))
     finally:
         os.environ = old_env
Example #8
0
def _InitGanetiServerSetup(master_name, cfg):
  """Setup the necessary configuration for the initial node daemon.

  This creates the nodepass file containing the shared password for
  the cluster, generates the SSL certificate and starts the node daemon.

  @type master_name: str
  @param master_name: Name of the master node
  @type cfg: ConfigWriter
  @param cfg: the configuration writer

  """
  # Generate cluster secrets
  GenerateClusterCrypto(True, False, False, False, False, False, master_name)

  # Add the master's SSL certificate digest to the configuration.
  master_uuid = cfg.GetMasterNode()
  master_digest = utils.GetCertificateDigest()
  cfg.AddNodeToCandidateCerts(master_uuid, master_digest)
  cfg.Update(cfg.GetClusterInfo(), logging.error)
  ssconf.WriteSsconfFiles(cfg.GetSsconfValues())

  if not os.path.exists(
      os.path.join(pathutils.DATA_DIR,
                   "%s%s" % (constants.SSCONF_FILEPREFIX,
                             constants.SS_MASTER_CANDIDATES_CERTS))):
    raise errors.OpExecError("Ssconf file for master candidate certificates"
                             " was not written.")

  if not os.path.exists(pathutils.NODED_CERT_FILE):
    raise errors.OpExecError("The server certficate was not created properly.")

  if not os.path.exists(pathutils.NODED_CLIENT_CERT_FILE):
    raise errors.OpExecError("The client certificate was not created"
                             " properly.")

  # set up the inter-node password and certificate
  result = utils.RunCmd([pathutils.DAEMON_UTIL, "start", constants.NODED])
  if result.failed:
    raise errors.OpExecError("Could not start the node daemon, command %s"
                             " had exitcode %s and error %s" %
                             (result.cmd, result.exit_code, result.output))

  _WaitForNodeDaemon(master_name)
Example #9
0
  def PowercycleNode(self, hvparams=None):
    """Xen-specific powercycle.

    This first does a Linux reboot (which triggers automatically a Xen
    reboot), and if that fails it tries to do a Xen reboot. The reason
    we don't try a Xen reboot first is that the xen reboot launches an
    external command which connects to the Xen hypervisor, and that
    won't work in case the root filesystem is broken and/or the xend
    daemon is not working.

    @type hvparams: dict of strings
    @param hvparams: hypervisor params to be used on this node

    """
    try:
      self.LinuxPowercycle()
    finally:
      xen_cmd = self._GetCommand(hvparams)
      utils.RunCmd([xen_cmd, "debug", "R"])
Example #10
0
  def Grow(self, amount, dryrun, backingstore, excl_stor):
    """Resize the DRBD device and its backing storage.

    See L{BlockDev.Grow} for parameter description.

    """
    if self.minor is None:
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
    if len(self._children) != 2 or None in self._children:
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
    self._children[0].Grow(amount, dryrun, backingstore, excl_stor)
    if dryrun or backingstore:
      # DRBD does not support dry-run mode and is not backing storage,
      # so we'll return here
      return
    cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
    result = utils.RunCmd(cmd)
    if result.failed:
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
Example #11
0
    def Open(self, force=False, exclusive=True):
        """Make the local state primary.

    If the 'force' parameter is given, DRBD is instructed to switch the device
    into primary mode. Since this is a potentially dangerous operation, the
    force flag should be only given after creation, when it actually is
    mandatory.

    """
        if self.minor is None and not self.Attach():
            logging.error("DRBD cannot attach to a device during open")
            return False

        cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)

        result = utils.RunCmd(cmd)
        if result.failed:
            base.ThrowError("drbd%d: can't make drbd device primary: %s",
                            self.minor, result.output)
  def testInputWithCloseFds(self):
    testfile = testutils.TestDataFilename("cert1.pem")

    temp = open(self.fname, "r+")
    try:
      temp.write("test283523367")
      temp.seek(0)

      result = utils.RunCmd(["/bin/bash", "-c",
                             ("cat && read -u %s; echo $REPLY" %
                              temp.fileno())],
                            input_fd=open(testfile, "r"),
                            noclose_fds=[temp.fileno()])
      self.assertFalse(result.failed)
      self.assertEqual(result.stdout.strip(),
                       utils.ReadFile(testfile) + "test283523367")
      self.assertEqual(result.stderr, "")
    finally:
      temp.close()
Example #13
0
  def _UnmapVolumeFromBlockdev(self, unique_id):
    """Unmaps the rbd device from the Volume it is mapped.

    Unmaps the rbd device from the Volume it was previously mapped to.
    This method should be idempotent if the Volume isn't mapped.

    """
    pool = self.params[constants.LDP_POOL]
    name = unique_id[1]

    # Check if the mapping already exists.
    rbd_dev = self._VolumeToBlockdev(pool, name)

    if rbd_dev:
      # The mapping exists. Unmap the rbd device.
      unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
      result = utils.RunCmd(unmap_cmd)
      if result.failed:
        base.ThrowError("rbd unmap failed (%s): %s",
                        result.fail_reason, result.output)
Example #14
0
  def Remove(self):
    """Remove the rbd device.

    """
    rbd_pool = self.params[constants.LDP_POOL]
    rbd_name = self.unique_id[1]

    if not self.minor and not self.Attach():
      # The rbd device doesn't exist.
      return

    # First shutdown the device (remove mappings).
    self.Shutdown()

    # Remove the actual Volume (Image) from the RADOS cluster.
    cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
    result = utils.RunCmd(cmd)
    if result.failed:
      base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
                      result.fail_reason, result.output)
Example #15
0
def _InitGanetiServerSetup(master_name):
  """Setup the necessary configuration for the initial node daemon.

  This creates the nodepass file containing the shared password for
  the cluster, generates the SSL certificate and starts the node daemon.

  @type master_name: str
  @param master_name: Name of the master node

  """
  # Generate cluster secrets
  GenerateClusterCrypto(True, False, False, False, False)

  result = utils.RunCmd([pathutils.DAEMON_UTIL, "start", constants.NODED])
  if result.failed:
    raise errors.OpExecError("Could not start the node daemon, command %s"
                             " had exitcode %s and error %s" %
                             (result.cmd, result.exit_code, result.output))

  _WaitForNodeDaemon(master_name)
Example #16
0
def GetInterfaceIpAddresses(ifname):
  """Returns the IP addresses associated to the interface.

  @type ifname: string
  @param ifname: Name of the network interface
  @return: A dict having for keys the IP version (either
           L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for
           values the lists of IP addresses of the respective version
           associated to the interface

  """
  result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
                         "dev", ifname])

  if result.failed:
    logging.error("Error running the ip command while getting the IP"
                  " addresses of %s", ifname)
    return None

  return _GetIpAddressesFromIpOutput(result.output)
Example #17
0
    def RebootInstance(self, instance):
        """Reboot an instance.

    """
        if "sys_boot" in self._GetInstanceDropCapabilities(instance.hvparams):
            raise HypervisorError(
                "The LXC container can't perform a reboot with the"
                " SYS_BOOT capability dropped.")

        # We can't use the --timeout=-1 approach as same as the StopInstance due to
        # the following patch was applied in lxc-1.0.5 and we are supporting
        # LXC >= 1.0.0.
        # http://lists.linuxcontainers.org/pipermail/lxc-devel/2014-July/009742.html
        result = utils.RunCmd([
            "lxc-stop", "-n", instance.name, "--reboot", "--timeout",
            str(self._REBOOT_TIMEOUT)
        ])
        if result.failed:
            raise HypervisorError("Failed to reboot instance %s: %s" %
                                  (instance.name, result.output))
Example #18
0
    def Grow(self, amount, dryrun, backingstore, excl_stor):
        """Grow the logical volume.

    """
        if not backingstore:
            return
        if self.pe_size is None or self.stripe_count is None:
            if not self.Attach():
                base.ThrowError("Can't attach to LV during Grow()")
        full_stripe_size = self.pe_size * self.stripe_count
        # pe_size is in KB
        amount *= 1024
        rest = amount % full_stripe_size
        if rest != 0:
            amount += full_stripe_size - rest
        cmd = ["lvextend", "-L", "+%dk" % amount]
        if dryrun:
            cmd.append("--test")
        if excl_stor:
            free_space = self._GetGrowthAvaliabilityExclStor()
            # amount is in KiB, free_space in MiB
            if amount > free_space * 1024:
                base.ThrowError(
                    "Not enough free space to grow %s: %d MiB required,"
                    " %d available", self.dev_path, amount / 1024, free_space)
            # Disk growth doesn't grow the number of spindles, so we must stay within
            # our assigned volumes
            pvlist = list(self.pv_names)
        else:
            pvlist = []
        # we try multiple algorithms since the 'best' ones might not have
        # space available in the right place, but later ones might (since
        # they have less constraints); also note that only recent LVM
        # supports 'cling'
        for alloc_policy in "contiguous", "cling", "normal":
            result = utils.RunCmd(cmd +
                                  ["--alloc", alloc_policy, self.dev_path] +
                                  pvlist)
            if not result.failed:
                return
        base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
Example #19
0
def ConfigureNIC(cmd, instance, seq, nic, tap):
  """Run the network configuration script for a specified NIC

  @type cmd: string
  @param cmd: command to run
  @type instance: instance object
  @param instance: instance we're acting on
  @type seq: int
  @param seq: nic sequence number
  @type nic: nic object
  @param nic: nic we're acting on
  @type tap: str
  @param tap: the host's tap interface this NIC corresponds to

  """
  env = CreateNICEnv(instance.name, nic, tap, seq, instance.GetTags())
  result = utils.RunCmd(cmd, env=env)
  if result.failed:
    raise errors.HypervisorError("Failed to configure interface %s: %s;"
                                 " network configuration script output: %s" %
                                 (tap, result.fail_reason, result.output))
Example #20
0
  def _GetSocatVersion(cls):
    """Returns the socat version, as a tuple of ints.

    The version is memoized in a class variable for future use.
    """
    if cls._SOCAT_VERSION > (0,):
      return cls._SOCAT_VERSION

    socat = utils.RunCmd([constants.SOCAT_PATH, "-V"])
    # No need to check for errors here. If -V is not there, socat is really
    # old. Any other failure will be handled when running the actual socat
    # command.
    for line in socat.output.splitlines():
      match = re.match(r"socat version ((\d+\.)*(\d+))", line)
      if match:
        try:
          cls._SOCAT_VERSION = tuple(int(x) for x in match.group(1).split('.'))
        except TypeError:
          pass
        break
    return cls._SOCAT_VERSION
Example #21
0
    def _AssembleLocal(self, minor, backend, meta, size):
        """Configure the local part of a DRBD device.

    @type minor: int
    @param minor: the minor to assemble locally
    @type backend: string
    @param backend: path to the data device to use
    @type meta: string
    @param meta: path to the meta device to use
    @type size: int
    @param size: size in MiB

    """
        cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta, size,
                                              self.params)

        for cmd in cmds:
            result = utils.RunCmd(cmd)
            if result.failed:
                base.ThrowError("drbd%d: can't attach local disk: %s", minor,
                                result.output)
Example #22
0
def _InitSSHSetup():
    """Setup the SSH configuration for the cluster.

  This generates a dsa keypair for root, adds the pub key to the
  permitted hosts and adds the hostkey to its own known hosts.

  """
    priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.SSH_LOGIN_USER)

    for name in priv_key, pub_key:
        if os.path.exists(name):
            utils.CreateBackup(name)
        utils.RemoveFile(name)

    result = utils.RunCmd(
        ["ssh-keygen", "-t", "dsa", "-f", priv_key, "-q", "-N", ""])
    if result.failed:
        raise errors.OpExecError("Could not generate ssh keypair, error %s" %
                                 result.output)

    utils.AddAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
Example #23
0
def ReadRemoteSshPubKeys(keyfiles, node, cluster_name, port, ask_key,
                         strict_host_check):
    """Fetches the public SSH keys from a node via SSH.

  @type keyfiles: dict from string to (string, string) tuples
  @param keyfiles: a dictionary mapping the type of key (e.g. rsa, dsa) to a
    tuple consisting of the file name of the private and public key

  """
    ssh_runner = SshRunner(cluster_name)

    failed_results = {}
    fetched_keys = {}
    for (kind, (_, public_key_file)) in keyfiles.items():
        cmd = ["cat", public_key_file]
        ssh_cmd = ssh_runner.BuildCmd(node,
                                      constants.SSH_LOGIN_USER,
                                      utils.ShellQuoteArgs(cmd),
                                      batch=False,
                                      ask_key=ask_key,
                                      quiet=False,
                                      strict_host_check=strict_host_check,
                                      use_cluster_key=False,
                                      port=port)

        result = utils.RunCmd(ssh_cmd)
        if result.failed:
            failed_results[kind] = (result.cmd, result.fail_reason)
        else:
            fetched_keys[kind] = result.stdout

    if len(fetched_keys.keys()) < 1:
        error_msg = "Could not fetch any public SSH key."
        for (kind, (cmd, fail_reason)) in failed_results.items():
            error_msg += "Could not fetch the public '%s' SSH key from node '%s':" \
                         " ran command '%s', failure reason: '%s'. " % \
                         (kind, node, cmd, fail_reason)
        raise errors.OpPrereqError(error_msg)

    return fetched_keys
Example #24
0
    def _SetAllocatable(self, name, allocatable):
        """Sets the "allocatable" flag on a physical volume.

    @type name: string
    @param name: Physical volume name
    @type allocatable: bool
    @param allocatable: Whether to set the "allocatable" flag

    """
        args = ["pvchange", "--allocatable"]

        if allocatable:
            args.append("y")
        else:
            args.append("n")

        args.append(name)

        result = utils.RunCmd(args)
        if result.failed:
            raise errors.StorageError("Failed to modify physical volume,"
                                      " pvchange output: %s" % result.output)
Example #25
0
  def Import(self):
    """Builds the shell command for importing data to device.

    @see: L{BlockDev.Import} for details

    """
    if not self.minor and not self.Attach():
      # The rbd device doesn't exist.
      base.ThrowError("Can't attach to rbd device during Import()")

    rbd_pool = self.params[constants.LDP_POOL]
    rbd_name = self.unique_id[1]

    # Currently, the 'rbd import' command imports data only to non-existing
    # volumes. If the rbd volume exists the command will fail.
    # The disk conversion mechanism though, has already created the new rbd
    # volume at the time we perform the data copy, so we have to first remove
    # the volume before starting to import its data. The 'rbd import' will
    # re-create the rbd volume. We choose to remove manually the rbd device
    # instead of calling its 'Remove()' method to avoid affecting the 'self.'
    # parameters of the device. Also, this part of the removal code will go
    # away once 'rbd import' has support for importing into an existing volume.
    # TODO: update this method when the 'rbd import' command supports the
    # '--force' option, which will allow importing to an existing volume.

    # Unmap the block device from the Volume.
    self._UnmapVolumeFromBlockdev(self.unique_id)

    # Remove the actual Volume (Image) from the RADOS cluster.
    cmd = self.__class__.MakeRbdCmd(self.params, ["rm", "-p", rbd_pool,
                                                  rbd_name])
    result = utils.RunCmd(cmd)
    if result.failed:
      base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
                      result.fail_reason, result.output)

    # We use "-" for importing from stdin
    return self.__class__.MakeRbdCmd(self.params, ["import", "-p", rbd_pool,
                                                   "-", rbd_name])
Example #26
0
def GenerateTapName():
    """Generate a TAP network interface name for a NIC.

  This helper function generates a special TAP network interface
  name for NICs that are meant to be used in instance communication.
  This function checks the existing TAP interfaces in order to find
  a unique name for the new TAP network interface.  The TAP network
  interface names are of the form 'gnt.com.%d', where '%d' is a
  unique number within the node.

  @rtype: string
  @return: TAP network interface name, or the empty string if the
           NIC is not used in instance communication

  """
    result = utils.RunCmd(["ip", "link", "show"])

    if result.failed:
        raise errors.HypervisorError("Failed to list TUN/TAP interfaces")

    idxs = set()

    for line in result.output.splitlines()[0::2]:
        parts = line.split(": ")

        if len(parts) < 2:
            raise errors.HypervisorError("Failed to parse TUN/TAP interfaces")

        r = re.match(r"gnt\.com\.([0-9]+)", parts[1])

        if r is not None:
            idxs.add(int(r.group(1)))

    if idxs:
        idx = max(idxs) + 1
    else:
        idx = 0

    return "gnt.com.%d" % idx
Example #27
0
    def _SetMinorSyncParams(self, minor, params):
        """Set the parameters of the DRBD syncer.

    This is the low-level implementation.

    @type minor: int
    @param minor: the drbd minor whose settings we change
    @type params: dict
    @param params: LD level disk parameters related to the synchronization
    @rtype: list
    @return: a list of error messages

    """
        cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
        result = utils.RunCmd(cmd)
        if result.failed:
            msg = ("Can't change syncer rate: %s - %s" %
                   (result.fail_reason, result.output))
            logging.error(msg)
            return [msg]

        return []
Example #28
0
    def PauseResumeSync(self, pause):
        """Pauses or resumes the sync of a DRBD device.

    See L{BlockDev.PauseResumeSync} for parameter description.

    """
        if self.minor is None:
            logging.info("Not attached during PauseSync")
            return False

        children_result = super(DRBD8Dev, self).PauseResumeSync(pause)

        if pause:
            cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
        else:
            cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)

        result = utils.RunCmd(cmd)
        if result.failed:
            logging.error("Can't %s: %s - %s", cmd, result.fail_reason,
                          result.output)
        return not result.failed and children_result
Example #29
0
    def _VerifyLXCCommands(cls):
        """Verify the validity of lxc command line tools.

    @rtype: list(str)
    @return: list of problem descriptions. the blank list will be returned if
             there is no problem.

    """
        msgs = []
        for cmd in cls._LXC_COMMANDS_REQUIRED:
            try:
                # lxc-ls needs special checking procedure.
                # there are two different version of lxc-ls, one is written in python
                # and the other is written in shell script.
                # we have to ensure the python version of lxc-ls is installed.
                if cmd == "lxc-ls":
                    help_string = utils.RunCmd(["lxc-ls", "--help"]).output
                    if "--running" not in help_string:
                        # shell script version has no --running switch
                        msgs.append(
                            "The python version of 'lxc-ls' is required."
                            " Maybe lxc was installed without --enable-python")
                else:
                    try:
                        version = cls._GetLXCVersionFromCmd(cmd)
                    except HypervisorError as err:
                        msgs.append(str(err))
                        continue

                    if version < cls._LXC_MIN_VERSION_REQUIRED:
                        msgs.append(
                            "LXC version >= %s is required but command %s has"
                            " version %s" %
                            (cls._LXC_MIN_VERSION_REQUIRED, cmd, version))
            except errors.OpExecError:
                msgs.append("Required command %s not found" % cmd)

        return msgs
Example #30
0
    def _GetLXCVersionFromCmd(cls, from_cmd):
        """Return the LXC version currently used in the system.

    Version information will be retrieved by command specified by from_cmd.

    @param from_cmd: the lxc command used to retrieve version information
    @type from_cmd: string
    @rtype: L{LXCVersion}
    @return: a version object which represents the version retrieved from the
             command

    """
        result = utils.RunCmd([from_cmd, "--version"])
        if result.failed:
            raise HypervisorError(
                "Failed to get version info from command %s: %s" %
                (from_cmd, result.output))

        try:
            return LXCVersion(result.stdout.strip())
        except ValueError as err:
            raise HypervisorError("Can't parse LXC version from %s: %s" %
                                  (from_cmd, err))