Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crmsh/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3155,7 +3155,7 @@ def sync_file(path):
Sync files between cluster nodes
"""
if _context.skip_csync2:
utils.cluster_copy_file(path, nodes=_context.node_list_in_cluster, output=False)
utils.cluster_copy_path(path, nodes=_context.node_list_in_cluster)
else:
csync2_update(path)

Expand Down
2 changes: 1 addition & 1 deletion crmsh/corosync.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def push_configuration(nodes):
'''
Push the local configuration to the list of remote nodes
'''
return utils.cluster_copy_file(conf(), nodes)
return utils.cluster_copy_path(conf(), nodes)


def pull_configuration(from_node):
Expand Down
8 changes: 4 additions & 4 deletions crmsh/ui_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,13 +963,13 @@ def do_run(self, context, cmd, *nodes):
else:
logger.info("[%s]\n%s", host, utils.to_ascii(result.stdout))

def do_copy(self, context, local_file, *nodes):
def do_copy(self, context, local_path, *nodes):
'''
usage: copy <filename> [nodes ...]
Copy file to other cluster nodes.
usage: copy <path> [nodes ...]
Copy file/directory to other cluster nodes.
If given no nodes as arguments, copy to all other cluster nodes.
'''
return utils.cluster_copy_file(local_file, nodes)
return utils.cluster_copy_path(local_path, nodes)

def do_diff(self, context, filename, *nodes):
"usage: diff <filename> [--checksum] [nodes...]. Diff file across cluster."
Expand Down
39 changes: 31 additions & 8 deletions crmsh/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1984,23 +1984,46 @@ def remote_checksum(local_path, nodes, this_node):
print("%-16s: %s" % (host, hashlib.sha1(f.read()).hexdigest()))


def cluster_copy_file(local_path, nodes=None, output=True):
def cluster_copy_path(local_path, nodes=None):
"""
Copies given file to all other cluster nodes.
Copies given file/directory to all other cluster nodes.
"""
if not nodes:
nodes = list_cluster_nodes_except_me()
rc = True
if not nodes:
return rc
results = prun.pcopy_to_remote(local_path, nodes, local_path)
return True

p = Path(local_path)
if not p.exists():
logger.error("%s does not exist", local_path)
return False

if p.is_absolute():
source_path = local_path
parent_path = p.parent
else:
absolute_path = p.resolve()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When p is a symlink /foo/link -> /bar, should we copy the symlink /foo/link or the link target /bar?

source_path = str(absolute_path)
parent_path = absolute_path.parent
mkdir_cmd = f"test -d {parent_path} || mkdir -p {parent_path}"
crmsh.parallax.parallax_call(nodes, mkdir_cmd)

recursive = False
sync_type = "file"
target_path = source_path
if p.is_dir():
recursive = True
sync_type = "directory"
target_path = parent_path

rc = True
results = prun.pcopy_to_remote(source_path, nodes, target_path, recursive=recursive)
for host, exc in results.items():
if exc is not None:
logger.error("Failed to copy %s to %s@%s: %s", local_path, exc.user, host, exc)
logger.error("Failed to copy %s to %s@%s: %s", source_path, exc.user, host, exc)
rc = False
else:
logger.info("%s", host)
logger.debug("Sync file %s to %s", local_path, host)
logger.info("Sync %s %s to %s", sync_type, source_path, host)
return rc


Expand Down
8 changes: 4 additions & 4 deletions doc/crm.8.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1014,17 +1014,17 @@ These commands enable easy installation and maintenance of a HA
cluster, by providing support for package installation, configuration
of the cluster messaging layer, file system setup and more.

[[cmdhelp_cluster_copy,Copy file to other cluster nodes]]
[[cmdhelp_cluster_copy,Copy file or directory to other cluster nodes]]
==== `copy`

Copy file to other cluster nodes.
Copy file or directory to other cluster nodes.

Copies the given file to all other nodes unless given a
Copies the given file or directory to all other nodes unless given a
list of nodes to copy to as argument.

Usage:
...............
copy <filename> [nodes ...]
copy <path> [nodes ...]
...............

Example:
Expand Down
17 changes: 17 additions & 0 deletions test/features/cluster_api.feature
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ Feature: Functional test to cover SAP clusterAPI
When Run "echo 'export PATH=$PATH:/usr/sbin/' > ~hacluster/.bashrc" on "hanode1"
When Run "echo 'export PATH=$PATH:/usr/sbin/' > ~hacluster/.bashrc" on "hanode2"

@clean
Scenario: Test cluster copy
When Try "crm cluster copy /tmp/none"
Then Expected "/tmp/none does not exist" in stderr
When Run "touch /tmp/file1" on "hanode1"
When Run "crm cluster copy /tmp/file1" on "hanode1"
When Run "ls /tmp/file1" on "hanode2"
Then Expected return code is "0"
When Run "touch /tmp/file2" on "hanode1"
When Run "cd /tmp; crm cluster copy file2" on "hanode1"
When Run "ls /tmp/file2" on "hanode2"
Then Expected return code is "0"
When Run "mkdir -p /tmp/dir1/dir2" on "hanode1"
When Run "crm cluster copy /tmp/dir1/dir2" on "hanode1"
When Run "test -d /tmp/dir1/dir2" on "hanode2"
Then Expected return code is "0"

@clean
Scenario: Start and stop resource by hacluster
When Run "su - hacluster -c 'crm resource stop d'" on "hanode1"
Expand Down
4 changes: 2 additions & 2 deletions test/unittests/test_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1404,11 +1404,11 @@ def test_adjust_properties(self, mock_is_active, mock_2node_qdevice, mock_adj_pc
mock_adj_priority.assert_called_once_with(True)
mock_adj_fence.assert_called_once_with(True)

@mock.patch('crmsh.utils.cluster_copy_file')
@mock.patch('crmsh.utils.cluster_copy_path')
def test_sync_file_skip_csync2(self, mock_copy):
bootstrap._context = mock.Mock(skip_csync2=True, node_list_in_cluster=["node1", "node2"])
bootstrap.sync_file("/file1")
mock_copy.assert_called_once_with("/file1", nodes=["node1", "node2"], output=False)
mock_copy.assert_called_once_with("/file1", nodes=["node1", "node2"])

@mock.patch('crmsh.bootstrap.csync2_update')
def test_sync_file(self, mock_csync2_update):
Expand Down
4 changes: 2 additions & 2 deletions test/unittests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1391,9 +1391,9 @@ def test_fetch_cluster_node_list_from_node(mock_run, mock_warn):


@mock.patch('crmsh.utils.list_cluster_nodes_except_me')
def test_cluster_copy_file_return(mock_list_nodes):
def test_cluster_copy_path_return(mock_list_nodes):
mock_list_nodes.return_value = []
assert utils.cluster_copy_file("/file1") == True
assert utils.cluster_copy_path("/file1") == True


@mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr')
Expand Down