Package spacewalkkoan :: Module spacewalkkoan
[hide private]
[frames] | no frames]

Source Code for Module spacewalkkoan.spacewalkkoan

  1  # 
  2  # Copyright (c) 2008--2016 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15  # Kickstart a system using koan. 
 16  # 
 17   
 18  import traceback 
 19  import stat 
 20  import shutil 
 21  import sys 
 22  import os 
 23  import os.path 
 24  import tempfile 
 25   
 26  if sys.version_info[0] == 3: 
 27      import xmlrpc.client as xmlrpclib 
 28  else: 
 29      import xmlrpclib 
 30   
 31  from koan.app import Koan 
 32   
 33  SHADOW      = "/tmp/ks-tree-shadow" 
 34   
35 -def execute(cmd):
36 tmp = tempfile.mktemp() 37 status = os.system(cmd + " > " + tmp) 38 data = open(tmp).readlines() 39 ret = [] 40 for l in data: 41 ret.append(l.strip()) 42 if status == 0: 43 return ret 44 raise Exception('Error executing command:\n %s\noutput:\n%s' % (cmd, '\n'.join(ret)))
45
46 -def find_host_name():
47 return execute("hostname")[0]
48
49 -def find_netmask(device):
50 nm = execute("LANG=C ipcalc -4ms $(ip -4 -o addr show dev %s | awk '{print $4}')|awk -F= '{print $2}'" % device) 51 if nm: 52 return nm[0] 53 else: 54 return ""
55
56 -def find_netmask6(device):
57 nm6 = execute("LANG=C ip -6 -o addr show dev %s | perl -lne 'print $1 if m!/(.+) scope global!'" % device) 58 if nm6: 59 return nm6[0] 60 else: 61 return ""
62
63 -def find_ip(device):
64 ip = execute("LANG=C ip -4 -o addr show dev %s | perl -lne 'print $1 if m!.+\s(.+)/.+ scope global!'" % device) 65 if ip: 66 return ip[0] 67 else: 68 return ""
69
70 -def find_ip6(device):
71 ip6 = execute("LANG=C ip -6 -o addr show dev %s | perl -lne 'print $1 if m!.+\s(.+)/.+ scope global!'" % device) 72 if ip6: 73 return ip6[0] 74 else: 75 return ""
76
77 -def find_name_servers():
78 servers = execute("cat /etc/resolv.conf | perl -lne '/^nameserver\s+(\S+)/ and print $1'") 79 ret = [] 80 for s in servers: 81 if s not in ("127.0.0.1", "::1"): 82 ret.append(s) 83 return ret
84
85 -def find_gateway(device):
86 response = execute("ip -f inet route list dev %s|awk '/^default/ {print $3}'" % device) 87 if response: 88 return response[0] 89 else: 90 return ""
91
92 -def find_gateway6(device):
93 response = execute("ip -f inet6 route list dev %s|awk '/^default/ {print $3}'" % device) 94 if response: 95 return response[0] 96 else: 97 return ""
98
99 -def getSystemId():
100 path = "/etc/sysconfig/rhn/systemid" 101 if not os.access(path, os.R_OK): 102 return None 103 return open(path, "r").read()
104
105 -def getInitrdPath():
106 path = '/boot/initrd.img' 107 if os.access(path + "_koan", os.R_OK): 108 return path + "_koan" 109 return path
110
111 -def update_static_device_records(kickstart_host, static_device):
112 client = xmlrpclib.Server("https://" + kickstart_host + "/rpc/api") 113 data = {"gateway" : find_gateway(static_device),\ 114 "nameservers": find_name_servers(),\ 115 "hostname" : find_host_name(),\ 116 "device" : static_device,\ 117 "ip": find_ip(static_device),\ 118 "netmask" : find_netmask(static_device)} 119 120 data6 = {"gateway" : find_gateway6(static_device), \ 121 "device" : static_device, \ 122 "ip" : find_ip6(static_device), \ 123 "netmask" : find_netmask6(static_device)} 124 125 api_version = client.api.get_version() 126 127 # Since api_version >= 11.1 we support setup_static_network with IPv6 data 128 if float(api_version) <= 11.00: 129 client.system.setup_static_network(getSystemId(), data) 130 else: 131 client.system.setup_static_network(getSystemId(), data, data6)
132
133 -def initiate(kickstart_host, base, extra_append, static_device=None, system_record="", preserve_files=[]):
134 135 error_messages = {} 136 success = 0 137 138 # cleanup previous attempt 139 rm_rf(SHADOW) 140 os.mkdir(SHADOW) 141 142 print("Preserve files! : %s" % preserve_files) 143 144 try: 145 if static_device: 146 update_static_device_records(kickstart_host, static_device) 147 148 k = Koan() 149 k.list_items = 0 150 k.server = kickstart_host 151 k.is_virt = 0 152 k.is_replace = 1 153 k.is_display = 0 154 k.profile = None 155 156 if system_record != "": 157 k.system = system_record 158 else: 159 k.system = None 160 k.port = 443 161 k.image = None 162 k.live_cd = None 163 k.virt_path = None 164 k.virt_type = None 165 k.virt_bridge = None 166 k.no_gfx = 1 167 k.add_reinstall_entry = None 168 k.kopts_override = None 169 k.use_kexec = None 170 k.embed_kickstart = k.embed_autoinst = None 171 if hasattr(k, 'no_copy_default'): 172 k.no_copy_default = 1 173 else: # older koan 174 k.grubby_copy_default = 0 175 if static_device: 176 k.embed_kickstart = k.embed_autoinst = 1 177 k.run() 178 179 except Exception: 180 (xa, xb, tb) = sys.exc_info() 181 try: 182 getattr(xb, "from_koan") 183 error_messages['koan'] = str(xb)[1:-1] 184 print(str(xb)[1:-1]) # nice exception, no traceback needed 185 except: 186 print(xa) 187 print(xb) 188 print(" ".join(traceback.format_list(traceback.extract_tb(tb)))) 189 error_messages['koan'] = " ".join(traceback.format_list(traceback.extract_tb(tb))) 190 return (1, "Kickstart failed. Koan error.", error_messages) 191 192 # Now process preserve_files if there are any 193 initrd = getInitrdPath() 194 if preserve_files: 195 ret = create_new_rd(initrd, preserve_files) 196 if ret: 197 # Error 198 return ret 199 initrd = initrd + ".merged" 200 201 202 203 return (0, "Kickstart initiate succeeded", error_messages)
204 205
206 -class VirtDiskPathExistsError(Exception):
207 - def __init__(self, disk_path):
208 self.value = disk_path
209 - def __str__(self):
210 return "Virt Disk Path %s already exists on the host system. Please provide another disk path for the virt guest and reschedule your guest kickstart." % self.value
211 212
213 -class BlockDeviceNonexistentError(Exception):
214 - def __init__(self, device_path):
215 self.value = device_path
216 - def __str__(self):
217 return "Block Device Path %s does not exist on the host system. Please create the device for the virtual guest and reschedule your guest kickstart." % self.value
218 219
220 -def initiate_guest(kickstart_host, cobbler_system_name, virt_type, name, mem_kb, 221 vcpus, disk_gb, virt_bridge, disk_path, extra_append, log_notify_handler=None):
222 223 error_messages = {} 224 success = 0 225 try: 226 if disk_path.startswith('/dev/'): 227 if not os.path.exists(disk_path): 228 raise BlockDeviceNonexistentError(disk_path) 229 else: 230 if os.path.exists(disk_path): 231 raise VirtDiskPathExistsError(disk_path) 232 # Switch to KVM if possible 233 if virt_type == "qemu": 234 if os.path.exists("/dev/kvm"): 235 virt_type = "kvm" 236 else: 237 print("Warning: KVM not available, using QEMU.") 238 k = Koan() 239 k.list_items = 0 240 k.server = kickstart_host 241 k.is_virt = 1 242 k.is_replace = 0 243 k.is_display = 0 244 k.port = 443 245 k.profile = None 246 k.system = cobbler_system_name 247 k.should_poll = 1 248 k.image = None 249 k.live_cd = None 250 k.virt_name = name 251 k.virt_path = disk_path 252 k.virt_type = virt_type 253 k.virt_bridge = virt_bridge 254 k.no_gfx = False 255 k.add_reinstall_entry = None 256 k.kopts_override = None 257 k.virt_auto_boot = None 258 if hasattr(k, 'no_copy_default'): 259 k.no_copy_default = 1 260 else: # older koan 261 k.grubby_copy_default = 0 262 if hasattr(k, 'virtinstall_wait'): 263 k.virtinstall_wait = 0 264 k.run() 265 266 # refresh current virtualization state on the server 267 import virtualization.support 268 virtualization.support.refresh() 269 except Exception: 270 (xa, xb, tb) = sys.exc_info() 271 if str(xb).startswith("The MAC address you entered is already in use"): 272 # I really wish there was a better way to check for this 273 error_messages['koan'] = str(xb) 274 print(str(xb)) 275 elif hasattr(xb, "from_koan") and len(str(xb)) > 1: 276 error_messages['koan'] = str(xb)[1:-1] 277 print(str(xb)[1:-1]) # nice exception, no traceback needed 278 else: 279 print(xa) 280 print(xb) 281 print(" ".join(traceback.format_list(traceback.extract_tb(tb)))) 282 error_messages['koan'] = str(xb) + ' ' + " ".join(traceback.format_list(traceback.extract_tb(tb))) 283 return (1, "Virtual kickstart failed. Koan error.", error_messages) 284 285 return (0, "Virtual kickstart initiate succeeded", error_messages)
286
287 -def create_new_rd(initrd, preserve_files=[]):
288 """ 289 Returns None if everything went well, or a tuple 290 (err_code, err_string, dict) if problems were found 291 """ 292 if not initrd: 293 return (3, "Kickstart create new init failed: initrd not found: %s" % 294 initrd, {}) 295 296 # quota should be configurable from the UI 297 quota = 1000000 298 # lame naming below to use /tmp/ks-tres-shadow 2X 299 # but needed to get it here the ks.cfg expects it 300 preserve_shadow = SHADOW + SHADOW 301 # new FileCopier class handles the dirty work of getting the 302 # preserved file set copied w/ all permissions, owners, etc 303 # kept intact and in the correct location 304 c = FileCopier(preserve_files, preserve_shadow, quota=quota) 305 try: 306 c.copy() 307 except QuotaExceeded: 308 return (3, "Quota of %s bytes exceeded" % quota, {}) 309 310 (status, stdout, stderr) = my_popen([ 311 "/usr/sbin/merge-rd.sh", initrd, initrd, SHADOW]) 312 if status: 313 return (status, 'Error creating the new RAM disk', 314 _build_error(status, stdout, stderr)) 315 316 return None
317 318
319 -def rm_rf(path):
320 "Equivalent of rm -rf" 321 # We need to make sure path exists 322 if os.path.islink(path): 323 # Broken links will be reported as non-existent 324 os.unlink(path) 325 return 326 # Now make sure path exists before we call the recursive function 327 if os.path.exists(path): 328 return _remove_func(path)
329 330
331 -def _remove_func(path):
332 "Recursive function for rm -rf; will fail if path doesn't exist" 333 if not os.path.isdir(path): 334 # Attempt to remove the file/link/etc 335 os.unlink(path) 336 return 337 338 # It's a directory! 339 files = os.listdir(path) 340 # We need to add the path since listdir only returns a relative path 341 files = list(map(lambda x, p=path: os.path.join(p, x), files)) 342 # Recursive call 343 map(_remove_func, files) 344 # After we remove everything from this directory we can also remove 345 # the directory 346 os.rmdir(path) 347 return
348
349 -def my_popen(cmd):
350 print("CMD: %s " % cmd) 351 352 subproc = 1 353 try: 354 import subprocess 355 except ImportError: 356 #RHEL 4 (python 2.3) doesn't have subprocess 357 import popen2 358 subproc = 0 359 360 if subproc: 361 c = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, 362 stderr=subprocess.PIPE, close_fds=True, bufsize=-1) 363 c.stdin.close() 364 while 1: 365 status = c.poll() 366 if status is not None: 367 # Save the exit code, we still have to read from 368 # the pipes 369 return status, c.stdout, c.stderr 370 else: 371 c = popen2.Popen3(cmd, capturestderr=1, bufsize=-1) 372 c.tochild.close() 373 374 while 1: 375 status = c.poll() 376 if os.WIFEXITED(status): 377 # Save the exit code, we still have to read from 378 # the pipes 379 return os.WEXITSTATUS(status), c.fromchild, c.childerr
380
381 -def _build_error(status, stdout, stderr):
382 params = { 383 'status' : status, 384 'stdout' : stdout.read(), 385 'stderr' : stderr.read(), 386 } 387 return params
388 389
390 -class FileCopier:
391 """A class that copies a list of files/directories to the specified 392 destination 393 """
394 - def __init__(self, files, dest, quota=None):
395 self.files = files 396 self.dest = dest 397 self.quota = quota 398 self.current_quota = 0
399
400 - def copy(self):
401 return self._copy(self.files)
402
403 - def _copy(self, files):
404 assert(isinstance(files, list)) 405 for f in files: 406 try: 407 st = os.lstat(f) 408 except OSError: 409 # Ignore it 410 continue 411 412 st_mode = st[stat.ST_MODE] 413 414 if stat.S_ISLNK(st_mode): 415 self._copy_link(f, st) 416 elif stat.S_ISDIR(st_mode): 417 self._copy_dir(f, st) 418 elif stat.S_ISREG(st_mode): 419 self._copy_file(f, st)
420 421
422 - def _copy_file(self, f, st):
423 # Check quota first 424 file_size = st[stat.ST_SIZE] 425 self._check_quota(f, file_size) 426 self._create_dirs(f) 427 428 # Now copy the file 429 dest = self._build_dest(f) 430 shutil.copy2(f, dest) 431 self._copy_perms(dest, st) 432 # Update space usage 433 self._update_quota(f, file_size)
434 435
436 - def _check_quota(self, f, file_size):
437 if not self.quota: 438 return 439 # Quota enabled 440 if self.current_quota + file_size > self.quota: 441 raise QuotaExceeded(f)
442 443
444 - def _update_quota(self, f, file_size):
445 self.current_quota = self.current_quota + file_size
446 447
448 - def _create_dirs(self, f):
449 dirname = os.path.dirname(f) 450 self._copy_dir_modes(dirname, self.dest)
451 452
453 - def _copy_perms(self, dest, st):
454 os.chmod(dest, st[stat.ST_MODE]) 455 os.chown(dest, st[stat.ST_UID], st[stat.ST_GID]) 456 os.utime(dest, (st[stat.ST_ATIME], st[stat.ST_MTIME]))
457 458
459 - def _copy_dir(self, f, st):
460 files = list(map(lambda x, d=f: os.path.join(d, x), os.listdir(f))) 461 # Create this directory since it may be empty 462 self._copy_dir_modes(f, self.dest) 463 return self._copy(files)
464 465 477 478
479 - def _build_dest(self, f):
480 return os.path.normpath(self.dest + f)
481 482
483 - def _copy_dir_modes(self, srcdir, dest):
484 if not os.path.exists(dest): 485 os.makedirs(dest) 486 l = [] 487 srcdir = os.path.normpath(srcdir) 488 while 1: 489 h, t = os.path.split(srcdir) 490 if not t: 491 break 492 l.append(t) 493 srcdir = h 494 495 l.reverse() 496 dest_dir = dest 497 src_dir = os.sep 498 for d in l: 499 src_dir = os.path.join(src_dir, d) 500 src_st = os.lstat(src_dir) 501 dest_dir = os.path.join(dest_dir, d) 502 if not os.path.exists(dest_dir): 503 os.mkdir(dest_dir) 504 os.chmod(dest_dir, src_st[stat.ST_MODE]) 505 os.chown(dest_dir, src_st[stat.ST_UID], src_st[stat.ST_GID])
506
507 -class QuotaExceeded(Exception):
508 pass
509