more pep8-friendly
[sfa.git] / sfa / client / manifolduploader.py
1 #!/usr/bin/env python
2 #
3 # inspired from tophat/bin/uploadcredential.py
4 #
5 # the purpose here is to let people upload their delegated credentials
6 # to a manifold/myslice infrastructure, without the need for having to
7 # install a separate tool; so duplicating this code is suboptimal in
8 # terms of code sharing but acceptable for hopefully easier use
9 #
10 # As of Nov. 2013, the signature for the forward API call has changed
11 # and now requires authentication to be passed as an annotation
12 # We take this chance to make things much simpler here by dropping
13 # support for multiple API versions/flavours
14 #
15 # As of April 2013, manifold is moving from old-fashioned API known as
16 # v1, that offers an AddCredential API call, towards a new API v2 that
17 # manages credentials with the same set of Get/Update calls as other
18 # objects
19 #
20
21 # mostly this is intended to be used through 'sfi myslice'
22 # so the defaults below are of no real importance
23 # this for now points at demo.myslice.info, but sounds like a
24 # better default for the long run
25 DEFAULT_URL = "http://myslice.onelab.eu:7080"
26 DEFAULT_PLATFORM = 'ple'
27
28 # starting with 2.7.9 we need to turn off server verification
29 import ssl
30 try:
31     turn_off_server_verify = {'context': ssl._create_unverified_context()}
32 except:
33     turn_off_server_verify = {}
34
35 import getpass
36
37 from sfa.util.py23 import xmlrpc_client
38
39
40 class ManifoldUploader:
41     """A utility class for uploading delegated credentials to a manifold/MySlice infrastructure"""
42
43     # platform is a name internal to the manifold deployment,
44     # that maps to a testbed, like e.g. 'ple'
45     def __init__(self, logger, url=None, platform=None, username=None, password=None, ):
46         self._url = url
47         self._platform = platform
48         self._username = username
49         self._password = password
50         self.logger = logger
51         self._proxy = None
52
53     def username(self):
54         if not self._username:
55             self._username = raw_input("Enter your manifold username: ")
56         return self._username
57
58     def password(self):
59         if not self._password:
60             username = self.username()
61             self._password = getpass.getpass(
62                 "Enter password for manifold user %s: " % username)
63         return self._password
64
65     def platform(self):
66         if not self._platform:
67             self._platform = raw_input(
68                 "Enter your manifold platform [%s]: " % DEFAULT_PLATFORM)
69             if self._platform.strip() == "":
70                 self._platform = DEFAULT_PLATFORM
71         return self._platform
72
73     def url(self):
74         if not self._url:
75             self._url = raw_input(
76                 "Enter the URL for your manifold API [%s]: " % DEFAULT_URL)
77             if self._url.strip() == "":
78                 self._url = DEFAULT_URL
79         return self._url
80
81     def prompt_all(self):
82         self.username()
83         self.password()
84         self.platform()
85         self.url()
86
87     # looks like the current implementation of manifold server
88     # won't be happy with several calls issued in the same session
89     # so we do not cache this one
90     def proxy(self):
91         #        if not self._proxy:
92         #            url=self.url()
93         #            self.logger.info("Connecting manifold url %s"%url)
94         #            self._proxy = xmlrpc_client.ServerProxy(url, allow_none = True)
95         #        return self._proxy
96         url = self.url()
97         self.logger.debug("Connecting manifold url %s" % url)
98         proxy = xmlrpc_client.ServerProxy(url, allow_none=True,
99                                           **turn_off_server_verify)
100
101         return proxy
102
103     # does the job for one credential
104     # expects the credential (string) and an optional message (e.g. hrn) for reporting
105     # return True upon success and False otherwise
106     def upload(self, delegated_credential, message=None):
107         platform = self.platform()
108         username = self.username()
109         password = self.password()
110         auth = {'AuthMethod': 'password',
111                 'Username': username, 'AuthString': password}
112         if not message:
113             message = ""
114
115         try:
116             manifold = self.proxy()
117             # the code for a V2 interface
118             query = {'action':     'update',
119                      'object':     'local:account',
120                      'filters':    [['platform', '=', platform]],
121                      'params':     {'credential': delegated_credential, },
122                      }
123             annotation = {'authentication': auth, }
124             # in principle the xmlrpc call should not raise an exception
125             # but fill in error code and messages instead
126             # however this is only theoretical so let's be on the safe side
127             try:
128                 self.logger.debug(
129                     "Using new v2 method forward+annotation@%s %s" % (platform, message))
130                 retcod2 = manifold.forward(query, annotation)
131             except Exception as e:
132                 # xxx we need a constant constant for UNKNOWN, how about using
133                 # 1
134                 MANIFOLD_UNKNOWN = 1
135                 retcod2 = {'code': MANIFOLD_UNKNOWN, 'description': "%s" % e}
136             if retcod2['code'] == 0:
137                 info = ""
138                 if message:
139                     info += message + " "
140                 info += 'v2 upload OK'
141                 self.logger.info(info)
142                 return True
143             # everything has failed, let's report
144             self.logger.error("Could not upload %s" %
145                               (message if message else "credential"))
146             self.logger.info("  V2 Update returned code %s and error >>%s<<" % (
147                 retcod2['code'], retcod2['description']))
148             self.logger.debug("****** full retcod2")
149             for k, v in retcod2.items():
150                 self.logger.debug("**** %s: %s" % (k, v))
151             return False
152         except Exception as e:
153             if message:
154                 self.logger.error("Could not upload %s %s" % (message, e))
155             else:
156                 self.logger.error("Could not upload credential %s" % e)
157             if self.logger.debugEnabled():
158                 import traceback
159                 traceback.print_exc()
160             return False
161
162 # this is mainly for unit testing this class but can come in handy as well
163
164
165 def main():
166     from argparse import ArgumentParser
167     parser = ArgumentParser(description="manifoldupoader simple tester.")
168     parser.add_argument('credential_files', metavar='FILE', type=str, nargs='+',
169                         help="the filenames to upload")
170     parser.add_argument('-u', '--url', dest='url', action='store', default=None,
171                         help='the URL of the manifold API')
172     parser.add_argument('-p', '--platform', dest='platform',
173                         action='store', default=None,
174                         help='the manifold platform name')
175     parser.add_argument('-U', '--user', dest='username',
176                         action='store', default=None,
177                         help='the manifold username')
178     parser.add_argument('-P', '--password', dest='password',
179                         action='store', default=None,
180                         help='the manifold password')
181     parser.add_argument('-v', '--verbose', dest='verbose',
182                         action='count', default=0,
183                         help='more and more verbose')
184     args = parser.parse_args()
185
186     from sfa.util.sfalogging import sfi_logger
187     sfi_logger.enable_console()
188     sfi_logger.setLevelFromOptVerbose(args.verbose)
189     uploader = ManifoldUploader(url=args.url, platform=args.platform,
190                                 username=args.username, password=args.password,
191                                 logger=sfi_logger)
192
193     for filename in args.credential_files:
194         with open(filename) as f:
195             result = uploader.upload(f.read(), filename)
196             sfi_logger.info('... result=%s' % result)
197
198 if __name__ == '__main__':
199     main()