source: fedd/federation/xmlrpc_emulab_segment.py @ 8fbef04

Last change on this file since 8fbef04 was 8fbef04, checked in by Ted Faber <faber@…>, 5 years ago

Shared NAT integrated

  • Property mode set to 100644
File size: 12.6 KB
Line 
1#!/usr/local/bin/python
2
3import logging 
4import util
5from deter import topdl
6
7from M2Crypto import SSL
8from M2Crypto.m2xmlrpclib import SSL_Transport
9from xmlrpclib import ServerProxy, dumps, loads, Fault, Error, Binary
10from M2Crypto.SSL import SSLError
11from M2Crypto.BIO import BIOError
12from socket import error as socket_error
13from socket import sslerror
14import httplib
15from federation.util import fedd_ssl_context
16from federation import service_error
17from federation.operation_status import operation_status
18
19class xmlrpc_emulab_segment:
20    class node_info:
21        def __init__(self, name, pname, lname=None, status=None, osname=None, 
22                osversion=None, image=None):
23            self.name = name
24            self.pname = pname
25            self.lname = lname
26            self.status = status
27            self.osname = osname
28            self.osversion = osversion
29            self.image = image
30        def getOS(self):
31            rv = None
32            if self.osname or self.osversion:
33                rv = topdl.OperatingSystem(name=self.osname, 
34                        version=self.osversion)
35            if self.image and rv:
36                rv.set_attribute('emulab_access:image', self.image)
37            return rv
38
39    class lan_info:
40        def __init__(self, name, cap=None, delay=None):
41            self.name = name
42            self.cap = cap
43            self.delay = delay
44        def __repr__(self):
45            return "%s %s %s" % (self.name, self.cap, self.delay)
46
47    class interface_info:
48        def __init__(self, name, node, sub, cap=None, delay=None):
49            self.name = name
50            self.node = node
51            self.sub = sub
52            self.cap = cap
53            self.delay = delay
54        def __repr__(self):
55            return "%s %s %s %s %s" % (self.name, self.node, self.sub, 
56                    self.cap, self.delay)
57
58
59    def __init__(self, boss, ops, cert):
60        self.ctxt = fedd_ssl_context(my_cert=cert)
61        self.ctxt.set_verify(SSL.verify_none, 10)
62        self.boss = boss
63        self.ops = ops
64        self.null = """
65set ns [new Simulator]
66source tb_compat.tcl
67
68set a [$ns node]
69
70$ns rtproto Session
71$ns run
72"""
73        self.log =  getattr(self, 'log', None) 
74        self.debug = getattr(self, 'debug', False)
75        self.node = { }
76        self.subs = { }
77        self.interfaces = { }
78        self.status = [ ]
79        self.node_info = xmlrpc_emulab_segment.node_info
80        self.lan_info = xmlrpc_emulab_segment.lan_info
81        self.interface_info = xmlrpc_emulab_segment.interface_info
82
83    def emulab_call(self, method, params):
84        VERSION = 0.1
85        try:
86            transport = SSL_Transport(self.ctxt)
87            port = ServerProxy(self.boss, transport=transport)
88            remote_method = getattr(port, method, None)
89            if remote_method is not None:
90                resp = remote_method(VERSION, params)
91            else:
92                raise service_error(service_error.internal, 
93                        "Bad method: %s" % method)
94        except socket_error, e:
95            raise service_error(service_error.connect, 
96                    "Cannot connect: %s" % e)
97        except BIOError, e:
98            if self.log:
99                self.log.warn("BIO error: %s" % e)
100            raise e
101        except sslerror, e:
102            if self.log:
103                self.log.warn("SSL (socket) error: %s" %  e)
104            raise e
105        except SSLError, e:
106            if self.log:
107                self.log.warn("SSL error: %s" % e)
108            raise e
109        except httplib.HTTPException, e:
110            if self.log:
111                self.log.warn("HTTP error: %s" % e)
112            raise e
113        except Fault, f:
114            raise service_error(service_error.protocol, 
115                    "Remote XMLRPC Fault: %s" % f)
116        except Error, e:
117            raise service_error(service_error.protocol, 
118                    "Remote XMLRPC Fault: %s" % e)
119
120        code = resp.get('code', -1)
121        if code ==0:
122            return (code, resp.get('value', None))
123        else:
124            return (code, resp.get('output', None))
125
126    def get_state(self, pid, eid):
127        """
128        Return the state of the experiment as reported by emulab
129        """
130
131        if self.debug:
132            state = 'swapped'
133        else:
134            params =  { 'proj': pid, 'exp': eid }
135            code, state = self.emulab_call('experiment.state', params)
136            if code != 0:
137                state = 'none'
138
139        if self.log:
140            self.log.debug("State is %s" % state)
141        return state
142
143    def make_null_experiment(self, pid, eid, tmpdir, gid=None, ns_str=None):
144        """
145        Create a null copy of the experiment so that we capture any logs there
146        if the modify fails.  Emulab software discards the logs from a failed
147        startexp.  If ns_str is given, use that to construct the null
148        experiment.
149        """
150
151        if ns_str is None:
152            ns_str = self.null
153
154        if self.debug:
155            if self.log:
156                self.log.debug("[make_null_experiment]: " + \
157                        "(debug) Creating experiment")
158            return True
159        else:
160            params = {
161                    'proj': pid,
162                    'exp': eid, 
163                    'nsfilestr': ns_str,
164                    'batch': False,
165                    'idleswap': 0,
166                    'noidleswap_reason': 'Federated experiment',
167                    'noswapin': True,
168                    'wait': True
169                    }
170            if gid is not None:
171                params['group'] = gid
172            if self.log:
173                self.log.info("[make_null_experiment]: Creating experiment")
174            code, value = self.emulab_call('experiment.startexp', params)
175
176            if self.log:
177                if code == 0: 
178                    self.log.info('[make_null_experiment]: Create succeeded')
179                else: 
180                    self.log.error('[make_null_experiment]: Create failed: %s' \
181                            % value)
182
183            return code == 0
184
185    def swap_exp(self, pid, eid, direction='out', wait=True):
186        """
187        Swap experiment in.
188        """
189        if self.debug:
190            if self.log:
191                self.log.info("[swap_exp]: (debug) Swapping %s %s" % \
192                        (eid, direction))
193            return True
194        else:
195            if self.log:
196                self.log.info("[swap_exp]: Swapping %s %s" % (eid, direction))
197            params = {
198                    'proj': pid,
199                    'exp': eid,
200                    'direction': direction,
201                    'wait': wait,
202                    }
203            code, value = self.emulab_call('experiment.swapexp', params)
204
205            if self.log:
206                if code == 0: self.log.info('[swap_exp]: Swap succeeded')
207                else: self.log.error('[swap_exp]: Swap failed: %s' % value)
208
209        return code == 0
210
211    def terminate_exp(self, pid, eid, wait=True):
212        """
213        Completely terminate experiment
214        """
215        if self.debug:
216            if self.log:
217                self.log.info("[swap_exp]: (debug) terminate %s" %  eid)
218            return True
219        else:
220            if self.log:
221                self.log.info("[swap_exp]: Terminating %s" % eid)
222            params = {
223                    'proj': pid,
224                    'exp': eid,
225                    'wait': wait,
226                    }
227            code, value = self.emulab_call('experiment.endexp', params)
228
229            if self.log:
230                if code == 0: self.log.info('[swap_exp]: Terminate succeeded')
231                else: self.log.error('[swap_exp]: Terminate failed: %s' % value)
232
233        return code == 0
234
235    def modify_exp(self, pid, eid, tcl, wait=True):
236        if self.debug:
237            self.log.info("[modify_exp]: (debug) Modifying %s" % eid)
238            return True
239        else:
240            self.log.info("[modify_exp]: Modifying %s" % eid)
241            params = {
242                    'proj': pid,
243                    'exp': eid,
244                    'nsfilestr': tcl,
245                    'wait': wait,
246                    'reboot': True,
247                    'restart_eventsys': True,
248                    }
249            code, value = self.emulab_call('experiment.modify', params)
250            if self.log:
251                if code == 0: 
252                    self.log.info('[modify_exp]: Modify succeeded')
253                else: 
254                    self.log.error('[modify_exp]: Modify failed: %s' \
255                            % value)
256            return code == 0
257
258    def get_osid_map(self):
259        oses = { }
260        code, osids = self.emulab_call('osid.getlist', {})
261        for key, val in osids.items():
262            val['imageid'] = key
263            oses[val['osid']] = val
264        return oses
265
266    def get_links(self, pid, eid):
267        params = {
268                'proj': pid,
269                'exp': eid,
270                'aspect': 'links'
271                }
272        code, links = self.emulab_call('experiment.info', params)
273        if code ==0:
274            lans = { }
275            # Links contains per-interface information about all the LANs in
276            # the emulab experiment.  First construct the LANs, then look for
277            # overrides.
278            for i in links.values():
279                if i['name'] not in lans:
280                    lans[i['name']] = { 'cap': None, 'delay': None };
281                cap = max(i['bandwidth'], i['r_bandwidth'])
282                delay = max(i['delay'], i['r_delay'])
283                if delay == 0.0: delay = None
284                if lans[i['name']]['cap'] is None or \
285                        cap > lans[i['name']]['cap']:
286                    lans[i['name']]['cap'] = cap
287
288                if (lans[i['name']]['delay'] is None and delay is not None) or \
289                        delay > lans[i['name']]['delay']:
290                    lans[i['name']]['delay'] = delay
291
292            for s, info in lans.items():
293                self.subs[s] = self.lan_info(s, info['cap'], info['delay'])
294
295            # XXX: should deal better with r_delay != delay, etc
296            for i in links.values():
297                li = self.subs[i['name']]
298                cap = max(i['bandwidth'], i['r_bandwidth'])
299                delay = max(i['delay'], i['r_delay'])
300                if delay == 0.0: delay = None
301                if cap != li.cap or delay != li.delay:
302                    node, dummy = i['member'].split(':', 1)
303                    self.interfaces[i['member']] = \
304                            self.interface_info(i['member'], node, i['name'], 
305                                    cap, delay)
306
307            if self.log:
308                self.log.info("Link mapping complete")
309            return True
310        else:
311            raise service_error(service_error.internal, 
312                    "Cannot get link mapping of segment:%s/%s" % (pid, eid))
313
314    def get_nodes(self, pid, eid):
315
316        ev_active = ('ISUP', 'ALWAYSUP' )
317        ev_starting = ('REBOOTED', 'REBOOTING','PXEBOOTING', 
318                'BOOTING', 'RELOADSETUP', 'RELOADING', 'RELOADDONE', 
319                'RELOADDONEV2', 'TBSETUP')
320        ev_terminating = ( 'SHUTDOWN' )
321
322        osidmap = self.get_osid_map()
323
324        params = {
325                'proj': pid,
326                'exp': eid,
327                'aspect': 'mapping'
328                }
329        code, nodes = self.emulab_call('experiment.info', params)
330        if code ==0:
331            for k, v in nodes.items():
332                if v.get('erole', False) and 'pnode' in v:
333                    st = v.get('status', 'up')
334                    ev = v.get('eventstatus', 'ISUP')
335                    os = v.get('osid', None)
336                    lname = "%s.%s.%s" % (k, eid, pid)
337
338                    if st == 'up':
339                        if ev in ev_active: st = 'active'
340                        elif ev in ev_starting: st = 'starting'
341                        elif ev in ev_terminating: st = 'terminating'
342                        else: st = 'failed'
343                    else: st = 'failed'
344
345                    if os and os in osidmap:
346                       osname = osidmap[os].get('OS', None)
347                       osversion = osidmap[os].get('version', None)
348                       osimage = "%s/%s" % \
349                               ( osidmap[os].get('pid', ''), 
350                                       osidmap[os].get('imageid', ''))
351                    else:
352                        osname = osversion = osimage = None
353
354                    self.node[k] = self.node_info(k, v['pnode'], lname,
355                            st, osname, osversion, osimage)
356            if self.log:
357                self.log.info("Node mapping complete")
358            return True
359        else:
360            raise service_error(service_error.internal,
361                    "Cannot get node mapping of segment:%s/%s" % (pid, eid))
362
363    def get_mapping(self, pid, eid):
364        """
365        Get the physical to virtual mapping from the expinfo command and save
366        it in the self.map member.
367        """
368       
369        if self.debug:
370            if self.log:
371                self.log.info("[get_mapping] (debug) Generating mapping")
372                return True
373        else:
374            if self.log:
375                self.log.info("[get_mapping] Generating mapping")
376            self.get_nodes(pid, eid)
377            self.get_links(pid, eid)
378            return True
379
380    def get_initial_image(self, node, top):
381        for e in top.elements:
382            if isinstance(e, topdl.Computer):
383                if node == e.name:
384                    if e.os and len(e.os) == 1: 
385                        return e.os[0].get_attribute(
386                                'emulab_access:initial_image')
387        return None
388
389
390    def do_operation(self, op, lnode, pnode, params, top):
391        """
392        Carry out operation on node in the given experiment.
393        """
394        def get_param(params, name):
395            if params is None:
396                return None
397            for d in params:
398                if 'attribute' in d and d['attribute'] == name:
399                    return d.get('value', None)
400            else:
401                return None
402
403        op = op.lower()
404
405        if op == 'restore':
406            state = get_param(params, 'state')
407            if state is None:
408                self.status.append(operation_status(lnode, 
409                        operation_status.bad_param, 'No state to restore'))
410                return False
411            elif state == 'initial': 
412                image = self.get_initial_image(lnode, top)
413                if image:
414                    pid, iid = image.split('/')
415                    p = {'nodes': pnode, 'imagename': iid, 'imageproj': pid, 
416                            'wait': False}
417                    code, result = self.emulab_call('node.reload', p)
418                    if code == 0: 
419                        self.status.append(operation_status(lnode,
420                            operation_status.success, 'reloading'))
421                        return True
422                    else:
423                        self.status.append(operation_status(lnode,
424                                operation_status.federant, 
425                                'Error code: %d' % code))
426                        return False
427                else:
428                    self.status.append(operation_status(lnode,
429                            operation_status.federant, 
430                            'cannot find imageid??'))
431                    return False
432            elif state == 'boot': 
433                p = {'nodes': pnode, 'wait': False}
434                code, result = self.emulab_call('node.reboot', p)
435                if code == 0: 
436                    self.status.append(operation_status(lnode,
437                        operation_status.success, 'rebooting'))
438                    return True
439                else:
440                    self.status.append(operation_status(lnode,
441                            operation_status.federant, 'Error code: %d' % code))
442                    return False
443            else: 
444                if '/' in state:
445                    pid, iid = state.split('/')
446                else:
447                    pid = 'emulab-ops'
448                    iid = state
449
450                p = {'nodes': pnode, 'imagename': iid, 'imageproj': pid, 
451                        'wait': False}
452                code, result = self.emulab_call('node.reload', p)
453                if code == 0: 
454                    self.status.append(operation_status(lnode,
455                        operation_status.success, 'reloading'))
456                    return True
457                else:
458                    self.status.append(operation_status(lnode,
459                            operation_status.federant, 
460                            'Error code: %d' % code))
461                    return False
462        else: 
463            self.status.append(operation_status(lnode, operation_status.unsupp))
464            return False
465
Note: See TracBrowser for help on using the repository browser.