nodeocc 1.0.11__tar.gz → 1.0.13__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.
Files changed (23) hide show
  1. {nodeocc-1.0.11 → nodeocc-1.0.13}/PKG-INFO +1 -1
  2. {nodeocc-1.0.11 → nodeocc-1.0.13}/pyproject.toml +1 -1
  3. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/controller/controller.py +0 -13
  4. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/nodeocc.egg-info/PKG-INFO +1 -1
  5. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/view/curses_multiwindow.py +72 -46
  6. {nodeocc-1.0.11 → nodeocc-1.0.13}/README.md +0 -0
  7. {nodeocc-1.0.11 → nodeocc-1.0.13}/setup.cfg +0 -0
  8. {nodeocc-1.0.11 → nodeocc-1.0.13}/setup.py +0 -0
  9. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/__init__.py +0 -0
  10. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/model/infrastructure.py +0 -0
  11. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/model/job.py +0 -0
  12. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/nodeocc.egg-info/SOURCES.txt +0 -0
  13. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/nodeocc.egg-info/dependency_links.txt +0 -0
  14. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/nodeocc.egg-info/entry_points.txt +0 -0
  15. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/nodeocc.egg-info/requires.txt +0 -0
  16. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/nodeocc.egg-info/top_level.txt +0 -0
  17. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/readers/compute_wait_time.py +0 -0
  18. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/readers/slurmreader.py +0 -0
  19. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/view/__init__.py +0 -0
  20. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/view/slurm_list.py +0 -0
  21. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/view/slurm_viz.py +0 -0
  22. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/view/styles.py +0 -0
  23. {nodeocc-1.0.11 → nodeocc-1.0.13}/src/view/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.11
3
+ Version: 1.0.13
4
4
  Summary: TUI application for viewing the status of GPU allocations on a Slurm cluster
5
5
  Author: Matteo Boschini
6
6
  Maintainer: Lorenzo Bonicelli, Aniello Panariello
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nodeocc"
7
- version = "1.0.11"
7
+ version = "1.0.13"
8
8
  authors = [{ name = "Matteo Boschini" }]
9
9
  maintainers = [{ name = "Lorenzo Bonicelli" }, { name = "Aniello Panariello" }]
10
10
  description = "TUI application for viewing the status of GPU allocations on a Slurm cluster"
@@ -70,7 +70,6 @@ JOB_LIMIT_PER_MSG = 60
70
70
  parser = argparse.ArgumentParser(description='Visualize slurm jobs')
71
71
  parser.add_argument('--debug', action='store_true', help='Enable logging')
72
72
  parser.add_argument('--master_only', action='store_true', help='Disable all prints - only run in background')
73
- parser.add_argument('--force_override', action='store_true', help='Force override of port file, kill previous instance')
74
73
  parser.add_argument('--basepath', type=str, default='/nas/softechict-nas-2/mboschini/cool_scripts/new_nodeocc/', help='Base path for nodeocc. Must be readable by all users')
75
74
  args = parser.parse_args()
76
75
 
@@ -123,7 +122,6 @@ def send_msg(instance: Singleton, msg, n_extra_msgs=0, is_extra=False):
123
122
  def update_data_master(instance: Singleton):
124
123
  if instance.check_existing_master_running():
125
124
  instance.log(f"Master already running, killing current instance")
126
- instance.cleanup()
127
125
  exit(0)
128
126
 
129
127
  inf = readers.slurmreader.read_infrastructure()
@@ -271,16 +269,6 @@ def _main():
271
269
  assert orig_instance.is_master, "Instance is not `master`, run with `force_override` to override"
272
270
 
273
271
  orig_instance.log(f"Starting master daemon")
274
- # register atexit
275
- import atexit
276
-
277
- def exit_handler():
278
- orig_instance.log(f"Exiting...")
279
- orig_instance.sock.close()
280
- # remove .port file
281
- if orig_instance.port_file_exists():
282
- Path(orig_instance.get_port_file_name()[1]).unlink()
283
- atexit.register(exit_handler)
284
272
 
285
273
  while True:
286
274
  orig_instance.timeme(f"Updating...")
@@ -306,5 +294,4 @@ def _main():
306
294
 
