hpc-task 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hpc_task-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 pj.ren
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: hpc_task
3
+ Version: 0.0.1
4
+ Summary: HPC Task python library.
5
+ Home-page: https://gitee.com/pjren/hpc_task
6
+ Author: Renpj
7
+ Author-email: 0403114076@163.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: paramiko
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: license
21
+ Dynamic: license-file
22
+ Dynamic: requires-dist
23
+ Dynamic: summary
24
+
25
+ # HPC Task
26
+
27
+ Python package for easy HPC task management based on paramiko.
28
+
29
+ ## Installation
30
+
31
+ pip install -U hpc_task
32
+
33
+ **Requirements**
34
+ * paramiko
35
+
36
+ ## Usage
37
+
38
+ See tests
39
+
40
+ ## TODO
@@ -0,0 +1,16 @@
1
+ # HPC Task
2
+
3
+ Python package for easy HPC task management based on paramiko.
4
+
5
+ ## Installation
6
+
7
+ pip install -U hpc_task
8
+
9
+ **Requirements**
10
+ * paramiko
11
+
12
+ ## Usage
13
+
14
+ See tests
15
+
16
+ ## TODO
File without changes
@@ -0,0 +1,147 @@
1
+ import os
2
+
3
+ import paramiko
4
+ from paramiko import SSHClient, SSHException
5
+
6
+
7
+ class HPCTask:
8
+ def __init__(self, workdir='.'):
9
+ self._ssh_jump = None
10
+ self.jobid = None
11
+ self.ssh_client = None
12
+ self.workdir = workdir
13
+
14
+ def connect(self, target_host, jump_host=None):
15
+ """
16
+ 开启队列
17
+ :return: jobid
18
+ """
19
+ # 建立 ssh 连接
20
+ # 跳板机连接信息
21
+ if self.ssh_client is None:
22
+ try:
23
+ # 使用隧道连接到目标服务器
24
+ target_client = SSHClient()
25
+ target_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
26
+ # 首先连接到跳板机
27
+ if jump_host is not None:
28
+ # 创建SSH客户端
29
+ jump_client = SSHClient()
30
+ jump_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
31
+ jump_client.connect(**jump_host)
32
+ # 在跳板机上创建到目标服务器的隧道
33
+ transport = jump_client.get_transport()
34
+ dest_addr = (target_host['hostname'], target_host['port'])
35
+ local_addr = ('127.0.0.1', 0) # 本地任意端口
36
+ channel = transport.open_channel('direct-tcpip', dest_addr, local_addr)
37
+ target_client.connect('127.0.0.1',
38
+ port=channel.getpeername()[1],
39
+ username=target_host['username'],
40
+ password=target_host['password'],
41
+ sock=channel)
42
+ self._ssh_jump = jump_client
43
+ else:
44
+ target_client.connect(**target_host)
45
+
46
+ self.ssh_client = target_client
47
+ print("Connect to SSH success.")
48
+ except SSHException as e:
49
+ raise e
50
+
51
+ def prerun(self):
52
+ # 提交任务,占据节点
53
+ # TODO: 命令改成配置,适配常见 hpc
54
+
55
+ if self.ssh_client is None:
56
+ raise RuntimeError('ssh client is not connected')
57
+ commands = [f'mkdir -p {self.workdir}',
58
+ f'cd {self.workdir}',
59
+ f'cp ~/bin/hpc_job.chess hpc_job.chess',
60
+ f'bsub < hpc_job.chess']
61
+ stdin, stdout, stderr = self.ssh_client.exec_command(';'.join(commands))
62
+ jobid = stdout.read().decode().strip() # "Job <688518> is submitted to queue <proj>."
63
+ jobid = jobid.split()[1].lstrip('<').rstrip('>')
64
+ self.jobid = jobid
65
+ return stdin, stdout, stderr
66
+
67
+ def postrun(self):
68
+ """
69
+ 说明:关闭任务节点占用
70
+ bkill JOBID是从任务头部开始杀, KILL 信号会传递到子进程
71
+ pkill gosh-remote 是直接杀
72
+ 二者可能相同, 也可能不相同. 取决于 bsub 时如何定义的.
73
+ 通常 bsub 是用一个 script 调 gosh-remote, 这时二者就不同了.
74
+ 那个主调script 可能会做信号处理, 会顺着 gosh-remote的调用进程下去, 逐一 KILL.
75
+ gosh-remote我不记得是否有信号处理的逻辑, 得做实验确认一下.
76
+
77
+ :return:
78
+
79
+ TODO: 命令改成配置,适配常见 hpc
80
+ """
81
+
82
+ if self.ssh_client is None:
83
+ raise RuntimeError('ssh client is not connected')
84
+ commands = ['sleep 1', 'pkill gosh-remote', f'bkill {self.jobid}']
85
+ stdin, stdout, stderr = self.ssh_client.exec_command(';'.join(commands))
86
+ return stdin, stdout, stderr
87
+
88
+ @property
89
+ def status(self):
90
+ """
91
+ 查询作业状态:排队,运行,结束
92
+ # TODO: 统一不同的排队系统状态码
93
+ :return:
94
+ """
95
+ if self.ssh_client is None:
96
+ raise RuntimeError('ssh client is not connected')
97
+ commands = [f'bjobs -noheader {self.jobid}',]
98
+ stdin, stdout, stderr = self.ssh_client.exec_command(';'.join(commands))
99
+ stat = stdout.read().decode().strip().split() # 688559 renpeng RUN proj Khpcserver0 72*Knode44 scheduler Sep 8 10:55
100
+ if len(stat) > 5:
101
+ return stat[2]
102
+ else:
103
+ return "UNKNOWN"
104
+
105
+ def submit(self):
106
+ """
107
+
108
+ :return: None
109
+ """
110
+ return None
111
+
112
+ def upload(self):
113
+ """
114
+ TODO: 使用 rsync
115
+ :return: file sync status
116
+ """
117
+ if self.ssh_client is None:
118
+ raise RuntimeError('ssh client is not connected')
119
+
120
+ # 创建服务器目录,如果不存在
121
+ self.ssh_client.exec_command(f'if [ ! -d "{self.workdir}" ]; then mkdir -p {self.workdir};fi')
122
+ sftp_client = self.ssh_client.open_sftp()
123
+ filelist = os.listdir(self.workdir) # TODO: 递归所有文件夹
124
+ for filename in filelist:
125
+ sftp_client.put(os.path.join(self.workdir,filename), os.path.join(self.workdir,filename), confirm=False)
126
+ sftp_client.close()
127
+ return None
128
+
129
+ def download(self):
130
+ if self.ssh_client is None:
131
+ raise RuntimeError('ssh client is not connected')
132
+
133
+ sftp_client = self.ssh_client.open_sftp()
134
+ for filename in sftp_client.listdir(self.workdir): # TODO: 递归所有文件夹
135
+ sftp_client.get(os.path.join(self.workdir,filename), os.path.join(self.workdir,filename))
136
+ sftp_client.close()
137
+ return None
138
+
139
+ def close(self):
140
+ """
141
+ 关闭队列
142
+ """
143
+ if self.ssh_client is not None:
144
+ self.ssh_client.close()
145
+ if self._ssh_jump is not None:
146
+ self._ssh_jump.close()
147
+ return None
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: hpc_task
3
+ Version: 0.0.1
4
+ Summary: HPC Task python library.
5
+ Home-page: https://gitee.com/pjren/hpc_task
6
+ Author: Renpj
7
+ Author-email: 0403114076@163.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: paramiko
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: license
21
+ Dynamic: license-file
22
+ Dynamic: requires-dist
23
+ Dynamic: summary
24
+
25
+ # HPC Task
26
+
27
+ Python package for easy HPC task management based on paramiko.
28
+
29
+ ## Installation
30
+
31
+ pip install -U hpc_task
32
+
33
+ **Requirements**
34
+ * paramiko
35
+
36
+ ## Usage
37
+
38
+ See tests
39
+
40
+ ## TODO
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ hpc_task/__init__.py
5
+ hpc_task/hpc.py
6
+ hpc_task.egg-info/PKG-INFO
7
+ hpc_task.egg-info/SOURCES.txt
8
+ hpc_task.egg-info/dependency_links.txt
9
+ hpc_task.egg-info/requires.txt
10
+ hpc_task.egg-info/top_level.txt
11
+ tests/test_task.py
@@ -0,0 +1 @@
1
+ paramiko
@@ -0,0 +1 @@
1
+ hpc_task
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ from setuptools import setup
2
+
3
+ with open("README.md", "r", encoding='utf-8') as f:
4
+ long_description = f.read()
5
+
6
+ install_requires = [
7
+ 'paramiko',
8
+ ]
9
+
10
+ setup(
11
+ name='hpc_task',
12
+ version='0.0.1',
13
+ packages=['hpc_task'],
14
+ url='https://gitee.com/pjren/hpc_task',
15
+ license='MIT',
16
+ author='Renpj',
17
+ author_email='0403114076@163.com',
18
+ description='HPC Task python library.',
19
+ long_description=long_description,
20
+ long_description_content_type="text/markdown",
21
+ install_requires=install_requires,
22
+ classifiers=[
23
+ "Programming Language :: Python :: 3",
24
+ "Operating System :: OS Independent",
25
+ ],
26
+ )
@@ -0,0 +1,94 @@
1
+ import os
2
+
3
+ import pytest
4
+ import ase
5
+ from ase.calculators.vasp import Vasp
6
+ from hpc_task.hpc import HPCTask
7
+
8
+ jump_host = {
9
+ 'hostname': os.getenv("JUMP_HOST_IP"),
10
+ 'port': int(os.getenv("JUMP_HOST_PORT")),
11
+ 'username': os.getenv("JUMP_HOST_USER"),
12
+ 'password': os.getenv("JUMP_HOST_PASS"), # 建议使用密钥认证而非密码
13
+ }
14
+
15
+ target_host = {
16
+ 'hostname': os.getenv("TARGET_HOST_IP"),
17
+ 'port': int(os.getenv("TARGET_HOST_PORT")),
18
+ 'username': os.getenv("TARGET_HOST_USER"),
19
+ 'password': os.getenv("TARGET_HOST_PASS"), # 建议使用密钥认证而非密码
20
+ }
21
+
22
+ class TestHPCTask:
23
+ def setup_method(self):
24
+ pass
25
+
26
+ def teardown_method(self):
27
+ pass
28
+
29
+
30
+ def test_connect(self):
31
+ hpc = HPCTask()
32
+ hpc.connect(target_host, jump_host)
33
+ stdin, stdout, stderr = hpc.ssh_client.exec_command('hostname')
34
+ print(stdout.read().decode().strip())
35
+ hpc.close()
36
+
37
+ def test_pre_post_run(self):
38
+ workdir = 'test_hpc_run'
39
+ hpc = HPCTask(workdir=workdir)
40
+ hpc.connect(target_host, jump_host)
41
+ stdin, stdout, stderr = hpc.prerun()
42
+ print(hpc.jobid)
43
+ print(f"Job {hpc.jobid} status: {hpc.status}")
44
+ stdin, stdout, stderr = hpc.postrun()
45
+ print(stdout.read().decode().strip())
46
+
47
+ def test_upload(self):
48
+ workdir = 'test_hpc_run'
49
+ hpc = HPCTask(workdir=workdir)
50
+ hpc.connect(target_host, jump_host)
51
+ # 在本地创建 workdir
52
+ if not os.path.exists(workdir):
53
+ os.makedirs(workdir)
54
+ # 新建一个空文件
55
+ with open(os.path.join(workdir, 'test.xyz'), 'w') as f:
56
+ f.write('test')
57
+ hpc.upload()
58
+
59
+ def test_download(self):
60
+ workdir = 'test_hpc_run'
61
+ hpc = HPCTask(workdir=workdir)
62
+ hpc.connect(target_host, jump_host)
63
+ if not os.path.exists(workdir):
64
+ os.makedirs(workdir)
65
+ hpc.download()
66
+
67
+ def test_vasp_calc(self):
68
+ atoms = ase.Atoms('N2', positions=[(0.,0.,0.),(1.4,0.,0.)],cell=[10,10,10], pbc=True)
69
+ workdir = 'test_vasp_calc'
70
+ curdir = os.path.abspath('.')
71
+ python_command = [ # TODO: 写成一个python 的 template
72
+ "from hpc_task.hpc import HPCTask",
73
+ "import os",
74
+ f"os.chdir('{curdir}')",
75
+ f"hpc = HPCTask(workdir='{workdir}')",
76
+ f"hpc.connect({target_host}, {jump_host})",
77
+ "hpc.upload()",
78
+ f"stdin,stdout,stderr = hpc.ssh_client.exec_command('cd {workdir};mpirun -np 2 vasp544_std')",
79
+ "print(stdout.read().decode().strip())",
80
+ "hpc.download()",
81
+ "hpc.close()",
82
+ ]
83
+ calc_command = f'python -c "{';'.join(python_command)}"'
84
+ calc = Vasp(xc='pbe',
85
+ command=calc_command,
86
+ directory=workdir,
87
+ gamma=True,
88
+ encut=400,
89
+ lwave=False,
90
+ lcharg=False,
91
+ )
92
+ atoms.calc = calc
93
+ e = atoms.get_potential_energy()
94
+ print(f"Energy of N2 is {e} eV.")