Skip to content

Commit ef6ba12

Browse files
Merge pull request #55 from HPCNow/main
Final development commit before extensive test development. froster v0.12.0
2 parents b256a18 + ed2e700 commit ef6ba12

File tree

4 files changed

+84
-72
lines changed

4 files changed

+84
-72
lines changed

Diff for: README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ Froster is a user-friendly archiving tool for teams that move data between highe
1010

1111
```
1212
sudo apt-get update
13-
sudo apt-get install -y curl pipx git gcc lib32gcc-s1 unzip fuse3
13+
sudo apt-get install -y curl pipx gcc lib32gcc-s1 unzip fuse3
1414
```
1515

1616
### On RHEL
1717

1818
```
1919
sudo yum update
20-
sudo yum install -y curl pipx git gcc lib32gcc-s1 unzip fuse3 python3-devel
20+
sudo yum install -y curl pipx gcc lib32gcc-s1 unzip fuse3 python3-devel
2121
```
2222

2323
### On HPC machine
2424

2525
Please contact your administrator to install these packages:
2626
```
27-
curl pipx git gcc lib32gcc-s1 unzip fuse3 python3.xx-devel
27+
curl pipx gcc lib32gcc-s1 unzip fuse3 python3.xx-devel
2828
```
2929

3030
</br>

Diff for: froster/froster.py

+80-56
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def __init__(self):
180180
# Last timestamp we checked for an updated
181181
self.update_check_timestamp = config.get(
182182
'UPDATE', 'timestamp', fallback=None)
183-
183+
184184
# Shared configuration
185185
self.is_shared = config.getboolean(
186186
'SHARED', 'is_shared', fallback=False)
@@ -1210,6 +1210,9 @@ def check_update(self):
12101210

12111211
if hasattr(self, 'update_check_timestamp') and self.update_check_timestamp is not None:
12121212
# Check if last day was less than 86400 * 7 = (1 day) * 7 = 1 week
1213+
print(timestamp)
1214+
print(self.update_check_timestamp)
1215+
print(timestamp - self.update_check_timestamp)
12131216
if timestamp - self.update_check_timestamp < (86400*7):
12141217
# Less than a week since last check
12151218
return False
@@ -3241,7 +3244,7 @@ def _index_locally(self, folder):
32413244
except Exception:
32423245
print_error()
32433246

3244-
def _slurm_cmd(self, folders, cmd_type):
3247+
def _slurm_cmd(self, folders, cmd_type, scheduled):
32453248
'''Execute the current command using SLURM'''
32463249

32473250
try:
@@ -3262,7 +3265,8 @@ def _slurm_cmd(self, folders, cmd_type):
32623265
se.submit_job(cmd=cmd,
32633266
cmd_type=cmd_type,
32643267
label=label,
3265-
shortlabel=shortlabel)
3268+
shortlabel=shortlabel,
3269+
scheduled=scheduled)
32663270

32673271
except Exception:
32683272
print_error()
@@ -4458,41 +4462,50 @@ def restore(self, folders, aws: AWSBoto):
44584462
f' froster archive --permissions "/your/folder/to/archive"\n', file=sys.stderr)
44594463
return
44604464

4461-
if use_slurm(self.args.noslurm):
4462-
self._slurm_cmd(folders=folders, cmd_type='restore')
44634465

4464-
else:
4465-
# Archive locally all folders. If recursive flag set, archive all subfolders too.
4466-
for folder in folders:
4467-
for root, dirs, files in self._walker(folder):
4466+
# Archive locally all folders. If recursive flag set, archive all subfolders too.
4467+
for folder in folders:
4468+
for root, dirs, files in self._walker(folder):
44684469

4469-
# Break in case of non-recursive restore
4470-
if not is_recursive and root != folder:
4471-
break
4470+
# Break in case of non-recursive restore
4471+
if not is_recursive and root != folder:
4472+
break
44724473

4473-
archived_folder_info = self.froster_archives_get_entry(
4474-
root)
4474+
archived_folder_info = self.froster_archives_get_entry(
4475+
root)
44754476

4476-
if archived_folder_info is None:
4477-
print(f'\nFolder {root} is not archived')
4478-
print(f'No entry found in froster-archives.json\n')
4479-
continue
4477+
if archived_folder_info is None:
4478+
print(f'\nFolder {root} is not archived')
4479+
print(f'No entry found in froster-archives.json\n')
4480+
continue
44804481

4481-
if not self._contains_non_froster_files(root):
4482-
print(f'\nWARNING: Folder {root} contains non-froster metadata files')
4483-
print('Has this folder been deleted using "froster delete" command?.')
4484-
print('Please empty the folder before restoring.\n')
4485-
continue
4482+
if not self._contains_non_froster_files(root):
4483+
print(
4484+
f'\nWARNING: Folder {root} contains non-froster metadata files')
4485+
print(
4486+
'Has this folder been deleted using "froster delete" command?.')
4487+
print('Please empty the folder before restoring.\n')
4488+
continue
44864489