307
295
  _main()
308
296
 
309
- orig_instance.cleanup()
310
297
  exit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nodeocc
3
- Version: 1.0.11
3
+ Version: 1.0.13
4
4
  Summary: TUI application for viewing the status of GPU allocations on a Slurm cluster
5
5
  Author: Matteo Boschini
6
6
  Maintainer: Lorenzo Bonicelli, Aniello Panariello
@@ -13,6 +13,7 @@ import traceback
13
13
  from logging.handlers import RotatingFileHandler
14
14
  from pathlib import Path
15
15
  from view import update_views
16
+ import stat
16
17
 
17
18
  a_filter_values = [None, 'me', 'prod', 'stud', 'cvcs']
18
19
 
@@ -35,11 +36,21 @@ BUTTON_ACTIONS = {
35
36
  }
36
37
 
37
38
 
39
+ def is_file_writable_byall(file_path):
40
+ return os.stat(file_path).st_mode & stat.S_IWOTH
41
+
42
+
43
+ def is_file_readable_byall(file_path):
44
+ return os.stat(file_path).st_mode & stat.S_IROTH
45
+
46
+
38
47
  def try_open_socket_as_slave(instance):
39
- if not instance.port_file_exists():
48
+ if len(instance.get_port_files()) == 0:
40
49
  raise Exception("No master running")
41
50
 
42
- cur_port = int(instance.get_port_file_name()[0].split('.')[0])
51
+ cur_port = [int(f.split('.')[0].split('master_')[1]) for f in instance.get_port_files()
52
+ if (pid := Path(f).read_text()) and psutil.pid_exists(int(pid))][0]
53
+
43
54
  if hasattr(instance, 'port'):
44
55
  # check if port file has same name
45
56
  if instance.port != cur_port:
@@ -67,14 +78,6 @@ class Singleton:
67
78
  Singleton.__instance.args = args
68
79
  return Singleton.__instance
69
80
 
70
- # destructor
71
- def cleanup(self):
72
- if self.is_master:
73
- if self.port_file_exists():
74
- os.system(f"rm {self.get_port_file_name()[1]}")
75
- if hasattr(self, 'sock') and self.sock is not None:
76
- self.sock.close()
77
-
78
81
  def setup_logging(self):
79
82
  if self.args.master_only:
80
83
  handler = RotatingFileHandler(os.path.join(self.basepath, '.master_log.txt'), maxBytes=5 * 1024 * 1024, backupCount=2, mode='w')
@@ -88,9 +91,18 @@ class Singleton:
88
91
  handlers=[handler])
89
92
 
90
93
  def clean_port_files(self):
94
+ nodename = socket.getfqdn().split('.')[0]
91
95
  for f in os.listdir(self.basepath):
92
- if f.endswith('.port'):
96
+ if nodename in f and 'master_' in f and f.endswith('.portfile') and is_file_writable_byall(os.path.join(self.basepath, f)):
97
+ # read pid from file
98
+ pid = Path(os.path.join(self.basepath, f)).read_text()
99
+ # assert no process is running
100
+ if psutil.pid_exists(int(pid)):
101
+ self.log(f"Process {pid} is still running, killing self")
102
+ return False
103
+
93
104
  os.system(f"rm {os.path.join(self.basepath, f)}")
105
+ return True
94
106
 
95
107
  def __init__(self, args=None):
96
108
  """ Virtually private constructor. """
@@ -131,49 +143,36 @@ class Singleton:
131
143
 
132
144
  self.basepath = self.args.basepath
133
145
 
134
- # check if any .port file exists
135
- if args.force_override and self.port_file_exists():
136
- _, port_filepath = self.get_port_file_name()
137
-
138
- # read pid from file
139
- pid = Path(port_filepath).read_text()
140
- # kill process
141
- try:
142
- os.kill(int(pid), signal.SIGTERM)
143
- except Exception as e:
144
- self.log("Could not kill process, deleting port file")
145
- self.log(e)
146
-
147
- # delete port file
148
- Path(port_filepath).unlink()
149
-
150
146
  self.is_master = not self.check_existing_master_running()
151
147
 
152
148
  if self.is_master:
