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