source: fedd/federation/emulab_shared_nat_segment.py @ a80a4a7

Last change on this file since a80a4a7 was d75005b, checked in by Ted Faber <faber@…>, 11 years ago

Need this module.

  • Property mode set to 100755
File size: 7.2 KB
Line 
1#!/usr/local/bin/python
2
3import sys, os
4import re
5import os.path
6
7import tempfile
8import subprocess
9import logging 
10import time
11import signal
12
13import util
14
15import xmlrpclib
16
17import emulab_segment
18
19
20class start_segment(emulab_segment.start_segment):
21    def __init__(self, log=None, keyfile=None, debug=False, boss=None,
22            ops=None, cert=None):
23        emulab_segment.start_segment.__init__(self, log=log, keyfile=keyfile,
24                debug=debug, boss=boss, ops=ops, cert=cert)
25
26    def __call__(self, parent, eid, pid, user, tclfile, tmpdir, timeout=0, 
27            gid=None):
28        """
29        Start a sub-experiment on a federant.
30
31        Get the current state, and terminate the experiment if it exists. The
32        group membership of the experiment is difficult to determine or change,
33        so start with a clean slate.  Create a new one and ship data
34        and configs and start the experiment.  There are small ordering
35        differences based on the initial state of the sub-experiment.
36        """
37
38        # exports_segment has established a clean dummy experiment.
39
40        if not self.set_up_experiment_filespace(user, self.ops, 
41                pid, eid, tmpdir):
42            return False
43
44        # Put the file into a string to pass to emulab.
45        try:
46            tcl = "".join([ l for l in open(tclfile,"r")])
47        except EnvironmentError, e:
48            self.log.error("Can't read %s: %s" % (tclfile, e))
49            return False
50       
51        # Stage the new configuration
52        if not self.modify_exp(pid, eid, tcl):
53            self.log.error("modify failed")
54            return False
55
56        if not self.swap_exp(pid, eid, 'in'):
57            self.log.error("swap in failed")
58            return False
59        # Everything has gone OK.
60        self.get_mapping(pid,eid)
61        return True
62
63class exports_segment(emulab_segment.exports_segment):
64    '''
65    Class to export parameters from this segment.  The shared NAT allocates
66    externally accessible ports in 2 steps.  Provisional ports are assigned
67    when the experiment is created or modified and then preserved when possible
68    across further mods.  This class creates a very simple experiment to make
69    those provisional allocations and then installs them in the connInfo list.
70    Note that this class takes on some of the initial steps from start_segment
71    - clearing out old experiments and creating the dummy and initial
72    experiment.
73    '''
74    def __init__(self, log=None, keyfile=None, debug=False, boss=None,
75            ops=None, cert=None):
76        emulab_segment.exports_segment.__init__(self, log=log, keyfile=keyfile,
77                debug=debug, boss=boss, ops=ops, cert=cert)
78
79    def make_portal_experiment(self, pid, eid, tmpdir, gid, ports):
80        ns_data = \
81'''
82set ns [new Simulator]
83source tb_compat.tcl
84
85'''
86        for p in ports:
87            ns_data += 'set %s [$ns node]\n' % p
88            ns_data += \
89                'tb-allow-external %s shared nat 0/0/tcp rdr 22/tcp\n' % p
90        ns_data += \
91'''     
92$ns rtproto Session
93$ns run
94'''
95        # If ports is empty, just create the standard null experiment
96        if len(ports) == 0:
97            ns_data = None
98
99        state = self.get_state(pid, eid)
100
101        if state != 'none':
102            self.terminate_exp(pid, eid)
103
104        return self.make_null_experiment(pid, eid, tmpdir, gid, ns_data)
105
106    def get_assignments(self, pid, eid, user, tmpdir, ports):
107        '''
108        Copy the output of the shared NAT risky experiment setup output to the
109        temporary directory and parse it for preliminary (and hopefully
110        permanent) port assignments.  That output is a pseudo-xmlrpc response.
111        '''
112        # No ports? Nothing to do here.
113        if len(ports) == 0:
114            return True
115
116        remote_risk = os.path.join('/proj', pid, 'exp', eid, 'tbdata',
117                '%s.risk' % eid)
118        local_risk = os.path.join(tmpdir, 'risk')
119
120        try:
121            self.scp_file_from(remote_risk, user, self.ops, local_risk)
122            f = open(local_risk)
123            params, method = xmlrpclib.loads(f.read())
124            f.close()
125            # params should be a single element tuple containing the "xmlrpc"
126            # request as a dict
127            if not isinstance(params, tuple):
128                self.log.error('misformatted risk file: params not a tuple')
129                return False
130            params = params[0]
131
132            if 'assignments' not in params:
133                self.log.error('misformatted risk file: No arguments in params')
134                return False
135
136            for k, v in params['assignments'].items():
137                if not k.endswith('/22/tcp'):
138                    continue
139                # Get the portal name from the SSH port assignment
140                p = k[0:k.index('/22/tcp')]
141                if p in ports:
142                    ports[p]  = v
143        except Exception, e:
144            self.log.error('Error getting assignments: %s' % e)
145            return False
146        return True
147
148
149    def __call__(self, parent, eid, pid, user, connInfo, tmpdir, timeout=0,
150            gid=None):
151        '''
152        Create an experiment containing only portals and read the ssh_ports
153        from it.  The shared NAT properly marshals DNS names.
154        '''
155
156        ports = { }
157
158        for c in connInfo:
159            for p in c.get('parameter', []):
160                # Only set output parameters
161                if  p.get('type', '') != 'output':
162                    continue
163                name = p.get('name', '')
164                k = p.get('key', None)
165                if name == 'peer':
166                    if k is None or k.index('/') == -1:
167                        self.log.debug('Bad key for peer: %s' % k)
168                        return False
169
170                    portal_name = k[k.index('/')+1:]
171
172                    if parent.nat_portal:
173                        value = parent.nat_portal
174                    elif k and k.index('/') != -1:
175                        value = "%s.%s.%s%s" % \
176                            (portal_name, eid, pid, parent.domain)
177                    else:
178                        self.log.error('Bad export request: %s' % p)
179                        continue
180                    p['value'] = value
181                    ports[portal_name] = None
182                    self.log.debug('Assigning %s to %s' % (k, value))
183                    self.log.debug('Adding entry for %s to ports' % portal_name)
184                elif name == 'ssh_port':
185                    # not yet
186                    continue
187                else:
188                    self.log.error("Unknown export parameter: %s" % name)
189                    return False
190
191        if not self.make_portal_experiment(pid, eid, tmpdir, gid, ports):
192            return False
193
194        if not self.get_assignments(pid, eid, user, tmpdir, ports):
195            return False
196
197        # Through these again to set ssh_ports
198        for c in connInfo:
199            for p in c.get('parameter', []):
200                # Only set output parameters
201                if  p.get('type', '') != 'output':
202                    continue
203                name = p.get('name', '')
204                k = p.get('key', None)
205                if name != 'ssh_port':
206                    continue
207                if k is None or k.index('/') == -1:
208                    self.log.debug('Bad key for ssh port: %s' % k)
209                    return False
210
211                # The portal name is the string following the slash in the key,
212                # with the trainling (5 character) -port removed.
213                portal_name = k[k.index('/')+1:-5]
214                if portal_name not in ports or ports[portal_name] is None:
215                    self.log.error('No port assigned to %s' % portal_name)
216                    return False
217                value = ports[portal_name]
218                p['value'] = value
219                self.log.debug('Assigning %s to %s' % (k, value))
220       
221        return True
222
223# These are all simple aliases to the main emulab_segment line.
224class stop_segment(emulab_segment.stop_segment):
225    def __init__(self, log=None, keyfile=None, debug=False, boss=None, 
226            ops=None, cert=None):
227        emulab_segment.stop_segment.__init__(self, log=log, keyfile=keyfile,
228                debug=debug, boss=boss, ops=ops, cert=cert)
229
230class info_segment(emulab_segment.info_segment):
231    def __init__(self, log=None, keyfile=None, debug=False, boss=None, 
232            ops=None, cert=None):
233        emulab_segment.info_segment.__init__(self, log=log, keyfile=keyfile,
234                debug=debug, boss=boss, ops=ops, cert=cert)
235
236class operation_segment(emulab_segment.operation_segment):
237    def __init__(self, log=None, keyfile=None, debug=False, boss=None, 
238            ops=None, cert=None):
239        emulab_segment.operation_segment.__init__(self, log=log,
240                keyfile=keyfile, debug=debug, boss=boss, ops=ops, cert=cert)
Note: See TracBrowser for help on using the repository browser.