[d75005b] | 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) |
---|