1 | #!/usr/local/bin/python |
---|
2 | |
---|
3 | import sys, os |
---|
4 | import re |
---|
5 | import os.path |
---|
6 | |
---|
7 | import tempfile |
---|
8 | import subprocess |
---|
9 | import logging |
---|
10 | import time |
---|
11 | import signal |
---|
12 | |
---|
13 | import util |
---|
14 | |
---|
15 | import xmlrpclib |
---|
16 | |
---|
17 | import emulab_segment |
---|
18 | |
---|
19 | |
---|
20 | class 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 | |
---|
63 | class 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 | ''' |
---|
82 | set ns [new Simulator] |
---|
83 | source 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. |
---|
224 | class 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 | |
---|
230 | class 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 | |
---|
236 | class 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) |
---|