4487-
if self._restore_locally(root, aws):
4490+
if self._restore_locally(root, aws):
4491+
# Already restored
44884492

4489-
# If nodownload flag is set we are done
4490-
if self.args.nodownload:
4491-
print(
4492-
f'\nFolder restored but not downloaded (--no-download flag set)\n')
4493-
return
4494-
else:
4493+
# If nodownload flag is set we are done
4494+
if self.args.nodownload:
4495+
print(
4496+
f'\nFolder restored but not downloaded (--no-download flag set)\n')
4497+
return
4498+
else:
44954499
self._download(root)
4500+
else:
4501+
# Restore ongoing
4502+
# In this case the slurm will only be used for downloading. AWS has taken care of the restore
4503+
if is_slurm_installed() and not self.args.noslurm:
4504+
# schedule execution in 12 hours
4505+
self._slurm_cmd(
4506+
folders=folders, cmd_type='restore', scheduled=12)
4507+
4508+
44964509

44974510
except Exception:
44984511
print_error()
@@ -4518,7 +4531,8 @@ def _restore_verify(self, source, target):
45184531
return
45194532

45204533
# Get the path to the hashfile
4521-
hashfile = os.path.join(restpath, self.md5sum_restored_filename)
4534+
hashfile = os.path.join(
4535+
restpath, self.md5sum_restored_filename)
45224536

45234537
# Create the Rclone object
45244538
rclone = Rclone(self.args, self.cfg)
@@ -4539,7 +4553,8 @@ def _restore_verify(self, source, target):
45394553
os.remove(tar_path)
45404554
print(' ...done\n')
45414555

4542-
where_did_file_go_full_path = os.path.join(target, self.where_did_the_files_go_filename)
4556+
where_did_file_go_full_path = os.path.join(
4557+
target, self.where_did_the_files_go_filename)
45434558
if os.path.exists(where_did_file_go_full_path):
45444559
os.remove(where_did_file_go_full_path)
45454560

@@ -5581,7 +5596,7 @@ def _reorder_sbatch_lines(self, script_buffer):
55815596
except Exception:
55825597
print_error()
55835598

5584-
def submit_job(self, cmd, cmd_type, label, shortlabel):
5599+
def submit_job(self, cmd, cmd_type, label, shortlabel, scheduled=None):
55855600
'''Submit a Slurm job'''
55865601

55875602
try:
@@ -5594,6 +5609,9 @@ def submit_job(self, cmd, cmd_type, label, shortlabel):
55945609
f'#SBATCH --job-name=froster:{cmd_type}:{shortlabel}')
55955610
self.add_line(f'#SBATCH --cpus-per-task={self.args.cores}')
55965611
self.add_line(f'#SBATCH --mem={self.args.memory}')
5612+
if scheduled:
5613+
self.add_line(f'#SBATCH --begin={scheduled}')
5614+
self.add_line(f'#SBATCH --requeue')
55975615
self.add_line(f'#SBATCH --output={output_dir}-%J.out')
55985616
self.add_line(f'#SBATCH --mail-type=FAIL,REQUEUE,END')
55995617
self.add_line(f'#SBATCH --mail-user={self.cfg.email}')
@@ -6201,7 +6219,8 @@ def subcmd_archive(self, arch: Archiver, aws: AWSBoto):
62016219
return
62026220

