This repository has been archived by the owner on Mar 6, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
cernbox-swan-project
executable file
·349 lines (256 loc) · 11.5 KB
/
cernbox-swan-project
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#!/usr/bin/env python2
# -*- python -*-
#
# The CERNBox Project.
#
# Author:
# License: AGPL
#
#$Id: $
#
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Perform internal setup of the environment.
# This is a Copy/Paste logic which must stay in THIS file
def standardSetup():
import sys, os.path
# insert the path to cernafs based on the relative position of this scrip inside the service directory tree
exeDir = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
pythonDir = os.path.join(exeDir, 'python' )
sys.path.insert(0, pythonDir)
import cernbox_utils.setup
cernbox_utils.setup.standardSetup(sys.argv[0]) # execute a setup hook
standardSetup()
del standardSetup
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
config = None
logger = None
eos = None # EOS interface instance
eos_no_backend = None # EOS interface instance without backend (it will be passed as ENV)
db = None # DB interface instance
eos_mgm_url = None
import cernbox_utils
from cernbox_utils.errors import CmdBadRequestError
from cernbox_utils.script import get_eos_server
import os, os.path, sys
import subprocess
import pwd
def main():
global config,logger
import cernbox_utils.script
from cernbox_utils.eos import is_special_folder
parser=cernbox_utils.script.arg_parser(description='Manipulate SWAN projects.')
parser.set_defaults(logfile="/var/log/cboxshareadmin.swanproject.log",configfile="/etc/cboxshareadmin.ini")
subparser = parser.add_subparsers(title='command',dest='cmd')
subcmd = subparser.add_parser('list-shared-by', help="list all SWAN projects shared by the user")
subcmd.add_argument("user", help="specify owner")
subcmd.add_argument("--project", default=None, action='store', help="specify particular project to list")
subcmd = subparser.add_parser('list-shared-with', help="list all SWAN projects shared with the user")
subcmd.add_argument("user", help="specify sharee")
subcmd = subparser.add_parser('update-share', help="update share for a SWAN project which will be shared to the specified sharees (and only to them)")
subcmd.add_argument("owner", help="share owner")
subcmd.add_argument("project", help="project path")
subcmd.add_argument("sharees", help="fully qualified names of sharees",nargs='+')
subcmd = subparser.add_parser('delete-share', help="delete all shares for a SWAN project")
subcmd.add_argument("owner", help="share owner")
subcmd.add_argument("project", help="project path")
subcmd = subparser.add_parser('clone-share', help="clone <shared_project> shared by <sharer> (with <receiver>) as project <cloned_project> to be owned by <receiver>")
subcmd.add_argument("sharer", help="name of the sharer")
subcmd.add_argument("shared_project", help="project to clone")
subcmd.add_argument("receiver", help="name of the user to receive the cloned project")
subcmd.add_argument("cloned_project", help="name of the project at new destination")
args = parser.parse_args()
# this script does json output by default
args.json = True
config = cernbox_utils.script.configure(args.configfile)
logger = cernbox_utils.script.getLogger(level=args.loglevel,filename=args.logfile)
if 'owner' in args:
this_user = args.owner
elif 'sharer' in args:
this_user = args.sharer
else:
this_user = args.user
global eos_mgm_url
eos_mgm_url = get_eos_server(this_user)
logger.info("Running command: %s",str(sys.argv))
logger.info("Using DB: %s",config['dbhost'])
logger.info("Using EOS: %s", eos_mgm_url)
logger.debug("getting gid of user")
p = pwd.getpwnam(this_user) # will end up with KeyError if user not resolved
role = (p.pw_uid,p.pw_gid)
logger.debug("got gid %s"%str(role))
global eos,eos_no_backend,db
import cernbox_utils.db, cernbox_utils.eos
db = cernbox_utils.db.ShareDB()
eos = cernbox_utils.eos.EOS(eos_mgm_url)
eos.role=role # do not run commands as root...
eos_no_backend = cernbox_utils.eos.EOS()
eos_no_backend.role=role # do not run commands as root...
def print_json(obj):
if args.json:
import json
if obj is not None:
print json.dumps(obj,ensure_ascii=False) # allows unicode characters from eos output
def print_json_error(msg,statuscode):
print_json({"error" : str(msg), "statuscode" : int(statuscode)})
f = globals()['cmd_'+args.cmd.replace('-','_')]
try:
r = f(args)
print_json(r)
except CmdBadRequestError,x:
logger.error("CmdBadRequestError: %s",x.msg)
print_json_error(x.msg,400)
sys.exit(4)
except Exception,x:
import traceback
logger.critical("%s. Unhandled exception: %s"%(x,traceback.format_exc()))
print_json_error("Unhandled exception.",400)
raise
def cmd_update_share(args):
parse_swan_project(args.project)
return _cmd_swan_update_share(args.project, args.owner, args.sharees)
def cmd_delete_share(args):
parse_swan_project(args.project)
return _cmd_swan_update_share(args.project, args.owner, [])
def _cmd_swan_update_share(swan_project, owner, sharees):
import cernbox_utils.sharing
for sharee in sharees:
try:
cernbox_utils.sharing.check_can_share(owner,sharee)
except ValueError,x:
logger.error(x)
raise CmdBadRequestError(str(x))
f = get_swan_project_fileinfo(eos,owner, swan_project)
if not f:
raise CmdBadRequestError("Project not found %s %s"%(owner,swan_project))
# BAD REQUEST
shares=db.get_share(owner=owner,fid=f.ino)
sharees_requested = [ cernbox_utils.sharing.split_sharee(x)[1] for x in sharees ]
# PENDING: this should be a DB TRANSACTION!
db_update_cnt = 0
for s in shares:
if s.share_with not in sharees_requested:
db.delete_share(s.id)
db_update_cnt += 1
sharees_existing = [ s.share_with for s in shares ]
for sharee in sharees:
if cernbox_utils.sharing.split_sharee(sharee)[1] not in sharees_existing:
cernbox_utils.sharing.add_share(owner,f.file,sharee,"r",eos,db,config,storage_acl_update=False)
db_update_cnt += 1
try:
logger.info("Updated %d share entries",db_update_cnt)
if db_update_cnt:
# modify storage ACL
cernbox_utils.sharing.update_acls(f.ino,eos,db,owner,dryrun=False)
except Exception,x:
logger.critical("Something went pretty wrong... %s %s",hash(x),x)
#rollback the insert?
raise
def cmd_list_shared_with(args):
return _cmd_list_shared(args,"sharee",None)
def cmd_list_shared_by(args):
if args.project:
parse_swan_project(args.project)
f = get_swan_project_fileinfo(eos,args.user,args.project)
if not f:
raise CmdBadRequestError("Source project not found")
inode=f.ino
else:
inode = None
return _cmd_list_shared(args,"owner",inode)
def _cmd_list_shared(args,role,inode):
import cernbox_utils.sharing
groups = []
# TODO: resolve groups via cboxgroupd
# curl -i localhost:2002/api/v1/membership/usergroups/moscicki -H "Authorization: Bearer abc"
retbuf = cernbox_utils.sharing.list_shares(args.user,role,groups,inode,"regular",False,False,db,eos)
retobj = []
for x in retbuf:
swanprj = path2swanprj(x['path'])
if swanprj:
x['project']=swanprj
retobj.append(x)
return {'shares':retobj}
def cmd_clone_share(args):
fsrc = get_swan_project_fileinfo(eos,args.sharer,args.shared_project)
if not fsrc:
raise CmdBadRequestError("Source project not found")
s = db.get_share(fid=fsrc.ino,sharee=args.receiver,owner=args.sharer,share_type="regular")
if not s:
raise CmdBadRequestError("Project not shared")
# we copy using the role of the receiver
logger.debug("getting gid of receiver")
p = pwd.getpwnam(args.receiver) # will end up with KeyError if user not resolved
receiver_role = (p.pw_uid,p.pw_gid)
logger.debug("got gid %s"%str(receiver_role))
# so far so good, share exists and target also exists...
receiver_backend = get_eos_server(args.receiver)
eos_receiver = cernbox_utils.eos.EOS(receiver_backend)
eos_receiver.role=receiver_role
fdest = get_swan_project_fileinfo(eos_receiver, args.receiver,args.cloned_project)
# destination exists
if fdest:
raise CmdBadRequestError("Destination project exists")
# eos cp -r will preserve the directory name at the destination
# copy to a temporary area and then rename into new destination
import uuid, tempfile, shutil
tmp_base = config['cp_tmp_dir'] if 'cp_tmp_dir' in config else None
local_tmp = tempfile.mkdtemp(dir=tmp_base)
tmppath = swanprj2path(args.receiver,"SWAN_projects/.sys.dav.hide#."+str(uuid.uuid1()))
try:
r = eos._runcmd(eos_receiver._eoscmd("mkdir","-p",tmppath))
# to copy between eos instances, copy locally and then to the target
# !! the host needs to be allowed in eos in order for this to work !!
r = eos._runcmd(eos_no_backend._eoscmd("cp","-r",os.path.normpath(fsrc.file)+'/',local_tmp+'/'))
eos_no_backend.role=receiver_role # do not run these commands as root nor the owner...
r = eos_receiver._runcmd(eos_no_backend._eoscmd("cp","-r",os.path.join(local_tmp,os.path.basename(fsrc.file))+'/',tmppath+'/'))
r = eos._runcmd(eos_receiver._eoscmd('file','rename',os.path.join(tmppath,os.path.basename(fsrc.file)),swanprj2path(args.receiver,args.cloned_project)))
r = eos._runcmd(eos_receiver._eoscmd('rmdir',tmppath))
finally:
shutil.rmtree(local_tmp)
# helpers
def get_swan_project_fileinfo(self_eos,owner,swan_project):
""" Return EOS file object for a SWAN project or None if not found.
Raise ValueError() if project specified in a wrong way or not sharable.
"""
p = swanprj2path(owner,swan_project)
if not p:
raise CmdBadRequestError("Project name misformatted: %s"%swan_project)
# TODO: FIX RC => 400 Bad Request
import cernbox_utils.sharing as sharing
return sharing.check_share_target(p,owner,self_eos,config)
def parse_swan_project(swanprj):
if not is_swanprj(swanprj):
raise CmdBadRequestError("ERROR: SWAN project name wrongly specified '%s'"%swanprj)
# convert SWAN project name to EOS path and vice-versa
def is_swanprj(pname):
p = os.path.split(os.path.normpath(pname))
return len(p)==2 and p[0]=='SWAN_projects'
def swanprj2path(owner,pname):
if not is_swanprj(pname):
return None
else:
return os.path.join(config['eos_prefix'],owner[0],owner,pname)
def path2swanprj(path):
if not path.startswith(config['eos_prefix']):
return None
p = os.path.normpath(path[len(config['eos_prefix']):])
p = "/".join(p.split('/')[2:])
if is_swanprj(p):
return p
else:
return None
def unit_test_swanprj():
print "--- UNIT TEST SWANPRJ: BEGIN"
assert(os.path.normpath(config['eos_prefix']) == '/eos/scratch/user')
print is_swanprj("SWAN_projects/X") # => True
print is_swanprj("SWAN_projects/X/Y") # => False
print is_swanprj("SWAN_projects/") # => False
print is_swanprj("SWAN_projects") # => False
print is_swanprj("X") # => False
print is_swanprj("") # => False
print swanprj2path('moscicki',"X") # => None
print swanprj2path('moscicki',"SWAN_projects/X") # => /eos/scratch/user/m/moscicki/SWAN_projects/X
print path2swanprj("/eos/scratch/user/m/moscicki/SWAN_projects/X") # => SWAN_projects/X
print "--- UNIT TEST SWANPRJ: END"
if __name__ == "__main__":
sys.exit(main())