pegasus-wms.worker 5.1.2__py3-none-any.whl

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.
Pegasus/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __path__ = __import__("pkgutil").extend_path(__path__, __name__)
@@ -0,0 +1 @@
1
+ __path__ = __import__("pkgutil").extend_path(__path__, __name__)
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import importlib
4
+ import logging
5
+ import logging.handlers
6
+ import os
7
+ import re
8
+ import signal
9
+ import sys
10
+ import tarfile
11
+ import threading
12
+ import time
13
+ from pathlib import Path
14
+ from typing import Iterable, List, Set
15
+
16
+ # PEGASUS_PYTHONPATH is set by the pegasus-python-wrapper script
17
+ peg_path = os.environ.get("PEGASUS_PYTHONPATH")
18
+ if peg_path:
19
+ for p in reversed(peg_path.split(":")):
20
+ if p not in sys.path:
21
+ sys.path.insert(0, p)
22
+
23
+ ptransfer_module = importlib.import_module("Pegasus.cli.pegasus-transfer")
24
+ pegasus_transfer = ptransfer_module.pegasus_transfer
25
+
26
+ log = logging.getLogger("pegasus-checkpoint")
27
+
28
+ # name of archived & compressed checkpoints
29
+ CHECKPOINT_FILENAME = "pegasus.checkpoint.tar.gz"
30
+
31
+ # pegasus-transfer url file expected to be generated by pegasus-lite
32
+ PEGASUS_TRANSFER_URL_FILE = "pegasus_checkpoint_transfer_urls.json"
33
+
34
+ # file to which this process's PID will be written upon startup
35
+ PEGASUS_CHECKPOINT_PID_FILE = "pegasus_checkpoint.pid"
36
+
37
+
38
+ def parse_args(args: List[str] = sys.argv[1:]) -> argparse.Namespace:
39
+ parser = argparse.ArgumentParser(
40
+ description="Periodically transfer checkpoints back to staging site."
41
+ )
42
+
43
+ parser.add_argument(
44
+ "-p",
45
+ "--pattern",
46
+ action="append",
47
+ dest="patterns",
48
+ metavar="PATTERN",
49
+ help="""regex patterns to match when searching for files to checkpoint""",
50
+ )
51
+
52
+ def validate_interval(value: str) -> int:
53
+ """
54
+ Ensure that given interval is an integer and is nonzero.
55
+
56
+ :param value: the given interval to validate
57
+ :type value: str
58
+ :raises argparse.ArgumentTypeError: interval cannot be casted to an int or is a negative integer
59
+ :return: the given value
60
+ :rtype: int
61
+ """
62
+ is_valid = True
63
+ try:
64
+ value = int(value)
65
+ except ValueError:
66
+ is_valid = False
67
+
68
+ if not is_valid or value < 0:
69
+ raise argparse.ArgumentTypeError(
70
+ f"interval {value} must be a nonnegative integer"
71
+ )
72
+
73
+ return value
74
+
75
+ parser.add_argument(
76
+ "-i",
77
+ "--interval",
78
+ type=validate_interval,
79
+ metavar="SECONDS",
80
+ help="""interval in seconds at which pegasus-checkpoint will send
81
+ checkpoint file back to the staging site
82
+ """,
83
+ )
84
+
85
+ # TODO: add --once to indicate pegasus-checkpoint is to run CheckpointWorker.run once
86
+
87
+ parser.add_argument(
88
+ "-d", "--debug", action="store_true", default=False, help="enable debug logging"
89
+ )
90
+
91
+ parser.add_argument(
92
+ "-l",
93
+ "--log-to-file",
94
+ action="store_true",
95
+ default=False,
96
+ help="enable logging to file pegasus-checkpoint.log",
97
+ )
98
+
99
+ return parser.parse_args(args)
100
+
101
+
102
+ def configure_logging(level: int, log_to_file: bool):
103
+ """
104
+ Setup logging.
105
+
106
+ :param level: log level to use; set to :code:`logging.DEBUG` to enable debug logging
107
+ :type level: int
108
+ :param log_to_file: whether or not to write logs to the file pegasus-checkpoint.log
109
+ :type log_to_file: bool
110
+ """
111
+ if level == logging.DEBUG:
112
+ level = logging.DEBUG
113
+ else:
114
+ level = logging.INFO
115
+
116
+ handlers = [logging.StreamHandler()]
117
+ if log_to_file:
118
+ handlers.append(
119
+ logging.handlers.RotatingFileHandler(
120
+ filename="pegasus-checkpoint.log",
121
+ mode="a",
122
+ maxBytes=(1 << 20),
123
+ backupCount=2,
124
+ )
125
+ )
126
+
127
+ logging.basicConfig(
128
+ level=level,
129
+ format="%(asctime)s %(threadName)s [%(levelname)s] %(message)s",
130
+ handlers=handlers,
131
+ )
132
+
133
+
134
+ def write_pid():
135
+ """Write PID to :code:`PEGASUS_CHECKPOINT_PID_FILE`"""
136
+ with open(PEGASUS_CHECKPOINT_PID_FILE, "w") as f:
137
+ pid = os.getpid()
138
+ f.write(str(pid))
139
+
140
+ log.debug(f"started with PID: {pid}")
141
+
142
+
143
+ class PeriodicCheckpointNotifier(threading.Thread):
144
+ """
145
+ Thread to periodically signal to the worker thread to start saving and
146
+ staging out checkpoint files.
147
+ """
148
+
149
+ log = logging.getLogger("pegasus-checkpoint.PeriodicCheckcpointNotifier")
150
+
151
+ def __init__(self, interval: int, notify: threading.Event):
152
+ """Constructor
153
+
154
+ :param interval: interval in seconds to sleep for
155
+ :type interval: int
156
+ :param notify: event to set, which will notify checkpoint worker thread to wake up and run
157
+ :type notify: threading.Event
158
+ """
159
+ super().__init__(group=None, target=None, name="PeriodicCheckpointNotifier")
160
+ self.interval = interval
161
+ self.notify = notify
162
+
163
+ def run(self):
164
+ """
165
+ Repeatedly sleep for the given interval, then notify checkpoint worker
166
+ thread to start working upon waking up.
167
+ """
168
+ self.log.debug(f"{self.name} started with interval: {self.interval} seconds")
169
+ while True:
170
+ time.sleep(self.interval)
171
+ self.notify.set()
172
+ self.log.debug("notify event set")
173
+
174
+
175
+ class CheckpointWorker(threading.Thread):
176
+ """Thread to handle saving and staging out checkpoint files."""
177
+
178
+ log = logging.getLogger("pegasus-checkpoint.CheckpointWorker")
179
+
180
+ def __init__(self, notify: threading.Event, patterns: List[str]):
181
+ """Constructor
182
+
183
+ :param notify: event to watch for, which will indicate that this thread needs to run
184
+ :type notify: threading.Event
185
+ :param patterns: regex file patterns to match
186
+ :type patterns: List[str]
187
+ """
188
+ super().__init__(group=None, target=None, name="CheckpointWorker")
189
+
190
+ self.notify = notify
191
+ self.patterns = patterns
192
+
193
+ def run(self):
194
+ """
195
+ Wait for notification to start working. Once notified, create the
196
+ checkpoint archive file and transfer back to staging site with either
197
+ pegasus-transfer or condor_chirp.
198
+ self.log.debug("{} started".format(self.name))
199
+ """
200
+ while True:
201
+ # wait till we are notified
202
+ self.notify.wait(timeout=None)
203
+ self.log.debug("got notification, starting work")
204
+
205
+ # work
206
+ matched_files = CheckpointWorker.get_matched_filenames(self.patterns)
207
+
208
+ CheckpointWorker.archive_and_compress(matched_files)
209
+
210
+ # TODO: add exception handling
211
+ pegasus_transfer(
212
+ max_attempts=3,
213
+ num_threads=8,
214
+ file=PEGASUS_TRANSFER_URL_FILE,
215
+ symlink=True,
216
+ )
217
+
218
+ # clear the notification, so we can wait until it is set again
219
+ self.notify.clear()
220
+ self.log.debug("work done, cleared notification")
221
+
222
+ @staticmethod
223
+ def get_matched_filenames(patterns: List[str]) -> Set[str]:
224
+ """
225
+ Get all files/folders in the current working directory that match the
226
+ given file patterns.
227
+
228
+ :param patterns: regex patterns to match filenames with
229
+ :type patterns: List[str]
230
+ :return: all files and or folders which match one of the given patterns
231
+ :rtype: Set[str]
232
+ """
233
+ patterns = [re.compile(p) for p in patterns]
234
+ files_in_cwd = [str(f) for f in Path(".").iterdir()]
235
+ matched = set()
236
+
237
+ # n^2.. maybe can do a little better by pulling out files from files_in_cwd (using a set
238
+ # isntead) as we make matches so we don't try to iterate over it again
239
+ for p in patterns:
240
+ for f in files_in_cwd:
241
+ is_match = bool(p.fullmatch(f))
242
+
243
+ if is_match:
244
+ matched.add(f)
245
+
246
+ CheckpointWorker.log.debug(
247
+ "pattern: {:<30}, file: {:>20}, match: {}".format(
248
+ p.pattern, f, is_match
249
+ )
250
+ )
251
+
252
+ CheckpointWorker.log.info(
253
+ f"given patterns matched the following filenames: {matched}"
254
+ )
255
+
256
+ return matched
257
+
258
+ @staticmethod
259
+ def archive_and_compress(filenames: Iterable[str]):
260
+ """
261
+ Given a list of file/folder names in CWD, archive and compress them into a single file
262
+ :code:`pegasus.checkpoint.tar.gz`
263
+
264
+ :param filenames: list of filenames to archive and compress
265
+ :type filenames: Iterable[str]
266
+ """
267
+ start = time.perf_counter()
268
+ with tarfile.open(CHECKPOINT_FILENAME, "w|gz") as tar:
269
+ for f in filenames:
270
+ tar.add(name=f, recursive=True)
271
+ end = time.perf_counter()
272
+
273
+ checkpoint_size_in_mb = Path(CHECKPOINT_FILENAME).stat().st_size / (1 << 20)
274
+
275
+ CheckpointWorker.log.info(
276
+ "created {} MB checkpoint file: {}".format(
277
+ checkpoint_size_in_mb, CHECKPOINT_FILENAME
278
+ )
279
+ )
280
+ CheckpointWorker.log.info(f"archive and gzip took {end - start} seconds")
281
+
282
+
283
+ if __name__ == "__main__":
284
+ args = parse_args()
285
+
286
+ log_level = logging.INFO
287
+ if args.debug:
288
+ log_level = logging.DEBUG
289
+
290
+ configure_logging(level=log_level, log_to_file=args.log_to_file)
291
+
292
+ write_pid()
293
+
294
+ # event to signal to worker thread to start saving checkpoints
295
+ notify = threading.Event()
296
+
297
+ # main thread to handle signal from user application
298
+ def SIGUSR1_handler(signum, frame):
299
+ log.info("SIGUSR1 received, setting notification")
300
+ notify.set()
301
+
302
+ signal.signal(signal.SIGUSR1, SIGUSR1_handler)
303
+
304
+ # create and start a periodic checkpoint notifier if user specified an interval to run on
305
+ periodic_notifier = None
306
+ if args.interval:
307
+ periodic_notifier = PeriodicCheckpointNotifier(
308
+ interval=args.interval, notify=notify
309
+ )
310
+ periodic_notifier.start()
311
+
312
+ worker = CheckpointWorker(notify=notify, patterns=args.patterns)
313
+ worker.start()
314
+
315
+ if periodic_notifier:
316
+ periodic_notifier.join()
317
+ worker.join()
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ from argparse import ArgumentParser
5
+
6
+ import globus_sdk
7
+ from globus_sdk.scopes import (
8
+ GCSCollectionScopeBuilder,
9
+ GCSEndpointScopeBuilder,
10
+ TransferScopes,
11
+ )
12
+
13
+ try:
14
+ from configparser import ConfigParser
15
+ except Exception:
16
+ from ConfigParser import ConfigParser
17
+
18
+ client_id = "d7382f5a-4ea3-4b69-b094-99c392fc820d"
19
+ config_file = os.path.expanduser("~/.pegasus/globus.conf")
20
+
21
+ parser = ArgumentParser(
22
+ description="Initialize Globus OAuth Tokens - SDK Version {}".format(
23
+ globus_sdk.__version__
24
+ )
25
+ )
26
+ parser.add_argument(
27
+ "-p", "--permanent", action="store_true", help="request a refreshable token",
28
+ )
29
+ parser.add_argument(
30
+ "-e",
31
+ "--endpoints",
32
+ nargs="*",
33
+ default=[],
34
+ help="list of endpoint uuids to acquire manage_collections consent",
35
+ )
36
+ parser.add_argument(
37
+ "-c",
38
+ "--collections",
39
+ nargs="*",
40
+ default=[],
41
+ help="list of collection uuids to acquire data_access consent",
42
+ )
43
+ parser.add_argument(
44
+ "-d",
45
+ "--domains",
46
+ nargs="*",
47
+ default=[],
48
+ help="list of domain requirements which must be satisfied by the identities under the globus user account",
49
+ )
50
+
51
+
52
+ args = parser.parse_args()
53
+
54
+ config = ConfigParser()
55
+ config.add_section("oauth")
56
+ config.set("oauth", "client_id", client_id)
57
+
58
+ pegasus_scopes = [TransferScopes.all]
59
+ for c in args.collections:
60
+ sb = GCSCollectionScopeBuilder(c, known_scopes=[TransferScopes.all])
61
+ pegasus_scopes.append(f"{TransferScopes.all}[*{sb.data_access}]")
62
+ pegasus_scopes.append(sb.data_access)
63
+
64
+ for e in args.endpoints:
65
+ sb = GCSEndpointScopeBuilder(e)
66
+ pegasus_scopes.append(sb.manage_collections)
67
+
68
+ required_domains = None
69
+ for d in args.domains:
70
+ if not required_domains:
71
+ required_domains = [d]
72
+ else:
73
+ required_domains.append(d)
74
+
75
+ client = globus_sdk.NativeAppAuthClient(client_id)
76
+ if args.permanent:
77
+ client.oauth2_start_flow(refresh_tokens=True, requested_scopes=pegasus_scopes)
78
+ else:
79
+ client.oauth2_start_flow(requested_scopes=pegasus_scopes)
80
+
81
+ authorize_url = client.oauth2_get_authorize_url(
82
+ session_required_single_domain=required_domains, prompt="login"
83
+ )
84
+ print(f"Please go to this URL and login: {authorize_url}")
85
+
86
+ get_input = getattr(__builtins__, "raw_input", input)
87
+ auth_code = get_input("Please enter the code you get after login here: ").strip()
88
+ token_response = client.oauth2_exchange_code_for_tokens(auth_code)
89
+
90
+ transfer_data = token_response.by_resource_server["transfer.api.globus.org"]
91
+
92
+ # get tokens as strings and their expiration timestamp
93
+ transfer_at = str(transfer_data["access_token"])
94
+ transfer_rt = str(transfer_data["refresh_token"])
95
+ transfer_at_exp = str(transfer_data["expires_at_seconds"])
96
+
97
+ config.set("oauth", "transfer_at", transfer_at) # transfer access token
98
+ config.set("oauth", "transfer_rt", transfer_rt) # transfer refresh token
99
+ config.set("oauth", "transfer_at_exp", transfer_at_exp) # transfer expiration time
100
+
101
+ cfg = open(config_file, "w+")
102
+ config.write(cfg)
103
+ cfg.close()