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