Changeset ecca6eb
- Timestamp:
- Sep 3, 2009 6:51:50 PM (15 years ago)
- Branches:
- axis_example, compt_changes, info-ops, master, version-2.00, version-3.01, version-3.02
- Children:
- f5ae004
- Parents:
- 66861a2
- Location:
- fedd/federation
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
fedd/federation/access.py
r66861a2 recca6eb 9 9 10 10 from threading import * 11 import subprocess 12 import time 11 13 12 14 from util import * … … 63 65 self.eventserver = config.get("access", "eventserver") 64 66 self.certdir = config.get("access","certdir") 67 self.ssh_privkey_file = config.get("access","ssh_privkey_file") 65 68 66 69 self.attrs = { } … … 616 619 else: self.projects[pname] = 1 617 620 self.allocation[aid]['project'] = pname 621 else: 622 # sproject is a static project associated with this allocation. 623 self.allocation[aid]['sproject'] = pname 618 624 619 625 if ap.has_key('resources'): … … 638 644 for u in ap['project']['user']: 639 645 uname = u['userID']['localname'] 646 if u['role'] == 'experimentCreation': 647 self.allocation[aid]['user'] = uname 640 648 for k in [ k['sshPubkey'] for k in u['access'] \ 641 649 if k.has_key('sshPubkey') ]: … … 789 797 790 798 791 class emulab_segment:799 class proxy_emulab_segment: 792 800 class ssh_cmd_timeout(RuntimeError): pass 793 801 794 802 def __init__(self, log=None, keyfile=None, debug=False): 795 803 self.log = log or logging.getLogger(\ 796 'fedd. experiment_control.emulab_segment')804 'fedd.access.proxy_emulab_segment') 797 805 self.ssh_privkey_file = keyfile 798 806 self.debug = debug 799 807 self.ssh_exec="/usr/bin/ssh" 800 808 self.scp_exec = "/usr/bin/scp" 801 self.ssh_cmd_timeout = emulab_segment.ssh_cmd_timeout809 self.ssh_cmd_timeout = access.proxy_emulab_segment.ssh_cmd_timeout 802 810 803 811 def scp_file(self, file, user, host, dest=""): … … 822 830 self.log.debug("[scp_file]: %s" % " ".join(scp_cmd)) 823 831 if not self.debug: 824 rv = call(scp_cmd, stdout=dnull, stderr=dnull, close_fds=True,825 832 rv = subprocess.call(scp_cmd, stdout=dnull, 833 stderr=dnull, close_fds=True, close_fds=True) 826 834 827 835 return rv == 0 … … 848 856 if not self.debug: 849 857 if dnull: 850 sub = Popen(sh_str, shell=True, stdout=dnull, stderr=dnull,851 858 sub = subprocess.Popen(sh_str, shell=True, stdout=dnull, 859 stderr=dnull, close_fds=True) 852 860 else: 853 sub = Popen(sh_str, shell=True, 854 close_fds=True) 861 sub = subprocess.Popen(sh_str, shell=True, close_fds=True) 855 862 if timeout: 856 863 i = 0 … … 876 883 return True 877 884 878 class start_segment( emulab_segment):885 class start_segment(proxy_emulab_segment): 879 886 def __init__(self, log=None, keyfile=None, debug=False): 880 experiment_control_local.emulab_segment.__init__(self, 881 log=log, keyfile=keyfile, debug=debug) 882 883 def create_config_tree(self, src_dir, dest_dir, script): 884 """ 885 Append commands to script that will create the directory hierarchy 886 on the remote federant. 887 """ 888 889 if os.path.isdir(src_dir): 890 print >>script, "mkdir -p %s" % dest_dir 891 print >>script, "chmod 770 %s" % dest_dir 892 893 for f in os.listdir(src_dir): 894 if os.path.isdir(f): 895 self.create_config_tree("%s/%s" % (src_dir, f), 896 "%s/%s" % (dest_dir, f), script) 897 else: 898 self.log.debug("[create_config_tree]: Not a directory: %s" \ 899 % src_dir) 900 901 def ship_configs(self, host, user, src_dir, dest_dir): 902 """ 903 Copy federant-specific configuration files to the federant. 904 """ 905 for f in os.listdir(src_dir): 906 if os.path.isdir(f): 907 if not self.ship_configs(host, user, "%s/%s" % (src_dir, f), 908 "%s/%s" % (dest_dir, f)): 909 return False 910 else: 911 if not self.scp_file("%s/%s" % (src_dir, f), 912 user, host, dest_dir): 913 return False 914 return True 915 916 def get_state(self, user, host, tb, pid, eid): 887 access.proxy_emulab_segment.__init__(self, log=log, 888 keyfile=keyfile, debug=debug) 889 self.null = """ 890 set ns [new Simulator] 891 source tb_compat.tcl 892 893 set a [$ns node] 894 895 $ns rtproto Session 896 $ns run 897 """ 898 899 def get_state(self, user, host, pid, eid): 917 900 # command to test experiment state 918 901 expinfo_exec = "/usr/testbed/bin/expinfo" … … 939 922 rv = 0 940 923 else: 941 status = Popen(cmd, stdout=PIPE, stderr=dev_null, 942 close_fds=True) 924 self.log.debug("Checking state") 925 status = subprocess.Popen(cmd, stdout=subprocess.PIPE, 926 stderr=dev_null, close_fds=True) 943 927 for line in status.stdout: 944 928 m = state_re.match(line) … … 956 940 if rv != 0 and state != 'none': 957 941 raise service_error(service_error.internal, 958 "Cannot get status of segment %s:%s/%s" % \ 959 (tb, pid, eid)) 942 "Cannot get status of segment:%s/%s" % (pid, eid)) 960 943 elif state not in ('active', 'swapped', 'swapping', 'none'): 961 944 raise service_error(service_error.internal, 962 "Cannot get status of segment %s:%s/%s" % \ 963 (tb, pid, eid)) 964 else: return state 965 966 967 def __call__(self, tb, eid, tbparams, tmpdir, timeout=0): 945 "Cannot get status of segment:%s/%s" % (pid, eid)) 946 else: 947 self.log.debug("State is %s" % state) 948 return state 949 950 951 def __call__(self, parent, eid, pid, user, tclfile, tmpdir, timeout=0): 968 952 """ 969 953 Start a sub-experiment on a federant. … … 974 958 """ 975 959 # ops node in the federant 976 host = "%s%s" % (tbparams[tb]['host'], tbparams[tb]['domain']) 977 user = tbparams[tb]['user'] # federant user 978 pid = tbparams[tb]['project'] # federant project 979 # XXX 980 base_confs = ( "hosts",) 981 tclfile = "%s.%s.tcl" % (eid, tb) # sub-experiment description 960 host = "%s%s" % (parent.ops, parent.domain) 982 961 # Configuration directories on the remote machine 983 962 proj_dir = "/proj/%s/exp/%s/tmp" % (pid, eid) 984 tarfiles_dir = "/proj/%s/tarfiles/%s" % (pid, eid) 985 rpms_dir = "/proj/%s/rpms/%s" % (pid, eid) 986 987 state = self.get_state(user, host, tb, pid, eid) 988 989 self.log.debug("[start_segment]: %s: %s" % (tb, state)) 990 self.log.info("[start_segment]:transferring experiment to %s" % tb) 991 992 if not self.scp_file("%s/%s/%s" % \ 993 (tmpdir, tb, tclfile), user, host): 963 softdir = "/proj/%s/software/%s" % (pid, eid) 964 # Local software dir 965 lsoftdir = "%s/software" % tmpdir 966 967 state = self.get_state(user, host, pid, eid) 968 969 if not self.scp_file(tclfile, user, host): 994 970 return False 995 971 … … 998 974 # logs there if the modify fails. Emulab software discards the 999 975 # logs from a failed startexp 976 try: 977 f = open("%s/null.tcl" % tmpdir, "w") 978 print >>f, self.null 979 f.close() 980 except IOError, e: 981 raise service_error(service_error.internal, 982 "Cannot stage tarfile/rpm: %s" % e.strerror) 983 1000 984 if not self.scp_file("%s/null.tcl" % tmpdir, user, host): 1001 985 return False 1002 self.log.info("[start_segment]: Creating %s on %s" % (eid, tb))986 self.log.info("[start_segment]: Creating %s" % eid) 1003 987 timedout = False 1004 988 try: … … 1012 996 1013 997 if timedout: 1014 state = self.get_state(user, host, tb,pid, eid)998 state = self.get_state(user, host, pid, eid) 1015 999 if state != "swapped": 1016 1000 return False 1017 1018 1001 1019 1002 # Open up a temporary file to contain a script for setting up the … … 1030 1013 # Script the filesystem changes 1031 1014 print >>scriptfile, "/bin/rm -rf %s" % proj_dir 1032 # Clear and create the tarfiles and rpm directories 1033 for d in (tarfiles_dir, rpms_dir): 1034 print >>scriptfile, "/bin/rm -rf %s/*" % d 1035 print >>scriptfile, "mkdir -p %s" % d 1015 # Clear and create the software directory 1016 print >>scriptfile, "/bin/rm -rf %s/*" % softdir 1036 1017 print >>scriptfile, 'mkdir -p %s' % proj_dir 1037 self.create_config_tree("%s/%s" % (tmpdir, tb), 1038 proj_dir, scriptfile) 1039 if os.path.isdir("%s/tarfiles" % tmpdir): 1040 self.create_config_tree("%s/tarfiles" % tmpdir, tarfiles_dir, 1041 scriptfile) 1042 if os.path.isdir("%s/rpms" % tmpdir): 1043 self.create_config_tree("%s/rpms" % tmpdir, rpms_dir, 1044 scriptfile) 1018 if os.path.isdir(lsoftdir): 1019 print >>scriptfile, 'mkdir -p %s' % softdir 1045 1020 print >>scriptfile, "rm -f %s" % scriptbase 1046 1021 scriptfile.close() … … 1057 1032 return False 1058 1033 1059 for f in base_confs: 1060 if not self.scp_file("%s/%s" % (tmpdir, f), user, host, 1061 "%s/%s" % (proj_dir, f)): 1062 return False 1063 if not self.ship_configs(host, user, "%s/%s" % (tmpdir, tb), 1064 proj_dir): 1065 return False 1066 if os.path.isdir("%s/tarfiles" % tmpdir): 1067 if not self.ship_configs(host, user, 1068 "%s/tarfiles" % tmpdir, tarfiles_dir): 1069 return False 1070 if os.path.isdir("%s/rpms" % tmpdir): 1071 if not self.ship_configs(host, user, 1072 "%s/rpms" % tmpdir, tarfiles_dir): 1073 return False 1034 for f in os.listdir(tmpdir): 1035 if not os.path.isdir("%s/%s" % (tmpdir, f)): 1036 if not self.scp_file("%s/%s" % (tmpdir, f), user, host, 1037 "%s/%s" % (proj_dir, f)): 1038 return False 1039 if os.path.isdir(lsoftdir): 1040 for f in os.listdir(lsoftdir): 1041 if not os.path.isdir("%s/%s" % (lsoftdir, f)): 1042 if not self.scp_file("%s/%s" % (lsoftdir, f), 1043 user, host, "%s/%s" % (softdir, f)): 1044 return False 1074 1045 # Stage the new configuration (active experiments will stay swapped 1075 1046 # in now) 1076 self.log.info("[start_segment]: Modifying %s on %s" % (eid, tb))1047 self.log.info("[start_segment]: Modifying %s" % eid) 1077 1048 try: 1078 1049 if not self.ssh_cmd(user, host, 1079 1050 "/usr/testbed/bin/modexp -r -s -w %s %s %s" % \ 1080 (pid, eid, tclfile ),1051 (pid, eid, tclfile.rpartition('/')[2]), 1081 1052 "modexp", timeout= 60 * 10): 1082 1053 return False … … 1088 1059 # Active experiments are still swapped, this swaps the others in. 1089 1060 if state != 'active': 1090 self.log.info("[start_segment]: Swapping %s in on %s" % \ 1091 (eid, tb)) 1061 self.log.info("[start_segment]: Swapping %s" % eid) 1092 1062 timedout = False 1093 1063 try: … … 1104 1074 self.log.debug("[start_segment]: swapin timed out " +\ 1105 1075 "checking state") 1106 state = self.get_state(user, host, tb,pid, eid)1076 state = self.get_state(user, host, pid, eid) 1107 1077 self.log.debug("[start_segment]: state is %s" % state) 1108 1078 return state == 'active' … … 1110 1080 return True 1111 1081 1112 class stop_segment( emulab_segment):1082 class stop_segment(proxy_emulab_segment): 1113 1083 def __init__(self, log=None, keyfile=None, debug=False): 1114 1084 experiment_control_local.emulab_segment.__init__(self, … … 1137 1107 1138 1108 def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 1139 tmpdir): 1109 tmpdir, master): 1110 1111 seer_out = False 1140 1112 for p in [ e for e in topo.elements \ 1141 1113 if isinstance(e, topdl.Computer) and e.get_attribute('portal')]: … … 1145 1117 lproj, leid = lexp.split('/', 1) 1146 1118 ldomain = e.get_attribute('domain') 1119 mexp = e.get_attribute('masterexperiment') 1120 mproj, meid = mexp.split("/", 1) 1121 mdomain = e.get_attribute('masterdomain') 1147 1122 scriptdir = e.get_attribute('scriptdir') 1148 1123 active = e.get_attribute('active') 1149 type = e.get_attribute(' type')1124 type = e.get_attribute('portal_type') 1150 1125 segid = fedid(hexstr=e.get_attribute('peer_segment')) 1151 1126 for e in topo.elements: … … 1170 1145 print >>f, "RemoteEventServerName: event-server%s" % rdomain 1171 1146 print >>f, "SeerControl: control.%s.%s%s" % \ 1172 ( leid.lower(), lproj.lower(), ldomain)1147 (meid.lower(), mproj.lower(), mdomain) 1173 1148 print >>f, "Type: %s" % type 1174 1149 print >>f, "RemoteExperiment: %s" % rexp … … 1188 1163 raise service_error(service_error.internal, 1189 1164 "Can't write protal config %s: %s" % (cfn, e)) 1165 1166 # XXX: This little seer config file needs to go away. 1167 if not seer_out: 1168 try: 1169 seerfn = "%s/seer.conf" % tmpdir 1170 f = open(seerfn, "w") 1171 if not master: 1172 print >>f, "ControlNode: control.%s.%s%s" % \ 1173 (meid.lower(), mproj.lower(), mdomain) 1174 print >>f, "ExperimentID: %s" % mexp 1175 f.close() 1176 except IOError, e: 1177 raise service_error(service_error.internal, 1178 "Can't write seer.conf: %s" %e) 1179 seer_out = True 1180 1181 1182 def generate_client_conf(self, user, proj, eid, tmpdir): 1183 try: 1184 f = open("%s/client.conf" % tmpdir, "w") 1185 if self.attrs.has_key('SMBshare'): 1186 print >>f, "SMBshare: %s" % self.attrs['SMBshare'] 1187 print >>f, "ProjectUser: %s" % user 1188 print >>f, "ProjectName: %s" % proj 1189 print >>f, "ExperimentID: %s/%s" % (proj, eid) 1190 f.close() 1191 except IOError, e: 1192 raise service_error(service_error.internal, 1193 "Cannot write client.conf: %s" %s) 1194 1195 def generate_ns2(self, topo, expfn, softdir, master): 1196 t = topo.clone() 1197 1198 # Weed out the things we aren't going to instantiate: Segments, portal 1199 # substrates, and portal interfaces. (The copi in the for loop allows 1200 # us to delete from e.elements in side the for loop). 1201 for e in [e for e in t.elements]: 1202 if isinstance(e, topdl.Segment): 1203 t.elements.remove(e) 1204 if isinstance(e, topdl.Computer): 1205 e.interface = [i for i in e.interface \ 1206 if not i.get_attribute('portal')] 1207 t.substrates = [ s for s in t.substrates \ 1208 if not s.get_attribute('portal')] 1209 t.incorporate_elements() 1210 1211 # Localize the software locations 1212 for e in t.elements: 1213 for s in getattr(e, 'software', []): 1214 s.location = re.sub("^.*/", softdir, s.location) 1215 1216 1217 # Customize the ns2 output for local portal commands and images 1218 filters = [] 1219 1220 if master: cmdname = 'MasterConnectorCmd' 1221 else:cmdname = 'SlaveConnectorCmd' 1222 1223 if self.attrs.has_key(cmdname): 1224 filters.append(topdl.generate_portal_command_filter( 1225 self.attrs.get(cmdname))) 1226 1227 if self.attrs.has_key('connectorImage'): 1228 filters.append(topdl.generate_portal_image_filter( 1229 self.attrs.get('connectorImage'))) 1230 1231 if self.attrs.has_key('connectorType'): 1232 filters.append(topdl.generate_portal_hardware_filter( 1233 self.attrs.get('connectorType'))) 1234 1235 # Convert to ns and write it out 1236 expfile = topdl.topology_to_ns2(t, filters) 1237 try: 1238 f = open(expfn, "w") 1239 print >>f, expfile 1240 f.close() 1241 except IOError: 1242 raise service_error(service_error.internal, 1243 "Cannot write experiment file %s: %s" % (expfn,e)) 1190 1244 1191 1245 def StartSegment(self, req, fid): … … 1214 1268 1215 1269 configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey')) 1216 keys = set(('ssh_pubkey', 'ssh_secretkey'))1217 1218 1270 1219 1271 try: … … 1221 1273 except KeyError: 1222 1274 raise service_error(server_error.req, "Badly formed request") 1275 1223 1276 auth_attr = req['allocID']['fedid'] 1277 aid = "%s" % auth_attr 1224 1278 attrs = req.get('fedAttr', []) 1225 print auth_attr 1226 print "%s" % auth_attr 1227 if self.auth.check_attribute(fid, auth_attr): 1228 print "OK" 1229 else: 1230 print "Fail" 1279 if not self.auth.check_attribute(fid, auth_attr): 1280 raise service_error(service_error.access, "Access denied") 1231 1281 1232 1282 if req.has_key('segmentdescription') and \ … … 1238 1288 "Request missing segmentdescription'") 1239 1289 1290 master = req.get('master', False) 1291 1240 1292 certfile = "%s/%s.pem" % (self.certdir, auth_attr) 1241 1293 try: 1242 1294 tmpdir = tempfile.mkdtemp(prefix="access-") 1295 softdir = "%s/software" % tmpdir 1243 1296 except IOError: 1244 1297 raise service_error(service_error.internal, "Cannot create tmp dir") 1245 1298 1246 1299 sw = set() 1247 for e in [c for c in topo.elements if getattr(c, 'software', False)]:1248 for s in e.software:1300 for e in topo.elements: 1301 for s in getattr(e, 'software', []): 1249 1302 sw.add(s.location) 1303 if len(sw) > 0: 1304 os.mkdir(softdir) 1250 1305 for s in sw: 1251 get_url(s, certfile, tmpdir)1306 get_url(s, certfile, softdir) 1252 1307 1253 1308 for a in attrs: … … 1255 1310 get_url(a['value'], certfile, tmpdir) 1256 1311 if a['attribute'] == 'ssh_pubkey': 1257 pubkey_base = a[' attribute'].rpartition('/')[2]1312 pubkey_base = a['value'].rpartition('/')[2] 1258 1313 if a['attribute'] == 'ssh_secretkey': 1259 secretkey_base = a['attribute'].rpartition('/')[2] 1260 1261 self.generate_portal_configs(topo, pubkey_base, secretkey_base, tmpdir) 1314 secretkey_base = a['value'].rpartition('/')[2] 1315 if a['attribute'] == 'experiment_name': 1316 ename = a['value'] 1317 1318 proj = None 1319 user = None 1320 self.state_lock.acquire() 1321 if self.allocation.has_key(aid): 1322 proj = self.allocation[aid].get('project', None) 1323 if not proj: 1324 proj = self.allocation[aid].get('sproject', None) 1325 user = self.allocation[aid].get('user', None) 1326 self.state_lock.release() 1327 1328 if not proj: 1329 raise service_error(service_error.internal, 1330 "Can't find project for %s" %aid) 1331 1332 if not user: 1333 raise service_error(service_error.internal, 1334 "Can't find creation user for %s" %aid) 1335 1336 expfile = "%s/experiment.tcl" % tmpdir 1337 1338 self.generate_portal_configs(topo, pubkey_base, 1339 secretkey_base, tmpdir, master) 1340 self.generate_ns2(topo, expfile, 1341 "/proj/%s/software/%s/" % (proj, ename), master) 1342 self.generate_client_conf(user, proj, ename, tmpdir) 1343 starter = self.start_segment(keyfile=self.ssh_privkey_file, debug=False) 1344 starter(self, ename, proj, user, expfile, tmpdir) 1262 1345 1263 1346 return { 'allocID': req['allocID'] } -
fedd/federation/experiment_control.py
r66861a2 recca6eb 2151 2151 self.caller = caller 2152 2152 2153 def __call__(self, uri, aid, topo, attrs=None):2153 def __call__(self, uri, aid, topo, master, attrs=None): 2154 2154 req = { 2155 2155 'allocID': { 'fedid' : aid }, … … 2157 2157 'topdldescription': topo.to_dict(), 2158 2158 }, 2159 'master': master, 2159 2160 } 2160 2161 if attrs: … … 2204 2205 trusted_certs=self.trusted_certs, 2205 2206 caller=self.call_StartSegment), 2206 args=(uri, aid, topo[tb], attrs), name=tb,2207 args=(uri, aid, topo[tb], False, attrs), name=tb, 2207 2208 pdata=thread_pool, trace_file=self.trace_file) 2208 2209 threads.append(t) … … 2231 2232 trusted_certs=self.trusted_certs, 2232 2233 caller=self.call_StartSegment) 2233 if not starter(uri, aid, topo[master], attrs):2234 if not starter(uri, aid, topo[master], True, attrs): 2234 2235 failed.append(master) 2235 2236 … … 2668 2669 "/usr/local/federation/bin"), 2669 2670 ('active', "%s" % active), 2670 (' type', 'both'),2671 ('startup', 'sudo -H /usr/local/federation/bin/fed-tun.pl -f /proj/%s/exp/%s/tmp/%s %s.gw.conf >& /tmp/bridge.log' % (sproject, eid, myname.lower(), sdomain.lower())))2671 ('portal_type', 'both'), 2672 ('startup', 'sudo -H /usr/local/federation/bin/fed-tun.pl -f /proj/%s/exp/%s/tmp/%s.%s.%s%s.gw.conf >& /tmp/bridge.log' % (sproject, eid, myname.lower(), eid.lower(), sproject.lower(), sdomain.lower()))) 2672 2673 ], 2673 2674 interface=[ … … 2836 2837 (url_base, expid) 2837 2838 }, 2839 { 2840 'attribute': 'experiment_name', 2841 'value': eid, 2842 }, 2838 2843 ] 2839 2844 -
fedd/federation/topdl.py
r66861a2 recca6eb 231 231 def clone(self): 232 232 return ID(self.fedid, self.uuid, self.uri, self.localname, 233 self.ker nberosUsername)233 self.kerberosUsername) 234 234 235 235 def to_dict(self): … … 327 327 return Segment(self.id.clone(), self.type, self.uri, 328 328 interface=[i.clone() for i in self.interface], 329 attribute=[a.clone() for a in attribute])329 attribute=[a.clone() for a in self.attribute]) 330 330 331 331 def to_dict(self): … … 579 579 580 580 def to_tcl_name(n): 581 t = re.sub st('-(\d+)', '(\1)', n)581 t = re.sub('-(\d+)', '(\1)', n) 582 582 return t 583 584 def generate_portal_command_filter(cmd): 585 def rv(e): 586 s ="" 587 if isinstance(e, Computer): 588 gw = e.get_attribute('portal') 589 if gw: 590 s = "%s $%s\n" % (cmd, to_tcl_name(e.name[0])) 591 return s 592 return rv 583 593 584 594 def generate_portal_image_filter(image): … … 588 598 gw = e.get_attribute('portal') 589 599 if gw: 590 s = "tb-set-node-os $%s %s " % (to_tcl_name(e.name[0]), image)600 s = "tb-set-node-os $%s %s\n" % (to_tcl_name(e.name[0]), image) 591 601 return s 592 602 return rv 593 603 594 def generate_portal_ command_filter(cmd):604 def generate_portal_hardware_filter(type): 595 605 def rv(e): 596 606 s ="" … … 598 608 gw = e.get_attribute('portal') 599 609 if gw: 600 s = " %s $%s" % (cmd, to_tcl_name(e.name[0]))610 s = "tb-set-hardware $%s %s\n" % (to_tcl_name(e.name[0]), type) 601 611 return s 602 612 return rv … … 620 630 if osid: 621 631 out += "tb-set-node-os $%s %s\n" % (name, osid) 632 hw = e.get_attribute('type') 633 if hw: 634 out += "tb-set-hardware $%s %s\n" % (name, hw) 622 635 for s in e.software: 623 636 if s.install:
Note: See TracChangeset
for help on using the changeset viewer.