88df708f17a6836ab5fde4f7fee0482d3cfcc881
[sfa.git] / sfa / managers / aggregate_manager_max.py
1 import os
2 import time
3 import re
4
5 #from sfa.util.faults import *
6 from sfa.util.sfalogging import logger
7 from sfa.util.sfatime import SFATIME_FORMAT
8 from sfa.util.config import Config
9 from sfa.util.callids import Callids
10 from sfa.util.version import version_core
11 from sfa.util.xrn import urn_to_hrn, hrn_to_urn, Xrn
12
13 # xxx the sfa.rspecs module is dead - this symbol is now undefined
14 #from sfa.rspecs.sfa_rspec import sfa_rspec_version
15
16 from sfa.managers.aggregate_manager import AggregateManager
17
18 from sfa.planetlab.plslices import PlSlices
19
20 class AggregateManagerMax (AggregateManager):
21
22     def __init__ (self, config):
23         pass
24
25     RSPEC_TMP_FILE_PREFIX = "/tmp/max_rspec"
26     
27     # execute shell command and return both exit code and text output
28     def shell_execute(self, cmd, timeout):
29         pipe = os.popen('{ ' + cmd + '; } 2>&1', 'r')
30         pipe = os.popen(cmd + ' 2>&1', 'r')
31         text = ''
32         while timeout:
33             line = pipe.read()
34             text += line
35             time.sleep(1)
36             timeout = timeout-1
37         code = pipe.close()
38         if code is None: code = 0
39         if text[-1:] == '\n': text = text[:-1]
40         return code, text
41     
42    
43     def call_am_apiclient(self, client_app, params, timeout):
44         """
45         call AM API client with command like in the following example:
46         cd aggregate_client; java -classpath AggregateWS-client-api.jar:lib/* \
47           net.geni.aggregate.client.examples.CreateSliceNetworkClient \
48           ./repo https://geni:8443/axis2/services/AggregateGENI \
49           ... params ...
50         """
51         (client_path, am_url) = Config().get_max_aggrMgr_info()
52         sys_cmd = "cd " + client_path + "; java -classpath AggregateWS-client-api.jar:lib/* net.geni.aggregate.client.examples." + client_app + " ./repo " + am_url + " " + ' '.join(params)
53         ret = self.shell_execute(sys_cmd, timeout)
54         logger.debug("shell_execute cmd: %s returns %s" % (sys_cmd, ret))
55         return ret
56     
57     # save request RSpec xml content to a tmp file
58     def save_rspec_to_file(self, rspec):
59         path = AggregateManagerMax.RSPEC_TMP_FILE_PREFIX + "_" + \
60             time.strftime(SFATIME_FORMAT, time.gmtime(time.time())) +".xml"
61         file = open(path, "w")
62         file.write(rspec)
63         file.close()
64         return path
65     
66     # get stripped down slice id/name plc.maxpl.xislice1 --> maxpl_xislice1
67     def get_plc_slice_id(self, cred, xrn):
68         (hrn, type) = urn_to_hrn(xrn)
69         slice_id = hrn.find(':')
70         sep = '.'
71         if hrn.find(':') != -1:
72             sep=':'
73         elif hrn.find('+') != -1:
74             sep='+'
75         else:
76             sep='.'
77         slice_id = hrn.split(sep)[-2] + '_' + hrn.split(sep)[-1]
78         return slice_id
79     
80     # extract xml 
81     def get_xml_by_tag(self, text, tag):
82         indx1 = text.find('<'+tag)
83         indx2 = text.find('/'+tag+'>')
84         xml = None
85         if indx1!=-1 and indx2>indx1:
86             xml = text[indx1:indx2+len(tag)+2]
87         return xml
88
89     # formerly in aggregate_manager.py but got unused in there...    
90     def _get_registry_objects(self, slice_xrn, creds, users):
91         """
92     
93         """
94         hrn, _ = urn_to_hrn(slice_xrn)
95     
96         #hrn_auth = get_authority(hrn)
97     
98         # Build up objects that an SFA registry would return if SFA
99         # could contact the slice's registry directly
100         reg_objects = None
101     
102         if users:
103             # dont allow special characters in the site login base
104             #only_alphanumeric = re.compile('[^a-zA-Z0-9]+')
105             #login_base = only_alphanumeric.sub('', hrn_auth[:20]).lower()
106             slicename = hrn_to_pl_slicename(hrn)
107             login_base = slicename.split('_')[0]
108             reg_objects = {}
109             site = {}
110             site['site_id'] = 0
111             site['name'] = 'geni.%s' % login_base 
112             site['enabled'] = True
113             site['max_slices'] = 100
114     
115             # Note:
116             # Is it okay if this login base is the same as one already at this myplc site?
117             # Do we need uniqueness?  Should use hrn_auth instead of just the leaf perhaps?
118             site['login_base'] = login_base
119             site['abbreviated_name'] = login_base
120             site['max_slivers'] = 1000
121             reg_objects['site'] = site
122     
123             slice = {}
124             
125             # get_expiration always returns a normalized datetime - no need to utcparse
126             extime = Credential(string=creds[0]).get_expiration()
127             # If the expiration time is > 60 days from now, set the expiration time to 60 days from now
128             if extime > datetime.datetime.utcnow() + datetime.timedelta(days=60):
129                 extime = datetime.datetime.utcnow() + datetime.timedelta(days=60)
130             slice['expires'] = int(time.mktime(extime.timetuple()))
131             slice['hrn'] = hrn
132             slice['name'] = hrn_to_pl_slicename(hrn)
133             slice['url'] = hrn
134             slice['description'] = hrn
135             slice['pointer'] = 0
136             reg_objects['slice_record'] = slice
137     
138             reg_objects['users'] = {}
139             for user in users:
140                 user['key_ids'] = []
141                 hrn, _ = urn_to_hrn(user['urn'])
142                 user['email'] = hrn_to_pl_slicename(hrn) + "@geni.net"
143                 user['first_name'] = hrn
144                 user['last_name'] = hrn
145                 reg_objects['users'][user['email']] = user
146     
147             return reg_objects
148     
149     def prepare_slice(self, api, slice_xrn, creds, users):
150         reg_objects = self._get_registry_objects(slice_xrn, creds, users)
151         (hrn, type) = urn_to_hrn(slice_xrn)
152         slices = PlSlices(self.driver)
153         peer = slices.get_peer(hrn)
154         sfa_peer = slices.get_sfa_peer(hrn)
155         slice_record=None
156         if users:
157             slice_record = users[0].get('slice_record', {})
158         registry = api.registries[api.hrn]
159         credential = api.getCredential()
160         # ensure site record exists
161         site = slices.verify_site(hrn, slice_record, peer, sfa_peer)
162         # ensure slice record exists
163         slice = slices.verify_slice(hrn, slice_record, peer, sfa_peer)
164         # ensure person records exists
165         persons = slices.verify_persons(hrn, slice, users, peer, sfa_peer)
166     
167     def parse_resources(self, text, slice_xrn):
168         resources = []
169         urn = hrn_to_urn(slice_xrn, 'sliver')
170         plc_slice = re.search("Slice Status => ([^\n]+)", text)
171         if plc_slice.group(1) != 'NONE':
172             res = {}
173             res['geni_urn'] = urn + '_plc_slice'
174             res['geni_error'] = ''
175             res['geni_status'] = 'unknown'
176             if plc_slice.group(1) == 'CREATED':
177                 res['geni_status'] = 'ready'
178             resources.append(res)
179         vlans = re.findall("GRI => ([^\n]+)\n\t  Status => ([^\n]+)", text)
180         for vlan in vlans:
181             res = {}
182             res['geni_error'] = ''
183             res['geni_urn'] = urn + '_vlan_' + vlan[0]
184             if vlan[1] == 'ACTIVE':
185                 res['geni_status'] = 'ready'
186             elif vlan[1] == 'FAILED':
187                 res['geni_status'] = 'failed'
188             else:
189                 res['geni_status'] = 'configuring'
190             resources.append(res)
191         return resources
192     
193     def slice_status(self, api, slice_xrn, creds):
194         urn = hrn_to_urn(slice_xrn, 'slice')
195         result = {}
196         top_level_status = 'unknown'
197         slice_id = self.get_plc_slice_id(creds, urn)
198         (ret, output) = self.call_am_apiclient("QuerySliceNetworkClient", [slice_id,], 5)
199         # parse output into rspec XML
200         if output.find("Unkown Rspec:") > 0:
201             top_level_staus = 'failed'
202             result['geni_resources'] = ''
203         else:
204             has_failure = 0
205             all_active = 0
206             if output.find("Status => FAILED") > 0:
207                 top_level_staus = 'failed'
208             elif (    output.find("Status => ACCEPTED") > 0 or output.find("Status => PENDING") > 0
209                    or output.find("Status => INSETUP") > 0 or output.find("Status => INCREATE") > 0
210                  ):
211                 top_level_status = 'configuring'
212             else:
213                 top_level_status = 'ready'
214             result['geni_resources'] = self.parse_resources(output, slice_xrn)
215         result['geni_urn'] = urn
216         result['geni_status'] = top_level_status
217         return result
218     
219     def create_slice(self, api, xrn, cred, rspec, users):
220         indx1 = rspec.find("<RSpec")
221         indx2 = rspec.find("</RSpec>")
222         if indx1 > -1 and indx2 > indx1:
223             rspec = rspec[indx1+len("<RSpec type=\"SFA\">"):indx2-1]
224         rspec_path = self.save_rspec_to_file(rspec)
225         self.prepare_slice(api, xrn, cred, users)
226         slice_id = self.get_plc_slice_id(cred, xrn)
227         sys_cmd = "sed -i \"s/rspec id=\\\"[^\\\"]*/rspec id=\\\"" +slice_id+ "/g\" " + rspec_path + ";sed -i \"s/:rspec=[^:'<\\\" ]*/:rspec=" +slice_id+ "/g\" " + rspec_path
228         ret = self.shell_execute(sys_cmd, 1)
229         sys_cmd = "sed -i \"s/rspec id=\\\"[^\\\"]*/rspec id=\\\"" + rspec_path + "/g\""
230         ret = self.shell_execute(sys_cmd, 1)
231         (ret, output) = self.call_am_apiclient("CreateSliceNetworkClient", [rspec_path,], 3)
232         # parse output ?
233         rspec = "<RSpec type=\"SFA\"> Done! </RSpec>"
234         return True
235     
236     def delete_slice(self, api, xrn, cred):
237         slice_id = self.get_plc_slice_id(cred, xrn)
238         (ret, output) = self.call_am_apiclient("DeleteSliceNetworkClient", [slice_id,], 3)
239         # parse output ?
240         return 1
241     
242     
243     def get_rspec(self, api, cred, slice_urn):
244         logger.debug("#### called max-get_rspec")
245         #geni_slice_urn: urn:publicid:IDN+plc:maxpl+slice+xi_rspec_test1
246         if slice_urn == None:
247             (ret, output) = self.call_am_apiclient("GetResourceTopology", ['all', '\"\"'], 5)
248         else:
249             slice_id = self.get_plc_slice_id(cred, slice_urn)
250             (ret, output) = self.call_am_apiclient("GetResourceTopology", ['all', slice_id,], 5)
251         # parse output into rspec XML
252         if output.find("No resouce found") > 0:
253             rspec = "<RSpec type=\"SFA\"> <Fault>No resource found</Fault> </RSpec>"
254         else:
255             comp_rspec = self.get_xml_by_tag(output, 'computeResource')
256             logger.debug("#### computeResource %s" % comp_rspec)
257             topo_rspec = self.get_xml_by_tag(output, 'topology')
258             logger.debug("#### topology %s" % topo_rspec)
259             rspec = "<RSpec type=\"SFA\"> <network name=\"" + Config().get_interface_hrn() + "\">"
260             if comp_rspec != None:
261                 rspec = rspec + self.get_xml_by_tag(output, 'computeResource')
262             if topo_rspec != None:
263                 rspec = rspec + self.get_xml_by_tag(output, 'topology')
264             rspec = rspec + "</network> </RSpec>"
265         return (rspec)
266     
267     def start_slice(self, api, xrn, cred):
268         # service not supported
269         return None
270     
271     def stop_slice(self, api, xrn, cred):
272         # service not supported
273         return None
274     
275     def reset_slices(self, api, xrn):
276         # service not supported
277         return None
278     
279     ### GENI AM API Methods
280     
281     def SliverStatus(self, api, slice_xrn, creds, options):
282         call_id = options.get('call_id')
283         if Callids().already_handled(call_id): return {}
284         return self.slice_status(api, slice_xrn, creds)
285     
286     def CreateSliver(self, api, slice_xrn, creds, rspec_string, users, options):
287         call_id = options.get('call_id')
288         if Callids().already_handled(call_id): return ""
289         #TODO: create real CreateSliver response rspec
290         ret = self.create_slice(api, slice_xrn, creds, rspec_string, users)
291         if ret:
292             return self.get_rspec(api, creds, slice_xrn)
293         else:
294             return "<?xml version=\"1.0\" ?> <RSpec type=\"SFA\"> Error! </RSpec>"
295     
296     def DeleteSliver(self, api, xrn, creds, options):
297         call_id = options.get('call_id')
298         if Callids().already_handled(call_id): return ""
299         return self.delete_slice(api, xrn, creds)
300     
301     # no caching
302     def ListResources(self, api, creds, options):
303         call_id = options.get('call_id')
304         if Callids().already_handled(call_id): return ""
305         # version_string = "rspec_%s" % (rspec_version.get_version_name())
306         slice_urn = options.get('geni_slice_urn')
307         return self.get_rspec(api, creds, slice_urn)
308     
309     def fetch_context(self, slice_hrn, user_hrn, contexts):
310         """
311         Returns the request context required by sfatables. At some point, this mechanism should be changed
312         to refer to "contexts", which is the information that sfatables is requesting. But for now, we just
313         return the basic information needed in a dict.
314         """
315         base_context = {'sfa':{'user':{'hrn':user_hrn}}}
316         return base_context
317