source: fedd/federation/xmlrpc_emulab_segment.py @ 9e5e251

compt_changes
Last change on this file since 9e5e251 was 6527d60, checked in by Ted Faber <faber@…>, 13 years ago

Improved info gathering

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