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