62036221
if not aws.check_credentials():
6204-
print('\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
6222+
print(
6223+
'\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
62056224
sys.exit(1)
62066225

62076226
# Check if the user provided the hotspots argument
@@ -6231,7 +6250,8 @@ def subcmd_restore(self, arch: Archiver, aws: AWSBoto):
62316250

62326251
try:
62336252
if not aws.check_credentials():
6234-
print('\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
6253+
print(
6254+
'\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
62356255
sys.exit(1)
62366256

62376257
if self.args.monitor:
@@ -6274,9 +6294,10 @@ def subcmd_delete(self, arch: Archiver, aws: AWSBoto):
62746294

62756295
try:
62766296
if not aws.check_credentials():
6277-
print('\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
6297+
print(
6298+
'\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
62786299
sys.exit(1)
6279-
6300+
62806301
if not self.args.folders:
62816302

62826303
# Get the list of folders from the archive
@@ -6311,9 +6332,10 @@ def subcmd_mount(self, arch: Archiver, aws: AWSBoto):
63116332

63126333
try:
63136334
if not aws.check_credentials():
6314-
print('\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
6335+
print(
6336+
'\nError: invalid credentials. Check the AWS configuration with "froster config --aws"\n')
63156337
sys.exit(1)
6316-
6338+
63176339
if self.args.list:
63186340
arch.print_current_mounts()
63196341
return
@@ -6454,19 +6476,20 @@ def subcmd_credentials(self, cfg: ConfigManager, aws: AWSBoto):
64546476
print(' ...AWS credentials are NOT valid\n')
64556477
return False
64566478

6457-
def subcmd_update(self):
6479+
def subcmd_update(self, mute_no_update):
64586480
'''Check if an update is available'''
64596481
try:
64606482

6461-
cmd = "curl -s https://api.github.com/repos/dirkpetersen/froster/releases"
6462-
6483+
cmd = "curl -s https://api.github.com/repos/hpcnow/froster/releases"
6484+
64636485
result = subprocess.run(cmd, shell=True, text=True,
64646486
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
6465-
6487+
64666488
if result.returncode != 0:
6467-
print(f"Error checking if froster update available. Command run: {cmd}: {result.stderr.strip()}")
6489+
print(
6490+
f"Error checking if froster update available. Command run: {cmd}: {result.stderr.strip()}")
64686491
return False
6469-
6492+
64706493
def compare_versions(version1, version2):
64716494
v1 = [int(v) for v in version1.split(".")]
64726495
v2 = [int(v) for v in version2.split(".")]
@@ -6477,7 +6500,7 @@ def compare_versions(version1, version2):
64776500
if v1_part != v2_part:
64786501
return v1_part - v2_part
64796502
return 0
6480-
6503+
64816504
releases = json.loads(result.stdout)
64826505
if not releases:
64836506
print('Note: Could not check for updates')
@@ -6488,10 +6511,12 @@ def compare_versions(version1, version2):
64886511

64896512
if compare_versions(latest, current) > 0:
64906513
print(f'\nA froster update is available: froster v{latest}')
6491-
print(f'You can update froster using the command:')
6492-
print(f' curl -s https://raw.githubusercontent.com/dirkpetersen/froster/main/install.sh?$(date +%s) | bash\n')
6514+
print(f'\nYou can update froster using the command:')
6515+
print(
6516+
f' curl -s https://raw.githubusercontent.com/dirkpetersen/froster/main/install.sh?$(date +%s) | bash\n')
64936517
else:
6494-
print(f'\nFroster is up to date: froster v{current}\n')
6518+
if not mute_no_update:
6519+
print(f'\nFroster is up to date: froster v{current}\n')
64956520

64966521
except Exception:
64976522
print_error()
@@ -6710,19 +6735,18 @@ def parse_arguments(self):
67106735
parser_restore.add_argument('folders', action='store', default=[], nargs='*',
67116736
help='folders you would like to to restore (separated by space)')
67126737

6713-
67146738
parser_restore.add_argument('-a', '--aws', dest='aws', action='store_true',
67156739
help="Restore folder on new AWS EC2 instance instead of local machine")
6716-
6740+
67176741
parser_restore.add_argument('-d', '--days', dest='days', action='store', default=30,
67186742
help='Number of days to keep data in S3 One Zone-IA storage at $10/TiB/month (default: 30)')
6719-
6743+
67206744
parser_restore.add_argument('-i', '--instance-type', dest='instancetype', action='store', default="",
67216745
help='The EC2 instance type is auto-selected, but you can pick any other type here')
67226746

67236747
parser_restore.add_argument('-l', '--no-download', dest='nodownload', action='store_true',
67246748
help="skip download to local storage after retrieval from Glacier")
6725-
6749+
67266750
parser_restore.add_argument('-m', '--monitor', dest='monitor', action='store_true',
67276751
help="Monitor EC2 server for cost and idle time.")
67286752

@@ -6945,13 +6969,13 @@ def main():
69456969
elif args.subcmd in ['credentials', 'crd']:
69466970
cmd.subcmd_credentials(cfg, aws)
69476971
elif args.subcmd in ['update', 'upd']:
6948-
cmd.subcmd_update()
6972+
cmd.subcmd_update(mute_no_update=False)
69496973
else:
69506974
cmd.print_help()
69516975

69526976
# Check if there are updates on froster every X days
6953-
if cfg.check_update():
6954-
cmd.subcmd_update()
6977+
if cfg.check_update() and args.subcmd not in ['update', 'upd']:
6978+
cmd.subcmd_update(mute_no_update=True)
69556979

69566980
# Close the AWS session
69576981
aws.close_session()

Diff for: install.sh

-12
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,6 @@ check_apt_dependencies() {
129129
exit 1
130130
fi
131131

132-
# Check if git is installed (pipx installation requirement)
133-
# TODO: Get rid of this requirement once froster is in PyPi repository
134-
if [[ -z $(command -v git) ]]; then
135-
echo "Error: git is not installed."
136-
echo
137-
echo "Please install git"
138-
echo "In most linux distros you can install the latest version of git by running the following commands:"
139-
echo " sudo apt update"
140-
echo " sudo apt install -y git"
141-
echo
142-
exit 1
143-
fi
144132

145133
# Check if unzip is installed (rclone requirement)
146134
if [[ -z $(command -v unzip) ]]; then

Diff for: pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "froster"
7-
version = "0.11.8"
7+
version = "0.12.0"
88
description = "Froster is a tool for easy data transfer between local file systems and AWS S3 storage."
99
authors = ["Victor Machado <[email protected]>"]
1010
readme = "README.md"

0 commit comments

Comments
 (0)