source: fedd/federation/xmlrpc_emulab_segment.py @ 5e71d34

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

Typo in unexercised error handler.

  • Property mode set to 100644
File size: 12.4 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):
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.
148        """
149
150        if self.debug:
151            if self.log:
152                self.log.debug("[make_null_experiment]: " + \
153                        "(debug) Creating experiment")
154            return True
155        else:
156            params = {
157                    'proj': pid,
158                    'exp': eid, 
159                    'nsfilestr': self.null,
160                    'batch': False,
161                    'idleswap': 0,
162                    'noidleswap_reason': 'Federated experiment',
163                    'noswapin': True,
164                    'wait': True
165                    }
166            if gid is not None:
167                params['group'] = gid
168            if self.log:
169                self.log.info("[make_null_experiment]: Creating experiment")
170            code, value = self.emulab_call('experiment.startexp', params)
171
172            if self.log:
173                if code == 0: 
174                    self.log.info('[make_null_experiment]: Create succeeded')
175                else: 
176                    self.log.error('[make_null_experiment]: Create failed: %s' \
177                            % value)
178
179            return code == 0
180
181    def swap_exp(self, pid, eid, direction='out', wait=True):
182        """
183        Swap experiment in.
184        """
185        if self.debug:
186            if self.log:
187                self.log.info("[swap_exp]: (debug) Swapping %s %s" % \
188                        (eid, direction))
189            return True
190        else:
191            if self.log:
192                self.log.info("[swap_exp]: Swapping %s %s" % (eid, direction))
193            params = {
194                    'proj': pid,
195                    'exp': eid,
196                    'direction': direction,
197                    'wait': wait,
198                    }
199            code, value = self.emulab_call('experiment.swapexp', params)
200
201            if self.log:
202                if code == 0: self.log.info('[swap_exp]: Swap succeeded')
203                else: self.log.error('[swap_exp]: Swap failed: %s' % value)
204
205        return code == 0
206
207    def terminate_exp(self, pid, eid, wait=True):
208        """
209        Completely terminate experiment
210        """
211        if self.debug:
212            if self.log:
213                self.log.info("[swap_exp]: (debug) terminate %s" %  eid)
214            return True
215        else:
216            if self.log:
217                self.log.info("[swap_exp]: Terminating %s" % eid)
218            params = {
219                    'proj': pid,
220                    'exp': eid,
221                    'wait': wait,
222                    }
223            code, value = self.emulab_call('experiment.endexp', params)
224
225            if self.log:
226                if code == 0: self.log.info('[swap_exp]: Terminate succeeded')
227                else: self.log.error('[swap_exp]: Terminate failed: %s' % value)
228
229        return code == 0
230
231    def modify_exp(self, pid, eid, tcl, wait=True):
232        if self.debug:
233            self.log.info("[modify_exp]: (debug) Modifying %s" % eid)
234            return True
235        else:
236            self.log.info("[modify_exp]: Modifying %s" % eid)
237            params = {
238                    'proj': pid,
239                    'exp': eid,
240                    'nsfilestr': tcl,
241                    'wait': wait,
242                    'reboot': True,
243                    'restart_eventsys': True,
244                    }
245            code, value = self.emulab_call('experiment.modify', params)
246            if self.log:
247                if code == 0: 
248                    self.log.info('[modify_exp]: Modify succeeded')
249                else: 
250                    self.log.error('[modify_exp]: Modify failed: %s' \
251                            % value)
252            return code == 0
253
254    def get_osid_map(self):
255        oses = { }
256        code, osids = self.emulab_call('osid.getlist', {})
257        for key, val in osids.items():
258            val['imageid'] = key
259            oses[val['osid']] = val
260        return oses
261
262    def get_links(self, pid, eid):
263        params = {
264                'proj': pid,
265                'exp': eid,
266                'aspect': 'links'
267                }
268        code, links = self.emulab_call('experiment.info', params)
269        if code ==0:
270            lans = { }
271            # Links contains per-interface information about all the LANs in
272            # the emulab experiment.  First construct the LANs, then look for
273            # overrides.
274            for i in links.values():
275                if i['name'] not in lans:
276                    lans[i['name']] = { 'cap': None, 'delay': None };
277                cap = max(i['bandwidth'], i['r_bandwidth'])
278                delay = max(i['delay'], i['r_delay'])
279                if delay == 0.0: delay = None
280                if lans[i['name']]['cap'] is None or \
281                        cap > lans[i['name']]['cap']:
282                    lans[i['name']]['cap'] = cap
283
284                if (lans[i['name']]['delay'] is None and delay is not None) or \
285                        delay > lans[i['name']]['delay']:
286                    lans[i['name']]['delay'] = delay
287
288            for s, info in lans.items():
289                self.subs[s] = self.lan_info(s, info['cap'], info['delay'])
290
291            # XXX: should deal better with r_delay != delay, etc
292            for i in links.values():
293                li = self.subs[i['name']]
294                cap = max(i['bandwidth'], i['r_bandwidth'])
295                delay = max(i['delay'], i['r_delay'])
296                if delay == 0.0: delay = None
297                if cap != li.cap or delay != li.delay:
298                    node, dummy = i['member'].split(':', 1)
299                    self.interfaces[i['member']] = \
300                            self.interface_info(i['member'], node, i['name'], 
301                                    cap, delay)
302
303            if self.log:
304                self.log.info("Link mapping complete")
305            return True
306        else:
307            raise service_error(service_error.internal, 
308                    "Cannot get link mapping of segment:%s/%s" % (pid, eid))
309
310    def get_nodes(self, pid, eid):
311
312        ev_active = ('ISUP', 'ALWAYSUP' )
313        ev_starting = ('REBOOTED', 'REBOOTING','PXEBOOTING', 
314                'BOOTING', 'RELOADSETUP', 'RELOADING', 'RELOADDONE', 
315                'RELOADDONEV2', 'TBSETUP')
316        ev_terminating = ( 'SHUTDOWN' )
317
318        osidmap = self.get_osid_map()
319
320        params = {
321                'proj': pid,
322                'exp': eid,
323                'aspect': 'mapping'
324                }
325        code, nodes = self.emulab_call('experiment.info', params)
326        if code ==0:
327            for k, v in nodes.items():
328                if v.get('erole', False) and 'pnode' in v:
329                    st = v.get('status', 'up')
330                    ev = v.get('eventstatus', 'ISUP')
331                    os = v.get('osid', None)
332                    lname = "%s.%s.%s" % (k, eid, pid)
333
334                    if st == 'up':
335                        if ev in ev_active: st = 'active'
336                        elif ev in ev_starting: st = 'starting'
337                        elif ev in ev_terminating: st = 'terminating'
338                        else: st = 'failed'
339                    else: st = 'failed'
340
341                    if os and os in osidmap:
342                       osname = osidmap[os].get('OS', None)
343                       osversion = osidmap[os].get('version', None)
344                       osimage = "%s/%s" % \
345                               ( osidmap[os].get('pid', ''), 
346                                       osidmap[os].get('imageid', ''))
347                    else:
348                        osname = osversion = osimage = None
349
350                    self.node[k] = self.node_info(k, v['pnode'], lname,
351                            st, osname, osversion, osimage)
352            if self.log:
353                self.log.info("Node mapping complete")
354            return True
355        else:
356            raise service_error(service_error.internal,
357                    "Cannot get node mapping of segment:%s/%s" % (pid, eid))
358
359    def get_mapping(self, pid, eid):
360        """
361        Get the physical to virtual mapping from the expinfo command and save
362        it in the self.map member.
363        """
364       
365        if self.debug:
366            if self.log:
367                self.log.info("[get_mapping] (debug) Generating mapping")
368                return True
369        else:
370            if self.log:
371                self.log.info("[get_mapping] Generating mapping")
372            self.get_nodes(pid, eid)
373            self.get_links(pid, eid)
374            return True
375
376    def get_initial_image(self, node, top):
377        for e in top.elements:
378            if isinstance(e, topdl.Computer):
379                if node == e.name:
380                    if e.os and len(e.os) == 1: 
381                        return e.os[0].get_attribute(
382                                'emulab_access:initial_image')
383        return None
384
385
386    def do_operation(self, op, lnode, pnode, params, top):
387        """
388        Carry out operation on node in the given experiment.
389        """
390        def get_param(params, name):
391            if params is None:
392                return None
393            for d in params:
394                if 'attribute' in d and d['attribute'] == name:
395                    return d.get('value', None)
396            else:
397                return None
398
399        op = op.lower()
400
401        if op == 'restore':
402            state = get_param(params, 'state')
403            if state is None:
404                self.status.append(operation_status(lnode, 
405                        operation_status.bad_param, 'No state to restore'))
406                return False
407            elif state == 'initial': 
408                image = self.get_initial_image(lnode, top)
409                if image:
410                    pid, iid = image.split('/')
411                    p = {'nodes': pnode, 'imagename': iid, 'imageproj': pid, 
412                            'wait': False}
413                    code, result = self.emulab_call('node.reload', p)
414                    if code == 0: 
415                        self.status.append(operation_status(lnode,
416                            operation_status.success, 'reloading'))
417                        return True
418                    else:
419                        self.status.append(operation_status(lnode,
420                                operation_status.federant, 
421                                'Error code: %d' % code))
422                        return False
423                else:
424                    self.status.append(operation_status(lnode,
425                            operation_status.federant, 
426                            'cannot find imageid??'))
427                    return False
428            elif state == 'boot': 
429                p = {'nodes': pnode, 'wait': False}
430                code, result = self.emulab_call('node.reboot', p)
431                if code == 0: 
432                    self.status.append(operation_status(lnode,
433                        operation_status.success, 'rebooting'))
434                    return True
435                else:
436                    self.status.append(operation_status(lnode,
437                            operation_status.federant, 'Error code: %d' % code))
438                    return False
439            else: 
440                if '/' in state:
441                    pid, iid = state.split('/')
442                else:
443                    pid = 'emulab-ops'
444                    iid = state
445
446                p = {'nodes': pnode, 'imagename': iid, 'imageproj': pid, 
447                        'wait': False}
448                code, result = self.emulab_call('node.reload', p)
449                if code == 0: 
450                    self.status.append(operation_status(lnode,
451                        operation_status.success, 'reloading'))
452                    return True
453                else:
454                    self.status.append(operation_status(lnode,
455                            operation_status.federant, 
456                            'Error code: %d' % code))
457                    return False
458        else: 
459            self.status.append(operation_status(lnode, operation_status.unsupp))
460            return False
461
Note: See TracBrowser for help on using the repository browser.