Skip to content

Commit fe30c09

Browse files
committed
Storage: add SS auth, log config. ref #9910 PR #22
- Add SS API key authentication - Add log level configuration - Update README and sample script - Bug fix
1 parent 587bce3 commit fe30c09

File tree

4 files changed

+73
-18
lines changed

4 files changed

+73
-18
lines changed

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ The Automation Tools project is a set of python scripts, that are designed to au
2020
- [user-input](#user-input)
2121
- [Logs](#logs)
2222
- [Multiple automated transfer instances](#multiple-automated-transfer-instances)
23+
- [Automated package moving](#automated-package-moving)
2324
- [Related Projects](#related-projects)
2425

2526
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -199,6 +200,37 @@ You may need to set up multiple automated transfer instances, for example if req
199200

200201
In case different hooks are required for each instance, a possible approach is to checkout a new instance of the automation tools, for example in `/usr/lib/archivematica/automation-tools-2`
201202

203+
204+
Automated package moving
205+
------------------------
206+
207+
`storage/move_packages.py` is a helper script used to automate moving packages between locations.
208+
209+
The script takes the UUID of a `<move_from>` Location, and the UUID of a `<move_to>` Location.
210+
211+
When executed, the script will:
212+
213+
* query the storage service and ask for a list of packages in the move_from Location.
214+
* check if the first package returned is in the automation tools db.
215+
* If it is not there, the script will call the move_package endpoint with this packages’s UUID and the UUID of the move_to Location, then exit.
216+
* The next time the script is executed, it will query the status of the package.
217+
* If it is ‘moving’, the script will go back to sleep.
218+
* Once the status of the current package is no longer ‘moving’, the script will go on to the next package.
219+
220+
The script makes use of the `move` endpoint in the Storage Service REST API.
221+
The `move` endpoint takes two arguments: UUID of an existing package (AIP or DIP or transfer) and the UUID of a Location.
222+
223+
The move_package endpoint will:
224+
* Confirm that the type of package (AIP or DIP or Transfer) matches the new Location
225+
* Set the status of the package to ‘moving’
226+
* Copy the package from its current location to the new location using rsync and leave the original copy of the package alone
227+
* Execute any post store hooks configured for the Location (for example, call the Arkivum finalize command)
228+
* Update the internal storage service database with the new location of the package (and new status, set by the Space)
229+
* If the rsync command does not work or there is a failure in post store commands, the status of the package will be set to ‘move failed’, and the internal ss database will not be updated
230+
231+
The `etc` directory contains an example script (`storage-script.sh`) and config file (`storage.conf`)
232+
233+
202234
Related Projects
203235
----------------
204236

common/utils.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def get_setting(config_file, config_name, setting, default=None):
2222
return default
2323

2424

25-
def configure_logging(name, filename):
25+
def configure_logging(name, filename, loglevel):
2626
"""
2727
Configure logging
2828
@@ -54,7 +54,7 @@ def configure_logging(name, filename):
5454
},
5555
'loggers': {
5656
name: {
57-
'level': 'INFO', # One of INFO, DEBUG, WARNING, ERROR, CRITICAL
57+
'level': loglevel, # One of INFO, DEBUG, WARNING, ERROR, CRITICAL
5858
'handlers': ['console', 'file'],
5959
},
6060
},
@@ -73,10 +73,10 @@ def open_pid_file(pid_file, logger=None):
7373
try:
7474
# Open PID file only if it doesn't exist for read/write
7575
f = os.fdopen(os.open(pid_file, os.O_CREAT | os.O_EXCL | os.O_RDWR), 'r+')
76-
except OSError as e:
77-
if logger:
78-
logger.info('Error accessing pid file %s: %s', pid_file, exc_info=True)
79-
return None
76+
except OSError:
77+
if logger:
78+
logger.info('Error accessing pid file %s:', pid_file, exc_info=True)
79+
return None
8080
except Exception:
8181
if logger:
8282
logger.info('This script is already running. To override this behaviour and start a new run, remove %s', pid_file)

etc/storage-script.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
# storage move package script example
3+
# /etc/archivematica/automation-tools/storage-script.sh
4+
cd /usr/lib/archivematica/automation-tools/
5+
/usr/share/python/automation-tools/bin/python -m storage.move_packages --config-file /etc/archivematica/automation-tools/storage.conf --ss-user USERNAME --ss-api-key KEY --from-location a13e466d-a144-430a-85b3-95e6aaa52f20 --to-location fbdf5325-c342-406a-ba66-3f4e3f73cf5f

storage/move_packages.py

+30-12
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import logging.config # Has to be imported separately
1313
import os
1414
import requests
15-
from six.moves import configparser
1615
import sys
1716

1817
# This project
@@ -30,21 +29,23 @@ def get_setting(setting, default=None):
3029
return utils.get_setting(CONFIG_FILE, 'storage', setting, default)
3130

3231

33-
def setup(config_file):
32+
def setup(config_file, log_level):
3433
global CONFIG_FILE
3534
CONFIG_FILE = config_file
3635

3736
# Configure logging
3837
default_logfile = os.path.join(THIS_DIR, 'automate-storage.log')
3938
logfile = get_setting('logfile', default_logfile)
40-
utils.configure_logging('storage', logfile)
39+
utils.configure_logging('storage', logfile, log_level)
4140

4241

43-
def get_first_eligible_package_in_location(ss_url, location_uuid):
42+
def get_first_eligible_package_in_location(ss_url, ss_user, ss_api_key, location_uuid):
4443
"""
4544
Get first package in a location that has a status of either UPLOADED or MOVING.
4645
4746
:param str ss_url: Storage service URL
47+
:param ss_user: User on the Storage Service for authentication
48+
:param ss_api_key: API key for user on the Storage Service for authentication
4849
:param str location_uuid: UUID of location to fetch package details from
4950
:returns: Dict containing package details or None if none found
5051
"""
@@ -55,7 +56,9 @@ def get_first_eligible_package_in_location(ss_url, location_uuid):
5556
("current_location__uuid", location_uuid),
5657
("status__in", "UPLOADED"),
5758
("status__in", "MOVING"),
58-
("order_by", "uuid")]
59+
("order_by", "uuid"),
60+
("username", ss_user),
61+
("api_key", ss_api_key)]
5962

6063
result = utils.call_url_json(get_url, params, LOGGER)
6164
if 'objects' in result and len(result['objects']):
@@ -64,22 +67,31 @@ def get_first_eligible_package_in_location(ss_url, location_uuid):
6467
return None
6568

6669

67-
def move_to_location(ss_url, package_uuid, location_uuid):
70+
def move_to_location(ss_url, ss_user, ss_api_key, package_uuid, location_uuid):
6871
"""
6972
Send request to move package to another location.
7073
7174
:param str ss_url: Storage service URL
75+
:param ss_user: User on the Storage Service for authentication
76+
:param ss_api_key: API key for user on the Storage Service for authentication
7277
:param str package_uuid: UUID of package to move
7378
:param str location_uuid: UUID of location to move package to
7479
:returns: Dict representing JSON response.
7580
"""
7681
LOGGER.info("Moving package %s to location %s", package_uuid, location_uuid)
7782

7883
post_url = '%s/api/v2/file/%s/move/' % (ss_url, package_uuid)
79-
post_data = {'location_uuid': location_uuid}
84+
params = {
85+
'username': ss_user,
86+
'api_key': ss_api_key,
87+
}
88+
post_data = {
89+
'location_uuid': location_uuid,
90+
}
8091
LOGGER.debug('URL: %s; Body: %s;', post_url, json.dumps(post_data))
8192

8293
r = requests.post(post_url,
94+
params=params,
8395
json=post_data,
8496
headers={'content-type': 'application/json'})
8597
LOGGER.debug('Response: %s', r)
@@ -90,9 +102,9 @@ def move_to_location(ss_url, package_uuid, location_uuid):
90102
return r.json()
91103

92104

93-
def main(ss_url, from_location_uuid, to_location_uuid, config_file=None):
105+
def main(ss_url, ss_user, ss_api_key, from_location_uuid, to_location_uuid, config_file=None, log_level='INFO'):
94106

95-
setup(config_file)
107+
setup(config_file, log_level)
96108

97109
LOGGER.info("Waking up")
98110

@@ -104,14 +116,14 @@ def main(ss_url, from_location_uuid, to_location_uuid, config_file=None):
104116

105117
# Check statuis of last package and attempt move
106118
move_result = None
107-
package = get_first_eligible_package_in_location(ss_url, from_location_uuid)
119+
package = get_first_eligible_package_in_location(ss_url, ss_user, ss_api_key, from_location_uuid)
108120
if package is None:
109121
LOGGER.info('No packages remain in location, nothing to do.')
110122
elif package['status'] == 'MOVING':
111123
LOGGER.info('Current package %s still processing, nothing to do.', package['uuid'])
112124
else:
113125
LOGGER.info('Moving package %s.', package['uuid'])
114-
move_result = move_to_location(ss_url, package['uuid'], to_location_uuid)
126+
move_result = move_to_location(ss_url, ss_user, ss_api_key, package['uuid'], to_location_uuid)
115127
if move_result is None:
116128
LOGGER.info('Move request failed')
117129
else:
@@ -125,14 +137,20 @@ def main(ss_url, from_location_uuid, to_location_uuid, config_file=None):
125137

126138
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
127139
parser.add_argument('--ss-url', '-s', metavar='URL', help='Storage Service URL. Default: http://127.0.0.1:8000', default='http://127.0.0.1:8000')
140+
parser.add_argument('--ss-user', metavar='USERNAME', required=True, help='Username of the Storage Service user to authenticate as.')
141+
parser.add_argument('--ss-api-key', metavar='KEY', required=True, help='API key of the Storage Service user.')
128142
parser.add_argument('--from-location', '-f', metavar='SOURCE', help="UUID of source location.", required=True)
129143
parser.add_argument('--to-location', '-t', metavar='DEST', help="UUID of destination location.", required=True)
130144
parser.add_argument('--config-file', '-c', metavar='FILE', help='Configuration file(log/db/PID files)', default=None)
145+
parser.add_argument('--log-level', choices=['ERROR', 'WARNING', 'INFO', 'DEBUG'], default='INFO', help='Set the debugging output level.')
131146
args = parser.parse_args()
132147

133148
sys.exit(main(
134149
ss_url=args.ss_url,
150+
ss_user=args.ss_user,
151+
ss_api_key=args.ss_api_key,
135152
from_location_uuid=args.from_location,
136153
to_location_uuid=args.to_location,
137-
config_file=args.config_file
154+
config_file=args.config_file,
155+
log_level=args.log_level,
138156
))

0 commit comments

Comments
 (0)