import repository from arizona
[raven.git] / lib / arizona-lib / transfer / arizonatransfer_ftp.py
1 #! /usr/bin/env python
2 """
3 Stork Project (http://www.cs.arizona.edu/stork/)
4 Module: arizonatransfer_ftp
5 Description:   Provides a general file transferring by FTP
6
7 """
8
9
10 import urllib
11 import urllib2
12 import arizonaconfig
13 import arizonareport
14 import os
15 import time
16 import socket
17 import getpass
18
19 """arizonaconfig
20    options=[["", "--transuser", "transuser", "store", "string", None, "user", "use this user to transfer files (the default is up to the transfer program)"]]
21    includes=[]
22 """
23
24
25 slicename = getpass.getuser()
26
27 # TODO: need to use arizona_config
28 username="anonymous"
29 password=slicename+"_AT_"+socket.gethostname()
30 port=21
31 #arizonaconfig.set_option("username", "anonymous")
32 #arizonaconfig.set_option("password", arizonaconfig.get_option("xyz") + "_AT_" + socket.gethostname())
33
34 def log_transfer(function, pid, timestamp, timestampend):
35    try:
36       import storklog
37       storklog.log_transfer(function, pid, timestamp, timestampend)
38    except:
39       pass
40
41 def close_transfer_program():
42    """
43    <Purpose>
44       This closes a connection.
45
46    <Arguments>
47       None.
48    
49    <Exceptions>
50       None.
51
52    <Side Effects>
53       Set username, password, and port as default.
54
55    <Returns>
56       True
57    """
58
59    # TODO: need to use arizona_config instead
60    username="anonymous"
61    password=slicename+"_AT_"+socket.gethostname()
62    port=21
63    
64    return True
65
66
67
68
69
70 # TODO: since basically everything is set by arizona_config,
71 #        there's actually nothing to do here...
72 def init_transfer_program(uname=None,passwd=None,prt=None,ignore4=None):
73    """
74    <Purpose>
75       This sets username, password, and port for a ftp connection.
76
77    <Arguments>
78       uname:
79          User name. It should be a string, otherwise it raises TypeError.
80          default: 'anonymous'
81
82       passwd:
83          password. It should be a string, otherwise it raises TypeError.
84          default: slicename_AT_hostname
85
86       prt:
87          port number. It should be an int, otherwise it raises TypeError.
88          default: 21
89
90       ignore4:
91          It is just used to be matched with return value of other transfer 
92          methods. Always None for 'storkftp'
93
94    <Exceptions>
95       TypeError:
96          If the types of given arguments are incorrect, then TypeError 
97          will be raised.
98
99    <Side Effects>
100       Set username, password, and port.
101
102    <Returns>
103       (True, username, password, port, ignore)
104    """
105
106    arizonareport.send_out(4, "[DEBUG] arizonatransfer_ftp.init_transfer_program: started")
107
108    arizonaconfig.set_option("transuser", "anonymous")
109
110    # if username is given, but incorrect type
111    if not uname == None and not isinstance(uname, str):
112       arizonareport.send_syslog(arizonareport.ERR, "username should be a string")
113       raise TypeError, "init_transfer_program(): Invalid type of username(should be a string)"
114
115    # if passwd is given, but incorrect type
116    if not passwd == None and not isinstance(passwd, str):
117       arizonareport.send_syslog(arizonareport.ERR, "passwd should be a string")
118       raise TypeError, "init_transfer_program(): Invalid type of passwd(should be a string)"
119
120    # if port is given, but incorrect type
121    if not prt == None and not isinstance(prt, int):
122       arizonareport.send_syslog(arizonareport.ERR, "port should be an integer")
123       raise TypeError, "init_transfer_program(): Invalid type of prt(should be an int)"
124
125
126    global username
127    global password
128    global port
129    
130    # default settings
131    username = arizonaconfig.get_option("transuser")
132    password=slicename+"_AT_"+socket.gethostname()
133    port=21
134
135    # set username with given username
136    if uname!=None:
137       username=uname
138    # set password with given password
139    if passwd!=None:
140       # if passwd has @, then replace @ to _AT_
141       if "@" in passwd:
142          arizonareport.send_syslog(arizonareport.ERR, "init_transfer_program(): Warning, replacing @ in password with _AT_")
143          password=passwd.replace("@","_AT_")
144       else:
145          password=passwd
146    # set port with given port
147    if prt!=None:
148       port=prt
149
150    # return result
151    return (username, password, port, ignore4)
152
153
154
155
156
157 def retrieve_files(host, filelist, destdir='.', indicator=None):
158    """
159    <Purpose>
160       This retrieves files from a host to a destdir.
161
162    <Arguments>
163       host:
164          'host' holds two things, a server name and target directory.
165          For example, if you want to retrieve files from '/tmp/' directory
166          in 'quadrus.cs.arizona.edu' server, the 'host' will be
167          'quadrus.cs.arizona.edu/tmp'.         
168
169       filelist:
170          'filelist' is a list of files which need to be retrieved.
171
172       junk_hashlist:
173          'junk_hashlist' is a list of the hashes for this list of files.
174          It should be a list of strings.
175
176       destdir:
177          'destdir' is a destination directory where retrieved files will 
178          be placed. A user should have 'destdir' exist before retrieving 
179          files. 'destdir' should be a string. Default is a current dir. 
180
181       indicator:
182          'indicator' is a module which has set_filename and 
183          download_indicator functions. 'indicator' will be passed in 
184          'urlretrieve' function so that progress bar will be shown 
185          while downloading files. Default is 'None'.
186
187    <Exceptions>
188       AttributeError:
189          If 'indicator' doesn't have functions such as set_filename, 
190          download_indicator, or 'indicator' is not a module, then 'False'
191          and an empty list will be returend.
192       
193       TypeError:
194          If 'indicator_file' is not a string, then 'False' and an empty 
195          list will be returend.
196
197    <Side Effects>
198       None.
199
200    <Returns>
201       (True, grabbed_list)
202       'grabbed_list' is a list of files which are retrieved
203    """
204       
205    arizonareport.send_out(4, "[DEBUG] arizonatransfer_ftp.retrieve_files: started")
206
207    # set grabbed_list as a empty list. Later it will be appended with retrieved files
208    grabbed_list = []
209
210    # check if host is a string   
211    if not isinstance(host, str):
212       arizonareport.send_syslog(arizonareport.ERR, "retrieve_files(): host should be a string")
213       # return false and empty list
214       return (False, grabbed_list)
215    
216    # check if filelist contains only strings
217    # Later I should just use something like arizonageneral.valid_sl   TODO!!!
218    # TODO - check if valid list of tuples
219    #if not valid_sl(filelist):
220    #   arizonareport.send_syslog(arizonareport.ERR, "retrieve_files(): filelist should be a list of strings")
221    #   # return false and empty list
222    #   return (False, grabbed_list)
223    
224    # check if destdir is a string
225    if not isinstance(destdir,str):
226       arizonareport.send_syslog(arizonareport.ERR, "retrieve_files(): destdir should be a string")
227       # return false and empty list
228       return (False, grabbed_list)
229    
230    # if destdir is a empty string, then make it as a current directory
231    if destdir == '':
232       destdir = '.'
233
234    # check that the destination directory exists  
235    if not os.path.isdir(destdir):
236       arizonareport.send_syslog(arizonareport.ERR, "\nretrieve_files(): The destination directory '" + destdir + "' for a requested does not exist")
237       # return false and empty list
238       return (False, grabbed_list)
239
240
241    # check host is valid
242    arizonareport.send_out(4, "[DEBUG] arizonatransfer_ftp.retrieve_files: verifying connection")
243    if not __verify_connection(host):
244       # return false and empty list
245       return (False, grabbed_list)
246       
247    # go through every file in the file list
248    arizonareport.send_out(4, "[DEBUG] arizonatransfer_ftp.retrieve_files: filelist = " + str(filelist))
249    for file in filelist:
250       filename = file['filename']
251       starttime = time.time()
252
253       arizonareport.send_out(4, "[DEBUG] arizonatransfer_ftp.retrieve_files: filename = " + str(filename))
254
255
256       # build url which specifies host and filename to be retrieved
257       thisurl = __build_url(host,filename)
258          
259       # open given url
260       arizonareport.send_out(4, "[DEBUG] arizonatransfer.ftp: thisurl = " + str(thisurl)) 
261       aurl = urllib.urlopen(thisurl)         
262       # check the info of opened url if it have 'Content-Length' 
263       # if it doesn't have, it means either a user has no permission to read the file, 
264       # or it's a directory, not a file. Either way, we won't retrieve it
265       if (str(aurl.info()).split().count('Content-Length:')) :   
266          aurl.close()         
267
268          # if idicator is passed in
269          if (indicator):
270             # make indicator_file store a file name which will be used in download_indicator function
271             indicator_file = filename
272             # make indicator_file hold only filename itself (without directory)
273             i = indicator_file.rfind("/")      
274             if i != -1:
275                indicator_file = indicator_file[i + 1:]
276                
277             try:
278                # set the filename so that indicator module can use the name to show for progress bar
279                indicator.set_filename(indicator_file)
280                arizonareport.send_out(0, "")
281                # download_indicator method of indicator module is passed
282                (filename,info) = urllib.urlretrieve(thisurl,destdir+"/"+filename, indicator.download_indicator)
283                arizonareport.send_out(0, "")
284             # indicator doesn't have method set_filename or download_indicator               
285             except AttributeError:
286                arizonareport.send_syslog(arizonareport.ERR, 'retrieve_files(): indicator module passed in is incorrect')
287                return (False, grabbed_list)
288             # if indicator_file which used for set_filename is not a string
289             except TypeError:
290                arizonareport.send_syslog(arizonareport.ERR, 'retrieve_files(): indicator_file is incorrect')
291                return (False, grabbed_list)
292          else:
293             # retrieve a file   
294             (filename,info) = urllib.urlretrieve(thisurl,destdir+"/"+filename)
295
296          grabbed_list = grabbed_list + [file]
297          endtime = time.time()
298          log_transfer("ftp", str(os.getpid()), str(starttime), str(endtime))
299       else:
300          aurl.close()
301          arizonareport.send_syslog(arizonareport.ERR, 'retrieve_files(): Cannot retrieve "' + filename + '"')
302   
303    if (grabbed_list) :
304       return (True, grabbed_list)
305    # if nothing in grabbed_list
306    else:
307       return (False, grabbed_list)
308
309
310
311
312
313 def transfer_name():
314    """
315    <Purpose>
316       This gives the name of this transfer method.
317
318    <Arguments>
319       None.
320
321    <Exceptions>
322       None.
323
324    <Side Effects>
325       None.
326
327    <Returns>
328       'arizona_ftp' as an string
329    """
330
331    return 'arizona_ftp'
332
333
334
335
336
337 def __build_url(host,fname):
338    """
339    <Purpose>
340       This builds a url string with host, username, password, port and
341       file name.
342
343    <Arguments>
344        host:
345          'host' holds two things, a server name and target directory.
346          For example, if you want to retrieve files from '/tmp/' directory
347          in 'quadrus.cs.arizona.edu' server, the 'host' will be
348          'quadrus.cs.arizona.edu/tmp'.
349       fname:
350          A file name to be retrieved
351    
352    <Exceptions>
353       None.
354
355    <Side Effects>
356       None.
357
358    <Returns>
359       A whole url string created
360    """
361
362    # if host doesn't contain 'ftp://' or contains 'http://'
363    # add 'ftp://' at the beginning or replace to 'ftp://'
364    if not "ftp://" in host:
365       host = host.replace("http://", "")      
366       host = "ftp://" + host
367    
368    # return url which contain all info
369    return host.replace("//","##"+username+":"+password+"@",1).replace("/",":"+str(port)+"/",1).replace("##","//",1)+"/"+fname
370
371
372
373
374 def __verify_connection(host):
375    """
376    <Purpose>
377       This verifies a connection, testing a host, username, password, port
378
379    <Arguments>
380       host:
381          'host' holds two things, a server name and target directory.
382          For example, if you want to retrieve files from '/tmp/' directory
383          in 'quadrus.cs.arizona.edu' server, the 'host' will be
384          'quadrus.cs.arizona.edu/tmp'.
385             
386    <Exceptions>
387       URLError:
388          If host name is incorrect or host is dead, then return False
389
390       IOError:
391          If given username, password, port, or target directory is 
392          incorrect, then return False
393
394    <Side Effects>
395       None.
396
397    <Returns>
398       True or False (see above)
399    """
400    
401    
402    # split host into server name and directory
403    index = host.find('/')
404    # set hostname to hold only a server name
405    if index != -1:
406       hostname = host[:index]
407    else :
408       hostname = host   
409
410    # checking only host
411    checkurl = __build_url(hostname, "")
412
413    # urllib2 is used since urllib doestn't offer a nice way to check the connection is valid
414    try :
415       urllib2.urlopen(checkurl)
416    # incorrect host name or host is dead
417    except urllib2.URLError, (msg):      
418       arizonareport.send_syslog(arizonareport.ERR, '__verify_connection(): "' + hostname + '" '+ str(msg).split("'")[1])
419       return False
420    # incorrect username, or password
421    except IOError, (errno, strerror):     
422       arizonareport.send_syslog(arizonareport.ERR, '__verify_connection(): ' + str(strerror))
423       close_transfer_program()
424       return False
425
426    # checking if either a directory exist in the server, or a port is correct
427    checkurl = __build_url(host, "")
428    try:
429       urllib2.urlopen(checkurl)
430    # if either a directory doesn't exist in the server, or a port is incorrect
431    except IOError, (errno, strerror):
432       arizonareport.send_syslog(arizonareport.ERR, '__verify_connection(): ' + str(strerror))
433       return False
434    # everything is fine       
435    return True
436    
437
438
439
440
441 # TODO: should go away!!!
442 def valid_sl(stringlist):
443    """
444    <Purpose>
445       This returns True if stringlist is a list of strings or False if it is 
446       not.
447
448    <Arguments>
449       stringlist:
450           The variable to be checked.
451
452    <Exceptions>
453       None
454
455    <Side Effects>
456       None
457
458    <Returns>
459       True or False (see above)
460    """
461
462    # If it's a list
463    if isinstance(stringlist,list):
464
465       for item in stringlist:
466          # If an item in the list isn't a string then False
467          if not isinstance(item,str):
468             return False
469       else:
470          # It's a list of strings so True
471          return True
472    else:
473       # Not a list so false
474       return False
475
476
477