153
- # clean up old port files
154
- self.clean_port_files()
155
149
  self.port = self.create_socket_as_master()
156
150
  else:
157
151
  try_open_socket_as_slave(self)
158
152
 
159
- def get_port_file_name(self):
160
- if self.port_file_exists():
161
- fname = [f for f in os.listdir(self.basepath) if f.endswith('.port')][0]
162
- return fname, os.path.join(self.basepath, fname)
163
- return None
164
-
165
153
  def check_existing_master_running(self):
166
- if self.port_file_exists():
167
- # check if process is still running
168
- pid = Path(self.get_port_file_name()[1]).read_text()
169
- if pid and int(pid) != os.getpid():
170
- return psutil.pid_exists(int(pid))
154
+ portfiles = self.get_port_files()
155
+ if len(portfiles) > 0:
156
+ for f in portfiles:
157
+ # check if process is still running
158
+ pid = Path(f).read_text()
159
+ if pid and int(pid) != os.getpid():
160
+ return psutil.pid_exists(int(pid))
171
161
  return False
172
162
 
173
- def port_file_exists(self):
174
- return len([f for f in os.listdir(self.basepath) if f.endswith('.port')]) > 0
163
+ def get_port_files(self):
164
+ """
165
+ Check if a port file exists in the basepath and the file has 666 permissions
166
+ """
167
+ nodename = socket.getfqdn().split('.')[0]
168
+ portfiles = [os.path.join(self.basepath, f) for f in os.listdir(self.basepath)
169
+ if f.endswith('.portfile') and 'master_' in f and nodename in f]
170
+ portfiles = [f for f in portfiles if is_file_readable_byall(f) and is_file_writable_byall(f)]
171
+ return portfiles
175
172
 
176
173
  def create_socket_as_master(self):
174
+ nodename = socket.getfqdn().split('.')[0]
175
+
177
176
  # create udp socket for broadcasting
178
177
  self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
179
178
 
@@ -191,8 +190,26 @@ class Singleton:
191
190
  # get pid of current process
192
191
  self.pid = os.getpid()
193
192
 
194
- # create file to store port
195
- Path(self.basepath, f'{self.port}.port').write_text(str(self.pid))
193
+ # clean up old port files
194
+ kill_ok = self.clean_port_files()
195
+ if not kill_ok:
196
+ return None
197
+
198
+ try:
199
+ # create file to store port with 666 permissions to file
200
+ filepath = Path(self.basepath, f'{nodename}_master_{self.port}.portfile')
201
+ filepath.write_text(str(self.pid))
202
+ os.chmod(filepath, 0o666)
203
+
204
+ self.port_filepath = filepath
205
+ except Exception as e:
206
+ self.err(f"Could not create port file: {e}")
207
+ self.err(traceback.format_exc())
208
+ # check if file exists
209
+ if filepath.exists():
210
+ # delete file
211
+ os.system(f"rm {filepath}")
212
+ return None
196
213
 
197
214
  return self.port
198
215
 
@@ -548,9 +565,12 @@ async def update_screen_info(stdscr, instance: Singleton):
548
565
  if not instance.check_existing_master_running():
549
566
  instance.log("MASTER STILL DEAD, TRYING TO BECOME MASTER")
550
567
  instance.setup_logging()
551
- instance.clean_port_files()
552
568
  instance.port = instance.create_socket_as_master()
553
569
  instance.is_master = True
570
+ else:
571
+ try_open_socket_as_slave(instance)
572
+ else:
573
+ try_open_socket_as_slave(instance)
554
574
 
555
575
  update_screen(stdscr, instance)
556
576
 
@@ -626,7 +646,13 @@ async def main(stdscr):
626
646
 
627
647
  def exit_handler(sig, frame):
628
648
  instance.log(f"FORCED EXIT...")
629
- instance.cleanup()
649
+ # instance.cleanup()
650
+ if hasattr(instance, 'port_filepath'):
651
+ os.system(f"rm {instance.port_filepath}")
652
+
653
+ if hasattr(instance, 'sock'):
654
+ instance.sock.close()
655
+
630
656
  exit(0)
631
657
  signal.signal(signal.SIGINT, exit_handler)
632
658
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes