├── lvm7.patch ├── lvm-master7.patch ├── lvm.patch ├── pxssh.patch ├── scsiutil7.patch ├── LVHDoISCSISR.patch ├── LVHDoISCSISR7.patch ├── README.md ├── RBDSR7.patch ├── install_rbdsr.py ├── pxssh.py ├── RBDSR.py ├── LICENSE └── pexpect.py /lvm7.patch: -------------------------------------------------------------------------------- 1 | --- lvm.conf 2016-05-27 13:33:00.000000000 +1000 2 | +++ /etc/lvm/lvm.conf 2016-05-27 12:38:04.000000000 +1000 3 | @@ -175,6 +175,7 @@ 4 | # 5 | # Example 6 | # types = [ "fd", 16 ] 7 | + types = [ "rbd", 64 ] 8 | # 9 | # This configuration option is advanced. 10 | # This configuration option does not have a default value defined. 11 | -------------------------------------------------------------------------------- /lvm-master7.patch: -------------------------------------------------------------------------------- 1 | --- a/lvm.conf 2016-05-30 00:24:25.000000000 +1000 2 | +++ b/lvm.conf 2016-05-30 00:24:09.000000000 +1000 3 | @@ -175,6 +175,7 @@ 4 | # 5 | # Example 6 | # types = [ "fd", 16 ] 7 | + types = [ "rbd", 1024 ] 8 | # 9 | # This configuration option is advanced. 10 | # This configuration option does not have a default value defined. 11 | -------------------------------------------------------------------------------- /lvm.patch: -------------------------------------------------------------------------------- 1 | --- a/lvm.conf 2015-11-16 12:04:53.000000000 +1100 2 | +++ b/lvm.conf 2015-11-16 12:05:25.000000000 +1100 3 | @@ -96,7 +96,7 @@ 4 | # List of pairs of additional acceptable block device types found 5 | # in /proc/devices with maximum (non-zero) number of partitions. 6 | # types = [ "fd", 16 ] 7 | - types= [ "nvme", 64, "mtip32xx", 64 ] 8 | + types= [ "nvme", 64, "mtip32xx", 64 , "rbd", 64 ] 9 | 10 | # If sysfs is mounted (2.6 kernels) restrict device scanning to 11 | # the block devices it believes are valid. 12 | -------------------------------------------------------------------------------- /pxssh.patch: -------------------------------------------------------------------------------- 1 | --- a/pxssh.py 2015-11-17 00:38:50.000000000 +1100 2 | +++ b/pxssh.py 2015-11-17 00:39:47.000000000 +1100 3 | @@ -132,6 +132,8 @@ 4 | # If latency is worse than these values then this will fail. 5 | # first two lines below are from "http://stackoverflow.com/questions/21055943/pxssh-connecting-to-an-ssh-proxy-timeout-exceeded-in-read-nonblocking" 6 | 7 | + self.sendline() 8 | + time.sleep(0.5) 9 | self.read_nonblocking(size=10000,timeout=1) # GAS: Clear out the cache before getting the prompt 10 | time.sleep(0.1) 11 | self.sendline() 12 | -------------------------------------------------------------------------------- /scsiutil7.patch: -------------------------------------------------------------------------------- 1 | --- a/scsiutil.py 2016-05-30 14:39:52.000000000 +1000 2 | +++ b/scsiutil.py 2016-05-30 14:41:08.000000000 +1000 3 | @@ -98,12 +98,14 @@ 4 | Raise: 5 | util.CommandException 6 | """ 7 | - 8 | - try: 9 | - stdout = util.pread2([SCSI_ID_BIN, '-g', '--device', path]) 10 | - except util.CommandException: # fallback call 11 | - dev = rawdev(path) 12 | - stdout = util.pread2([SCSI_ID_BIN, '-g', '-s', '/block/%s' % dev]) 13 | + if 'rbd' in rawdev(path): 14 | + stdout = util.pread2(['echo', path.split('-')[2]]) 15 | + else: 16 | + try: 17 | + stdout = util.pread2([SCSI_ID_BIN, '-g', '--device', path]) 18 | + except util.CommandException: # fallback call 19 | + dev = rawdev(path) 20 | + stdout = util.pread2([SCSI_ID_BIN, '-g', '-s', '/block/%s' % dev]) 21 | 22 | return SCSIid_sanitise(stdout[:-1]) 23 | 24 | -------------------------------------------------------------------------------- /LVHDoISCSISR.patch: -------------------------------------------------------------------------------- 1 | --- LVHDoISCSISR.py 2015-11-17 13:21:39.000000000 +1100 2 | +++ LVHDoISCSISR.py-new 2015-11-17 13:23:24.000000000 +1100 3 | @@ -18,7 +18,7 @@ 4 | # LVHDoISCSISR: LVHD over ISCSI software initiator SR driver 5 | # 6 | 7 | -import SR, VDI, LVHDSR, ISCSISR, SRCommand, util, scsiutil, lvutil 8 | +import SR, VDI, LVHDSR, RBDSR, ISCSISR, SRCommand, util, scsiutil, lvutil 9 | import statvfs, time 10 | import os, socket, sys, re 11 | import xs_errors 12 | @@ -77,7 +77,10 @@ 13 | # This is a probe call, generate a temp sr_uuid 14 | sr_uuid = util.gen_uuid() 15 | 16 | - driver = SR.driver('iscsi') 17 | + if self.original_srcmd.dconf.has_key('port') and '6789' in self.original_srcmd.dconf['port']: 18 | + driver = SR.driver('rbd') 19 | + else: 20 | + driver = SR.driver('iscsi') 21 | if self.original_srcmd.dconf.has_key('target'): 22 | self.original_srcmd.dconf['targetlist'] = self.original_srcmd.dconf['target'] 23 | iscsi = driver(self.original_srcmd, sr_uuid) 24 | -------------------------------------------------------------------------------- /LVHDoISCSISR7.patch: -------------------------------------------------------------------------------- 1 | --- a/LVHDoISCSISR.py 2016-05-30 14:48:25.000000000 +1000 2 | +++ b/LVHDoISCSISR.py 2016-05-30 14:48:36.000000000 +1000 3 | @@ -18,7 +18,7 @@ 4 | # LVHDoISCSISR: LVHD over ISCSI software initiator SR driver 5 | # 6 | 7 | -import SR, VDI, LVHDSR, BaseISCSI, SRCommand, util, scsiutil, lvutil 8 | +import SR, VDI, LVHDSR, RBDSR, BaseISCSI, SRCommand, util, scsiutil, lvutil 9 | import statvfs, time 10 | import os, socket, sys, re 11 | import xs_errors 12 | @@ -79,7 +79,11 @@ 13 | 14 | if self.original_srcmd.dconf.has_key('target'): 15 | self.original_srcmd.dconf['targetlist'] = self.original_srcmd.dconf['target'] 16 | - iscsi = BaseISCSI.BaseISCSISR(self.original_srcmd, sr_uuid) 17 | + 18 | + if self.original_srcmd.dconf.has_key('port') and '6789' in self.original_srcmd.dconf['port']: 19 | + iscsi = RBDSR.RBDSR(self.original_srcmd, sr_uuid) 20 | + else: 21 | + iscsi = BaseISCSI.BaseISCSISR(self.original_srcmd, sr_uuid) 22 | self.iscsiSRs = [] 23 | self.iscsiSRs.append(iscsi) 24 | 25 | @@ -144,7 +148,10 @@ 26 | srcmd_copy.dconf['targetIQN'] = iqn 27 | srcmd_copy.dconf['multiSession'] = IQNstring 28 | util.SMlog("Setting targetlist: %s" % srcmd_copy.dconf['targetlist']) 29 | - self.iscsiSRs.append(BaseISCSI.BaseISCSISR(srcmd_copy, sr_uuid)) 30 | + if self.original_srcmd.dconf.has_key('port') and '6789' in self.original_srcmd.dconf['port']: 31 | + self.iscsiSRs.append(RBDSR.RBDSR(srcmd_copy, sr_uuid)) 32 | + else: 33 | + self.iscsiSRs.append(BaseISCSI.BaseISCSISR(srcmd_copy, sr_uuid)) 34 | pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref) 35 | if pbd <> None and not self.dconf.has_key('multiSession'): 36 | dconf = self.session.xenapi.PBD.get_device_config(pbd) 37 | @@ -557,7 +564,10 @@ 38 | raise xs_errors.XenError('InvalidDev') 39 | self._pathrefresh(LVHDoISCSISR) 40 | out = LVHDSR.LVHDSR.probe(self) 41 | - self.iscsi.detach(self.uuid) 42 | + if self.original_srcmd.dconf.has_key('port') and '6789' in self.original_srcmd.dconf['port']: 43 | + util.SMlog('This is RBD SR, we don\'t attach target during probing so no detach required') 44 | + else: 45 | + self.iscsi.detach(self.uuid) 46 | return out 47 | 48 | def vdi(self, uuid): 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RBDSR - CEPH plugin for XenServer 6.5 and 7(testing) 2 | This plugin automates creation and attaching RBD objects to XenServer as an SR. It creates LVM Volume group on top of the attached RBD object and then uses LVs as LVHD VDIs. 3 | 4 | XenServer demo RBD SR, implemented as an extension of the exsiting iSCSI(LVHDoISCSISR) SR. It doesn't mean that it uses iSCSI per se, but rather forks iSCSI storage plugin operation path, to take advantage of creating SR from XenCenter. 5 | 6 | In particular, after installing plugin, creating iSCSI SR with port 6789 will redirect code path to the RBDSR.py plugin and allow user to attach RBD object to XenServer and create LVM based SR on top of that object. While allowing RBD SR creation using XenCenter, this method doesn't impact LVHDoISCSISR.py in any other way, leaving iSCSI functionality otherwise unchanged. 7 | 8 | This plugin takes adventage of the following changes in XenServer 6.5. In this version of XenServer, an rbd module has been enbled on the kernel. As a result, RBD blocks can be attached to Dom0 with sysfs command: 9 | 10 | ```echo "$mons name=$name,secret=$secret $rbddev" > /sys/bus/rbd/add``` 11 | 12 | like the one described here: https://github.com/ceph/ceph-docker/blob/master/examples/coreos/rbdmap/rbdmap#L51 13 | 14 | Once the RBD block device is mapped, LVM SR can be created on top of it and shared across a XenServer pool. 15 | 16 | ## Install 17 | Download latest version of the pluging to each host: `wget https://github.com/mstarikov/rbdsr/archive/master.zip` 18 | 19 | Unzip the archive(wget on xenserver might strip the extension): `unzip master` 20 | 21 | Now you can install this demo script automatically using `rbd-install.py`. 22 | Run `python ./rbd-install.py enable` on each host to patch all required files and copy RBDSR.py to `/opt/xensource/sm`. 23 | 24 | rbd-install.py will automatically detect the version of the XenServer and apply corresponding patches to XenServer 6.5 or 7.0. 25 | 26 | If for some reason you are having problems with the install script, please [let me know](mailto:mr.mark.starikov@gmail.com) first and then perform following changes on each host in the pool to enable RBD SR: 27 | ``` 28 | FOR ALL VERSIONS: 29 | # echo modprobe rbd >> /etc/rc.modules 30 | # chmod +x /etc/rc.modules 31 | # cp RBDSR.py /opt/xensource/sm/ 32 | 33 | FOR XENSERVER 6.5: 34 | # patch /usr/lib/python2.4/site-packages/pxssh.py pxssh.patch 35 | # patch /etc/lvm.conf lvm.patch 36 | # patch /opt/xensource/sm/LVHDoISCSISR.py LVHDoISCSISR.patch 37 | 38 | FOR XENSERVER 7.0: 39 | # cp pxssh.py /usr/lib/python2.7/site-packages/ 40 | # cp pexpect.py /usr/lib/python2.7/site-package/ 41 | # patch /etc/lvm/lvm.conf lvm7.patch 42 | # patch /etc/lvm/master/lvm.conf lvm-master7.patch 43 | # patch /opt/xensource/sm/LVHDoISCSISR.py LVHDoISCSISR7.patch 44 | # patch /opt/xensource/sm/RBDSR.py RBDSR7.patch 45 | # patch /opt/xensource/sm/scsiutil.py scsiutil7.patch 46 | ``` 47 | ## Usage 48 | 49 | RBDSR.py extends iSCSI SR(lvmoiscsi) functionality to attach rbd images to the Dom0 and place LVHDs(VHD inside of LVM volume) VDIs on top of that block device. 50 | 51 | Minimal requirements to create RBDSR are: 52 | * target - IP address or hostname of the ceph monitor 53 | * targetIQN - RBD pool name 54 | * SCSIid - RBD image name 55 | * chapuser - username of sudoer on ceph monitor 56 | * chappassword - password of the ceph user 57 | * port - monitor port number. currently only 6789 will divert LVHDoISCSISR into RBDSR 58 | 59 | ###### Examples 60 | To create SR you can use ragular sr-create syntax: 61 | ``` 62 | # xe sr-create type=lvmoiscsi name-label=RADOS-SR shared=true device-config:target= device-config:port=6789 device-config:targetIQN= device-config:SCSIid= device-config:chapuser= device-config:chappassword= 63 | ``` 64 | Or via XenCenter: 65 | 66 | ![XenCenter Create new RBD SR with caption](https://cloud.githubusercontent.com/assets/15868352/11228256/83176bc8-8ddf-11e5-9394-3a533f1ccf1b.png) 67 | -------------------------------------------------------------------------------- /RBDSR7.patch: -------------------------------------------------------------------------------- 1 | --- a/RBDSR.py 2016-05-30 14:36:09.000000000 +1000 2 | +++ b/RBDSR.py 2016-05-30 14:38:14.000000000 +1000 3 | @@ -1,12 +1,12 @@ 4 | #!/usr/bin/python 5 | -# Following ISCSISR.py as an example, RBDSR provides LVHD SR over rbd block device. 6 | +# Following BaseISCSI.py as an example, RBDSR provides LVHD SR over rbd block device. 7 | # created by Mark Starikov(mr.mark.starikov@gmail.com) 8 | 9 | -import ISCSISR, VDI, scsiutil, SR, SRCommand, util, xs_errors, xmlrpclib, LUNperVDI 10 | +import BaseISCSI, VDI, scsiutil, SR, SRCommand, util, xs_errors, xmlrpclib, LUNperVDI 11 | import socket, os, copy, sys, pxssh 12 | from xml.dom.minidom import parseString 13 | 14 | -''' start of modified parameters, pretty much direct copy from ISCSISR.py ''' 15 | +''' start of modified parameters, pretty much direct copy from BaseISCSI.py ''' 16 | CAPABILITIES = ["SR_PROBE","VDI_CREATE","VDI_DELETE","VDI_ATTACH", 17 | "VDI_DETACH", "VDI_INTRODUCE"] 18 | 19 | @@ -42,9 +42,9 @@ 20 | ISCSI_PROCNAME = "iscsi_tcp" 21 | # changing default port to monitor port 22 | DEFAULT_PORT = 6789 23 | -''' end of modified definitions of paramters like in ISCSISR.py ''' 24 | +''' end of modified definitions of paramters like in BaseISCSI.py ''' 25 | 26 | -class RBDSR(ISCSISR.ISCSISR): 27 | +class RBDSR(BaseISCSI.BaseISCSISR): 28 | def handles(type): 29 | if type == "rbd": 30 | return True 31 | @@ -108,7 +108,7 @@ 32 | pool = "*" 33 | map.append((real_address+":"+self.dconf['port'],"0",pool)) 34 | util.SMlog(map) 35 | - # Recycling code here and calling print_entries from ISCSISR.py 36 | + # Recycling code here and calling print_entries from BaseISCSI.py 37 | super(RBDSR, self).print_entries(map) 38 | # User hasn't selected targetIQN yet, so throwing xs_error like in its iSCSI counterpart 39 | raise xs_errors.XenError('ConfigTargetIQNMissing') 40 | @@ -221,6 +221,10 @@ 41 | 42 | 43 | def attach(self, sr_uuid): 44 | + # LVHDoISCSI likes to call attach to get target information. if we have block device already attached, we don't need to do it again 45 | + if self.dconf.has_key('SCSIid') and self.dconf['SCSIid'] and os.path.exists('/dev/rbd%s' % self._getRBD_index(self.dconf['SCSIid'])): 46 | + self.attached = True 47 | + return 48 | ### Getting MON list using admin key above 49 | ceph_mon_list_xml = self._getCEPH_response('sudo ceph mon_status -f xml') 50 | ceph_mon_list = self._formatMON_list(ceph_mon_list_xml) 51 | @@ -262,7 +266,7 @@ 52 | os.symlink('../../../rbd%s' % rbd_image_index, '%s/rbd%s' % (rbd_scsi_path,rbd_image_index)) 53 | self.attached = True 54 | except IOError, e: 55 | - util.SMlog('the error is %s' % e) 56 | + util.SMlog('Attach thrown exception and the error is %s' % e) 57 | self.attached = False 58 | else: 59 | '''in iSCSI sr we need to attach target to interrogate LUN for size, scsi_id etc etc. 60 | @@ -282,15 +286,21 @@ 61 | if os.path.exists(rbd_scsi_path): 62 | self._cleanCEPH_folder(rbd_scsi_path) 63 | if os.path.exists('/dev/rbd%s' % rbd_image_index): 64 | - rbd_remove = open('/sys/bus/rbd/remove','w') 65 | - rbd_remove.write(rbd_image_index) 66 | - rbd_remove.close() 67 | - self.attached = False 68 | + try: 69 | + util.time.sleep(MAX_TIMEOUT) 70 | + with open('/sys/bus/rbd/remove','w') as rem: 71 | + util.SMlog("Writing %s into rbd/remove" % rbd_image_index) 72 | + rem.write(rbd_image_index) 73 | + self.attached = False 74 | + except IOError, e: 75 | + util.SMlog('Detach thrown exception and the error is %s' % e) 76 | + self.attached = True 77 | 78 | 79 | def refresh(self): 80 | - # Unlike iSCSI SR we don't need to refresh paths or rescan sessions 81 | - pass 82 | + # Unlike iSCSI SR we don't need to refresh paths or rescan sessions, 83 | + # but if disk hasn't been attached already(like when creating SR) we can do it now 84 | + self.attach('temp_mount') 85 | 86 | def print_LUNs(self): 87 | self.LUNs = {} 88 | -------------------------------------------------------------------------------- /install_rbdsr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ''' Install script which will do following: 3 | 1. add modprobe rbd to rc.modules 4 | 2. patch lvm.conf to detect volumes on rbd block devices 5 | 3. patch LVHDoISCSISR.py to redirect port 6789 requests to RBDSR.py 6 | 4. patch pxssh.py to allow remote commands 7 | 5. copy RBDSR.py to /opt/xensource/sm''' 8 | 9 | import os, sys, shutil, subprocess 10 | 11 | def usage(): 12 | print "Usage: %s enable" % (sys.argv[0]) 13 | 14 | if __name__ == "__main__": 15 | if len(sys.argv) != 2: 16 | usage() 17 | sys.exit(1) 18 | version = '6' 19 | if 'PRODUCT_VERSION=\'7.0.0\'' in open('/etc/xensource-inventory').read(): 20 | version = '7' 21 | print ('########################################\nChecking if all files are in place:\n') 22 | if os.path.exists('RBDSR.py'): 23 | print('#### found RBDSR.py ####') 24 | else: 25 | print('Couldn\'t find RBDSR.py here - download package again') 26 | sys.exit(1) 27 | 28 | if os.path.exists('LVHDoISCSISR.patch'): 29 | print('#### LVHDoISCSISR.patch is here too ####') 30 | else: 31 | print('Couldn\'t find LVHDoISCSISR.py here - download package again') 32 | sys.exit(1) 33 | 34 | if os.path.exists('lvm.patch'): 35 | print('#### and lvm.patch is here as well ####') 36 | else: 37 | print('Couldn\'t find lvm.patch here - download package again') 38 | sys.exit(1) 39 | 40 | if os.path.exists('lvm-master7.patch'): 41 | print('#### and lvm-master7.patch is here as well ####') 42 | else: 43 | print('Couldn\'t find lvm-master7.patch here - download package again') 44 | sys.exit(1) 45 | 46 | 47 | if os.path.exists('lvm7.patch'): 48 | print('#### and lvm7.patch is here as well ####') 49 | else: 50 | print('Couldn\'t find lvm7.patch here - download package again') 51 | sys.exit(1) 52 | 53 | if os.path.exists('pxssh.patch'): 54 | print('#### and pxssh.patch is here as well ####') 55 | else: 56 | print('Couldn\'t find pxssh.patch here - download package again') 57 | sys.exit(1) 58 | 59 | if os.path.exists('RBDSR7.patch'): 60 | print('#### and RBDSR7.patch is here as well ####') 61 | else: 62 | print('Couldn\'t find RBDSR7.patch here - download package again') 63 | sys.exit(1) 64 | 65 | if os.path.exists('LVHDoISCSISR.patch'): 66 | print('#### and LVHDoSCSISR7.patch is here as well ####') 67 | else: 68 | print('Couldn\'t find LVHDoISCSISR7.patch here - download package again') 69 | sys.exit(1) 70 | print('\n########################################\n\nWe have all files we need, enabling RBDSR:') 71 | print('Enabling rbd driver on boot via rc.modules(ref https://www.centos.org/docs/)') 72 | '''TODO: should check if rbd is already in the rc.modules before writing it''' 73 | 74 | try: 75 | rcfile = open('/etc/rc.modules','a') 76 | rcfile.write('\nmodprobe rbd\n') 77 | rcfile.close() 78 | except IOError, e: 79 | print 'Was unable to add rbd to rc.modules. Error: %s' % e 80 | sys.exit(1) 81 | 82 | try: 83 | os.chmod('/etc/rc.modules', 0744) 84 | except OSError, e: 85 | print 'Couldn\'t set execute permissions to rc.modules. Error: %s [errno=%s]' % (e.args) 86 | sys.exit(1) 87 | current_path = os.path.dirname(os.path.realpath(__file__)) 88 | 89 | if version == '7': 90 | os.chdir('/usr/lib/python2.7/site-packages/') 91 | shutil.copy('%s/pxssh.py' % current_path, 'pxssh.py') 92 | shutil.copy('%s/pexpect.py' % current_path, 'pexpect.py') 93 | else: 94 | os.chdir('/usr/lib/python2.4/site-packages/') 95 | shutil.copy('pxssh.py','pxssh.py-oring') 96 | try: 97 | subprocess.call(["patch", "pxssh.py", "%s/pxssh.patch" % current_path]) 98 | print('....\npxssh.py is patched') 99 | except OSError, e: 100 | print 'Couldn\'t patch pxssh.py. Error: %s [errno=%s]' % (e.args) 101 | sys.exit(1) 102 | 103 | os.chdir('/etc/lvm/') 104 | shutil.copy('lvm.conf','lvm.conf-oring') 105 | try: 106 | if version == '7': 107 | subprocess.call(["patch", "lvm.conf", "%s/lvm7.patch" % current_path]) 108 | os.chdir('/etc/lvm/master') 109 | shutil.copy('lvm.conf','lvm.conf-oring') 110 | subprocess.call(["patch", "lvm.conf", "%s/lvm-master7.patch" % current_path]) 111 | else: 112 | subprocess.call(["patch", "lvm.conf", "%s/lvm.patch" % current_path]) 113 | print('....\nlvm.conf is patched') 114 | except OSError, e: 115 | print 'Couldn\'t patch lvm.conf. Error: %s [errno=%s]' % (e.args) 116 | sys.exit(1) 117 | 118 | os.chdir('/opt/xensource/sm') 119 | shutil.copy('LVHDoISCSISR.py','LVHDoISCSISR.py-orig') 120 | try: 121 | if version == '7': 122 | subprocess.call(["patch", "LVHDoISCSISR.py", "%s/LVHDoISCSISR7.patch" % current_path]) 123 | else: 124 | subprocess.call(["patch", "LVHDoISCSISR.py", "%s/LVHDoISCSISR.patch" % current_path]) 125 | print('....\nLVHDoISCSISR.py is patched') 126 | except OSError, e: 127 | print 'Couldn\'t patch LVHDoISCSISR.py. Error: %s [errno=%s]' % (e.args) 128 | sys.exit(1) 129 | 130 | try: 131 | shutil.copyfile(current_path + '/RBDSR.py', 'RBDSR.py') 132 | if version == '7': 133 | subprocess.call(["patch", "RBDSR.py", "%s/RBDSR7.patch" % current_path]) 134 | except OSError, e: 135 | print 'Couldn\'t patch RBDSR.py. Error: %s [errno=%s]' % (e.args) 136 | sys.exit(1) 137 | print('....\nRBDSR.py has been copied to /opt/xensource/sm') 138 | 139 | try: 140 | if version == '7': 141 | subprocess.call(["patch", "scsiutil.py", "%s/scsiutil7.patch" % current_path]) 142 | except OSError, e: 143 | print 'Couldn\'t patch scsiutil.py. Error: %s [errno=%s]' % (e.args) 144 | sys.exit(1) 145 | 146 | sys.exit(0) 147 | -------------------------------------------------------------------------------- /pxssh.py: -------------------------------------------------------------------------------- 1 | """This class extends pexpect.spawn to specialize setting up SSH connections. 2 | This adds methods for login, logout, and expecting the shell prompt. 3 | 4 | $Id: pxssh.py 487 2007-08-29 22:33:29Z noah $ 5 | """ 6 | 7 | from pexpect import * 8 | import pexpect 9 | import time 10 | 11 | __all__ = ['ExceptionPxssh', 'pxssh'] 12 | 13 | # Exception classes used by this module. 14 | class ExceptionPxssh(ExceptionPexpect): 15 | """Raised for pxssh exceptions. 16 | """ 17 | 18 | class pxssh (spawn): 19 | 20 | """This class extends pexpect.spawn to specialize setting up SSH 21 | connections. This adds methods for login, logout, and expecting the shell 22 | prompt. It does various tricky things to handle many situations in the SSH 23 | login process. For example, if the session is your first login, then pxssh 24 | automatically accepts the remote certificate; or if you have public key 25 | authentication setup then pxssh won't wait for the password prompt. 26 | 27 | pxssh uses the shell prompt to synchronize output from the remote host. In 28 | order to make this more robust it sets the shell prompt to something more 29 | unique than just $ or #. This should work on most Borne/Bash or Csh style 30 | shells. 31 | 32 | Example that runs a few commands on a remote server and prints the result:: 33 | 34 | import pxssh 35 | import getpass 36 | try: 37 | s = pxssh.pxssh() 38 | hostname = raw_input('hostname: ') 39 | username = raw_input('username: ') 40 | password = getpass.getpass('password: ') 41 | s.login (hostname, username, password) 42 | s.sendline ('uptime') # run a command 43 | s.prompt() # match the prompt 44 | print s.before # print everything before the prompt. 45 | s.sendline ('ls -l') 46 | s.prompt() 47 | print s.before 48 | s.sendline ('df') 49 | s.prompt() 50 | print s.before 51 | s.logout() 52 | except pxssh.ExceptionPxssh, e: 53 | print "pxssh failed on login." 54 | print str(e) 55 | 56 | Note that if you have ssh-agent running while doing development with pxssh 57 | then this can lead to a lot of confusion. Many X display managers (xdm, 58 | gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI 59 | dialog box popup asking for a password during development. You should turn 60 | off any key agents during testing. The 'force_password' attribute will turn 61 | off public key authentication. This will only work if the remote SSH server 62 | is configured to allow password logins. Example of using 'force_password' 63 | attribute:: 64 | 65 | s = pxssh.pxssh() 66 | s.force_password = True 67 | hostname = raw_input('hostname: ') 68 | username = raw_input('username: ') 69 | password = getpass.getpass('password: ') 70 | s.login (hostname, username, password) 71 | """ 72 | 73 | def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None): 74 | spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env) 75 | 76 | self.name = '' 77 | 78 | #SUBTLE HACK ALERT! Note that the command to set the prompt uses a 79 | #slightly different string than the regular expression to match it. This 80 | #is because when you set the prompt the command will echo back, but we 81 | #don't want to match the echoed command. So if we make the set command 82 | #slightly different than the regex we eliminate the problem. To make the 83 | #set command different we add a backslash in front of $. The $ doesn't 84 | #need to be escaped, but it doesn't hurt and serves to make the set 85 | #prompt command different than the regex. 86 | 87 | # used to match the command-line prompt 88 | self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] " 89 | self.PROMPT = self.UNIQUE_PROMPT 90 | 91 | # used to set shell command-line prompt to UNIQUE_PROMPT. 92 | self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '" 93 | self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '" 94 | self.SSH_OPTS = "-o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" 95 | # Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from 96 | # displaying a GUI password dialog. I have not figured out how to 97 | # disable only SSH_ASKPASS without also disabling X11 forwarding. 98 | # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying! 99 | #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" 100 | self.force_password = False 101 | self.auto_prompt_reset = True 102 | 103 | def levenshtein_distance(self, a,b): 104 | 105 | """This calculates the Levenshtein distance between a and b. 106 | """ 107 | 108 | n, m = len(a), len(b) 109 | if n > m: 110 | a,b = b,a 111 | n,m = m,n 112 | current = range(n+1) 113 | for i in range(1,m+1): 114 | previous, current = current, [i]+[0]*n 115 | for j in range(1,n+1): 116 | add, delete = previous[j]+1, current[j-1]+1 117 | change = previous[j-1] 118 | if a[j-1] != b[i-1]: 119 | change = change + 1 120 | current[j] = min(add, delete, change) 121 | return current[n] 122 | 123 | def synch_original_prompt (self): 124 | 125 | """This attempts to find the prompt. Basically, press enter and record 126 | the response; press enter again and record the response; if the two 127 | responses are similar then assume we are at the original prompt. """ 128 | 129 | # All of these timing pace values are magic. 130 | # I came up with these based on what seemed reliable for 131 | # connecting to a heavily loaded machine I have. 132 | # If latency is worse than these values then this will fail. 133 | 134 | self.sendline() 135 | self.read_nonblocking(size=10000,timeout=1) # GAS: Clear out the cache before getting the prompt 136 | time.sleep(0.1) 137 | self.sendline() 138 | time.sleep(0.5) 139 | x = self.read_nonblocking(size=1000,timeout=1) 140 | time.sleep(0.1) 141 | self.sendline() 142 | time.sleep(0.5) 143 | a = self.read_nonblocking(size=1000,timeout=1) 144 | time.sleep(0.1) 145 | self.sendline() 146 | time.sleep(0.5) 147 | b = self.read_nonblocking(size=1000,timeout=1) 148 | ld = self.levenshtein_distance(a,b) 149 | len_a = len(a) 150 | if len_a == 0: 151 | return False 152 | if float(ld)/len_a < 0.4: 153 | return True 154 | return False 155 | 156 | ### TODO: This is getting messy and I'm pretty sure this isn't perfect. 157 | ### TODO: I need to draw a flow chart for this. 158 | def login (self,server,username,password='',terminal_type='ansi',original_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True): 159 | 160 | """This logs the user into the given server. It uses the 161 | 'original_prompt' to try to find the prompt right after login. When it 162 | finds the prompt it immediately tries to reset the prompt to something 163 | more easily matched. The default 'original_prompt' is very optimistic 164 | and is easily fooled. It's more reliable to try to match the original 165 | prompt as exactly as possible to prevent false matches by server 166 | strings such as the "Message Of The Day". On many systems you can 167 | disable the MOTD on the remote server by creating a zero-length file 168 | called "~/.hushlogin" on the remote server. If a prompt cannot be found 169 | then this will not necessarily cause the login to fail. In the case of 170 | a timeout when looking for the prompt we assume that the original 171 | prompt was so weird that we could not match it, so we use a few tricks 172 | to guess when we have reached the prompt. Then we hope for the best and 173 | blindly try to reset the prompt to something more unique. If that fails 174 | then login() raises an ExceptionPxssh exception. 175 | 176 | In some situations it is not possible or desirable to reset the 177 | original prompt. In this case, set 'auto_prompt_reset' to False to 178 | inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh 179 | uses a unique prompt in the prompt() method. If the original prompt is 180 | not reset then this will disable the prompt() method unless you 181 | manually set the PROMPT attribute. """ 182 | 183 | ssh_options = '-q' 184 | if self.force_password: 185 | ssh_options = ssh_options + ' ' + self.SSH_OPTS 186 | if port is not None: 187 | ssh_options = ssh_options + ' -p %s'%(str(port)) 188 | cmd = "ssh %s -l %s %s" % (ssh_options, username, server) 189 | 190 | # This does not distinguish between a remote server 'password' prompt 191 | # and a local ssh 'passphrase' prompt (for unlocking a private key). 192 | spawn._spawn(self, cmd) 193 | i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host"], timeout=login_timeout) 194 | 195 | # First phase 196 | if i==0: 197 | # New certificate -- always accept it. 198 | # This is what you get if SSH does not have the remote host's 199 | # public key stored in the 'known_hosts' cache. 200 | self.sendline("yes") 201 | i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) 202 | if i==2: # password or passphrase 203 | self.sendline(password) 204 | i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) 205 | if i==4: 206 | self.sendline(terminal_type) 207 | i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) 208 | 209 | # Second phase 210 | if i==0: 211 | # This is weird. This should not happen twice in a row. 212 | self.close() 213 | raise ExceptionPxssh ('Weird error. Got "are you sure" prompt twice.') 214 | elif i==1: # can occur if you have a public key pair set to authenticate. 215 | ### TODO: May NOT be OK if expect() got tricked and matched a false prompt. 216 | pass 217 | elif i==2: # password prompt again 218 | # For incorrect passwords, some ssh servers will 219 | # ask for the password again, others return 'denied' right away. 220 | # If we get the password prompt again then this means 221 | # we didn't get the password right the first time. 222 | self.close() 223 | raise ExceptionPxssh ('password refused') 224 | elif i==3: # permission denied -- password was bad. 225 | self.close() 226 | raise ExceptionPxssh ('permission denied') 227 | elif i==4: # terminal type again? WTF? 228 | self.close() 229 | raise ExceptionPxssh ('Weird error. Got "terminal type" prompt twice.') 230 | elif i==5: # Timeout 231 | #This is tricky... I presume that we are at the command-line prompt. 232 | #It may be that the shell prompt was so weird that we couldn't match 233 | #it. Or it may be that we couldn't log in for some other reason. I 234 | #can't be sure, but it's safe to guess that we did login because if 235 | #I presume wrong and we are not logged in then this should be caught 236 | #later when I try to set the shell prompt. 237 | pass 238 | elif i==6: # Connection closed by remote host 239 | self.close() 240 | raise ExceptionPxssh ('connection closed') 241 | else: # Unexpected 242 | self.close() 243 | raise ExceptionPxssh ('unexpected login response') 244 | if not self.synch_original_prompt(): 245 | self.close() 246 | raise ExceptionPxssh ('could not synchronize with original prompt') 247 | # We appear to be in. 248 | # set shell prompt to something unique. 249 | if auto_prompt_reset: 250 | if not self.set_unique_prompt(): 251 | self.close() 252 | raise ExceptionPxssh ('could not set shell prompt\n'+self.before) 253 | return True 254 | 255 | def logout (self): 256 | 257 | """This sends exit to the remote shell. If there are stopped jobs then 258 | this automatically sends exit twice. """ 259 | 260 | self.sendline("exit") 261 | index = self.expect([EOF, "(?i)there are stopped jobs"]) 262 | if index==1: 263 | self.sendline("exit") 264 | self.expect(EOF) 265 | self.close() 266 | 267 | def prompt (self, timeout=20): 268 | 269 | """This matches the shell prompt. This is little more than a short-cut 270 | to the expect() method. This returns True if the shell prompt was 271 | matched. This returns False if there was a timeout. Note that if you 272 | called login() with auto_prompt_reset set to False then you should have 273 | manually set the PROMPT attribute to a regex pattern for matching the 274 | prompt. """ 275 | 276 | i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout) 277 | if i==1: 278 | return False 279 | return True 280 | 281 | def set_unique_prompt (self): 282 | 283 | """This sets the remote prompt to something more unique than # or $. 284 | This makes it easier for the prompt() method to match the shell prompt 285 | unambiguously. This method is called automatically by the login() 286 | method, but you may want to call it manually if you somehow reset the 287 | shell prompt. For example, if you 'su' to a different user then you 288 | will need to manually reset the prompt. This sends shell commands to 289 | the remote host to set the prompt, so this assumes the remote host is 290 | ready to receive commands. 291 | 292 | Alternatively, you may use your own prompt pattern. Just set the PROMPT 293 | attribute to a regular expression that matches it. In this case you 294 | should call login() with auto_prompt_reset=False; then set the PROMPT 295 | attribute. After that the prompt() method will try to match your prompt 296 | pattern.""" 297 | 298 | self.sendline ("unset PROMPT_COMMAND") 299 | self.sendline (self.PROMPT_SET_SH) # sh-style 300 | i = self.expect ([TIMEOUT, self.PROMPT], timeout=10) 301 | if i == 0: # csh-style 302 | self.sendline (self.PROMPT_SET_CSH) 303 | i = self.expect ([TIMEOUT, self.PROMPT], timeout=10) 304 | if i == 0: 305 | return False 306 | return True 307 | 308 | # vi:ts=4:sw=4:expandtab:ft=python: 309 | -------------------------------------------------------------------------------- /RBDSR.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Following ISCSISR.py as an example, RBDSR provides LVHD SR over rbd block device. 3 | # created by Mark Starikov(mr.mark.starikov@gmail.com) 4 | 5 | import ISCSISR, VDI, scsiutil, SR, SRCommand, util, xs_errors, xmlrpclib, LUNperVDI 6 | import socket, os, copy, sys, pxssh 7 | from xml.dom.minidom import parseString 8 | 9 | ''' start of modified parameters, pretty much direct copy from ISCSISR.py ''' 10 | CAPABILITIES = ["SR_PROBE","VDI_CREATE","VDI_DELETE","VDI_ATTACH", 11 | "VDI_DETACH", "VDI_INTRODUCE"] 12 | 13 | CONFIGURATION = [ [ 'SCSIid', 'The rbd image name' ], \ 14 | [ 'target', 'IP address or hostname of the ceph monitor' ], \ 15 | [ 'targetIQN', 'The rbd pool name' ], \ 16 | [ 'chapuser', 'The ssh username' ], \ 17 | [ 'chappassword', 'The ssh password' ], \ 18 | [ 'incoming_chapuser', 'The ceph admin user' ], \ 19 | [ 'incoming_chappassword', 'The ceph admin password' ], \ 20 | [ 'port', 'The monitor port number (default 6789) ' ], \ 21 | [ 'multihomed', 'Enable multi-homing to this target, true or false (optional, defaults to same value as host.other_config:multipathing)' ], 22 | [ 'force_tapdisk', 'Force use of tapdisk, true or false (optional, defaults to false)'], 23 | ] 24 | 25 | DRIVER_INFO = { 26 | 'name': 'RBD', 27 | 'description': 'SR plugin which creates LVHDs on top of RBD object.', 28 | 'vendor': 'Mark Starikov', 29 | 'copyright': '(C) 2016 Mark Starikov', 30 | 'driver_version': '1.0', 31 | 'required_api_version': '1.0', 32 | 'capabilities': CAPABILITIES, 33 | 'configuration': CONFIGURATION 34 | } 35 | ''' Should remove the iSCSI defaults, but need to test if they would break creating SR from XC''' 36 | # 2^16 Max port number value 37 | INITIATORNAME_FILE = '/etc/iscsi/initiatorname.iscsi' 38 | SECTOR_SHIFT = 9 39 | MAXPORT = 65535 40 | MAX_TIMEOUT = 15 41 | MAX_LUNID_TIMEOUT = 60 42 | ISCSI_PROCNAME = "iscsi_tcp" 43 | # changing default port to monitor port 44 | DEFAULT_PORT = 6789 45 | ''' end of modified definitions of paramters like in ISCSISR.py ''' 46 | 47 | class RBDSR(ISCSISR.ISCSISR): 48 | def handles(type): 49 | if type == "rbd": 50 | return True 51 | return False 52 | handles = staticmethod(handles) 53 | 54 | def load(self, sr_uuid): 55 | ''' Some repetition since LVHDoISCSI has pretty much everything inside of load method''' 56 | # Check if minimal amount of parameters(i.e. IP address of monitor) is passed to the call 57 | if not self.dconf.has_key('target') or not self.dconf['target']: 58 | raise xs_errors.XenError('ConfigTargetMissing') 59 | 60 | self.path = '' 61 | real_address = '' 62 | try: 63 | # For monitors we only need one address, since we get accurate map from the ceph later on in attach. 64 | target_string = self.dconf['target'].split(',') 65 | real_address = socket.gethostbyname(target_string[0]) 66 | util.SMlog('successfully resolved address to %s' % real_address) 67 | except: 68 | raise xs_errors.XenError('DNSError') 69 | 70 | ''' During XC SR creation, dialog first dicovers IQN and only then LUN. 71 | So if we have targetIQN in device-config but no SCSIid, means we discovering LUN/RBD image here ''' 72 | if self.dconf.has_key('targetIQN') and not self.dconf.has_key('SCSIid'): 73 | pool_name = self.dconf['targetIQN'] 74 | 75 | ### Getting RBD image corresponding to RBD pool 76 | block_list = self._getCEPH_response('sudo rbd -p %s ls' % pool_name) 77 | rbd_image_list = self._formatRBD_image(pool_name, block_list) 78 | ''' We don't attach rbd during discovery of the images, 79 | but parent class(LVHDoISCSI) needs 'attached' flag to print LUNs''' 80 | self.attached = True 81 | 82 | ''' If we have SCSIid, means we have discovered targetIQN already and ready to attach rbd block''' 83 | elif self.dconf.has_key('SCSIid'): 84 | self.attached = os.path.exists('/dev/disk/by-scsid/%s' % self.dconf['SCSIid']) 85 | self.path = '/dev/disk/by-id/scsi-%s' % self.dconf['SCSIid'] 86 | if os.path.exists('/var/lock/sm/%s/sr' % sr_uuid) and not self.attached: 87 | self.attach(sr_uuid) 88 | 89 | ''' This is a bit backwards, but based on the previous two if/elif statements, here we don't have 90 | either targetIQN nor SCSIid, which means we are at the begining of the XC iSCSI SR create dialog 91 | and need to discover RBD pools from the monitors.''' 92 | else: 93 | ### Getting RBD pool using ssh user and password 94 | rbd_pool_string = self._getCEPH_response('sudo ceph osd lspools') 95 | if 'fault' in rbd_pool_string or not rbd_pool_string: 96 | raise xs_errors.XenError('ISCSILogin') 97 | else: 98 | self._cleanCEPH_folder('/var/lib/rbd') 99 | rbd_pool_list = self._formatRBD_pool(rbd_pool_string) 100 | 101 | '''Discovered list should look like this: (':', '', '')''' 102 | map = [] 103 | for pool in rbd_pool_list.split('\n'): 104 | ''' With new-line-split we end up with last pool entry empty. 105 | This saves us efforts to append "*" record, which actually useless in this case. 106 | Should really remove it -> TODO''' 107 | if not pool: 108 | pool = "*" 109 | map.append((real_address+":"+self.dconf['port'],"0",pool)) 110 | util.SMlog(map) 111 | # Recycling code here and calling print_entries from ISCSISR.py 112 | super(RBDSR, self).print_entries(map) 113 | # User hasn't selected targetIQN yet, so throwing xs_error like in its iSCSI counterpart 114 | raise xs_errors.XenError('ConfigTargetIQNMissing') 115 | 116 | self.targetIQN = unicode(self.dconf['targetIQN']).encode('utf-8') 117 | 118 | 119 | def _formatCEPH_key(self, auth_key_string): 120 | if not os.path.exists('/etc/rbd'): 121 | os.makedirs('/etc/rbd') 122 | a = open('/etc/rbd/auth', 'w') 123 | for words in auth_key_string: 124 | if 'key: ' in words: 125 | rbd_admin_password = words.split('key: ')[1].rstrip() 126 | a.write(rbd_admin_password) 127 | a.close() 128 | return(rbd_admin_password) 129 | 130 | 131 | def _formatMON_list(self, mon_addrs_xml): 132 | addrs_xml = parseString(mon_addrs_xml[1].rstrip()) 133 | if not os.path.exists('/etc/rbd'): 134 | os.makedirs('/etc/rbd') 135 | addr_file = open('/etc/rbd/mons', 'w') 136 | addr_list = [] 137 | for ip in addrs_xml.getElementsByTagName('addr'): 138 | addr = ip.firstChild.data.split(':')[0] + '\n' 139 | addr_list.append(addr) 140 | addr_file.write(addr) 141 | addr_file.close() 142 | return(addr_list) 143 | 144 | 145 | def _formatRBD_pool(self, rbd_pool_string): 146 | base_path = '/var/lib/rbd/' 147 | pool_name_list = '' 148 | for pool in rbd_pool_string[1].split(','): 149 | if not pool.isspace() and pool != '': 150 | pool_name = pool.split(' ')[1] 151 | pool_name_list += pool_name + '\n' 152 | pool_path = os.path.join(base_path,pool_name) 153 | if not os.path.exists(pool_path): 154 | os.makedirs(pool_path) 155 | return pool_name_list 156 | 157 | 158 | def _formatRBD_image(self, rbd_pool_name, rbd_image_string): 159 | base_path = '/var/lib/rbd/' 160 | pool_path = os.path.join(base_path, rbd_pool_name) 161 | self._cleanCEPH_folder(pool_path,False) 162 | for block in rbd_image_string: 163 | if not 'sudo' in block and '\r' in block: 164 | rbd_image_path = os.path.join(base_path, rbd_pool_name, block.rstrip()) 165 | rbd_image = open(rbd_image_path,'w') 166 | image_info_response = self._getCEPH_response('sudo rbd --format json -p %s info %s' % (rbd_pool_name, block)) 167 | image_info = '' 168 | if len(image_info_response) >=3: 169 | image_info = image_info_response[2] 170 | else: 171 | image_info = image_info_response[1] 172 | util.SMlog('RBD info of the image is %s' % image_info) 173 | rbd_image.write('%s' % image_info) 174 | rbd_image.close() 175 | return 'list' 176 | 177 | 178 | def _cleanCEPH_folder(self, path,remove_root=True): 179 | if os.path.exists(path): 180 | for root, dirs, files in os.walk(path, topdown=False): 181 | for name in files: 182 | os.remove(os.path.join(root, name)) 183 | for name in dirs: 184 | os.rmdir(os.path.join(root, name)) 185 | if remove_root: 186 | os.rmdir(root) 187 | 188 | def _getRBD_index(self, image_name): 189 | parent_folder = '' 190 | for root, dirs, files in os.walk('/sys/devices/rbd', topdown=False): 191 | current_folder = root.split('/')[-1] 192 | if current_folder.isdigit(): 193 | for file in files: 194 | if file == 'name': 195 | f = open(os.path.join(root,file), 'rt') 196 | rbd_image_name = f.readline() 197 | f.close() 198 | if image_name in rbd_image_name: 199 | parent_folder = current_folder 200 | return parent_folder 201 | 202 | 203 | def _getCEPH_response(self, cmd): 204 | s = pxssh.pxssh() 205 | s.force_password = True 206 | password = "" 207 | if self.dconf.has_key('chappassword_secret'): 208 | password = util.get_secret(self.session, self.dconf['chappassword_secret']) 209 | elif self.dconf.has_key('chappassword'): 210 | password = self.dconf['chappassword'] 211 | user = self.srcmd.dconf['chapuser'] 212 | target = self.srcmd.dconf['target'].split(',')[0] 213 | port = self.srcmd.dconf['port'] 214 | if not s.login(target,user,password): 215 | util.SMlog('ssh login failed with last message %s' % s) 216 | else: 217 | s.sendline (cmd) 218 | s.prompt() 219 | result = s.before.split('\n') 220 | return result 221 | 222 | 223 | def attach(self, sr_uuid): 224 | ### Getting MON list using admin key above 225 | ceph_mon_list_xml = self._getCEPH_response('sudo ceph mon_status -f xml') 226 | ceph_mon_list = self._formatMON_list(ceph_mon_list_xml) 227 | 228 | # We got accurate list of monitor addresses, need pass that list of IPs to rbd add srting 229 | accurate_address_string = '' 230 | ''' TODO: very long list-to-string conversion, 231 | should be something like: address_string = ''.join(ceph_mon_list).replace('\n',',') if len(ceph_mon_list) >= 1''' 232 | if len(ceph_mon_list) >= 1: 233 | address_string = '' 234 | for address in ceph_mon_list: 235 | address_string += address.replace('\n',',') 236 | self.dconf['targetlist'] = address_string[:-1] 237 | accurate_address_string = address_string[:-1] 238 | else: 239 | accurate_address_string = ceph_mon_list 240 | 241 | ### Getting admin key using ssh command 242 | if self.dconf.has_key('SCSIid') and self.dconf['SCSIid']: 243 | util._testHost(self.dconf['target'].split(',')[0], long(self.dconf['port']), 'RBD Monitor') 244 | rbd_auth_output = self._getCEPH_response('sudo ceph auth list| grep admin -A1| grep key') 245 | rbd_auth_key = self._formatCEPH_key(rbd_auth_output) 246 | 247 | rbd_image_name = self.dconf['SCSIid'] 248 | attach_string = '%s name=admin,secret=%s %s %s' % (accurate_address_string, rbd_auth_key, self.dconf['targetIQN'], rbd_image_name) 249 | if not os.path.exists('/sys/bus/rbd'): 250 | os.execlp("modprobe", "modprobe", "rbd") 251 | rbd_disk_path = '/dev/disk/by-id/scsi-%s' % rbd_image_name 252 | rbd_scsi_path = '/dev/disk/by-scsid/%s' % rbd_image_name 253 | rbd_block_index = self._getRBD_index(rbd_image_name) 254 | if not os.path.exists(rbd_disk_path) and not rbd_block_index: 255 | try: 256 | rbd_add = open('/sys/bus/rbd/add','w') 257 | rbd_add.write(attach_string) 258 | rbd_add.close() 259 | rbd_image_index = str(self._getRBD_index(rbd_image_name)) 260 | os.symlink('/dev/rbd%s' % rbd_image_index , rbd_disk_path) 261 | os.makedirs(rbd_scsi_path) 262 | os.symlink('../../../rbd%s' % rbd_image_index, '%s/rbd%s' % (rbd_scsi_path,rbd_image_index)) 263 | self.attached = True 264 | except IOError, e: 265 | util.SMlog('the error is %s' % e) 266 | self.attached = False 267 | else: 268 | '''in iSCSI sr we need to attach target to interrogate LUN for size, scsi_id etc etc. 269 | We don't need to do this with current way of finding things over ssh''' 270 | self.attached = True 271 | 272 | 273 | def detach(self, sr_uuid): 274 | if self.dconf.has_key('SCSIid') and self.dconf['SCSIid']: 275 | rbd_image_name = self.dconf['SCSIid'] 276 | ''' should have pool and rbd name combination for better uniqueness of the link ''' 277 | rbd_disk_path = '/dev/disk/by-id/scsi-%s' % rbd_image_name 278 | rbd_scsi_path = '/dev/disk/by-scsid/%s' % rbd_image_name 279 | rbd_image_index = self._getRBD_index(rbd_image_name) 280 | if os.path.exists(rbd_disk_path): 281 | os.unlink(rbd_disk_path) 282 | if os.path.exists(rbd_scsi_path): 283 | self._cleanCEPH_folder(rbd_scsi_path) 284 | if os.path.exists('/dev/rbd%s' % rbd_image_index): 285 | rbd_remove = open('/sys/bus/rbd/remove','w') 286 | rbd_remove.write(rbd_image_index) 287 | rbd_remove.close() 288 | self.attached = False 289 | 290 | 291 | def refresh(self): 292 | # Unlike iSCSI SR we don't need to refresh paths or rescan sessions 293 | pass 294 | 295 | def print_LUNs(self): 296 | self.LUNs = {} 297 | rbd_size = '' 298 | rbd_identity = '' 299 | pool_path = os.path.join('/var/lib/rbd', self.dconf['targetIQN']) 300 | for file in util.listdir(pool_path): 301 | lun_path = os.path.join(pool_path, file) 302 | lun_file = open(lun_path, 'rt') 303 | lun_info = lun_file.read() 304 | for params in lun_info.split(','): 305 | if '"size' in params: 306 | rbd_size = params.split(':')[1] 307 | if 'block_name_prefix' in params: 308 | rbd_identity = params.split(':')[1] 309 | if rbd_size and rbd_identity: 310 | obj = self.vdi(self.uuid) 311 | self._divert_query(obj, lun_path,rbd_identity,rbd_size,file) 312 | self.LUNs[obj.uuid] = obj 313 | 314 | 315 | def _divert_query(self, vdi, path, rbd_id, rbd_size, lun_name): 316 | vdi.uuid = scsiutil.gen_uuid_from_string(rbd_id) 317 | vdi.location = self.uuid 318 | vdi.vendor = 'RADOS' 319 | vdi.serial = lun_name 320 | vdi.LUNid = rbd_id.split('.')[1] 321 | vdi.size = rbd_size 322 | vdi.SCSIid = lun_name 323 | vdi.path = path 324 | sm_config = util.default(vdi, "sm_config", lambda: {}) 325 | sm_config['LUNid'] = str(vdi.LUNid) 326 | sm_config['SCSIid'] = vdi.SCSIid 327 | vdi.sm_config = sm_config 328 | 329 | 330 | def _attach_LUN_bySCSIid(self, SCSIid): 331 | if os.path.exists('/dev/disk/by-id/scsi-%s' % SCSIid): 332 | return True 333 | else: 334 | # Couldn't find what sr_uuid is used in attach, so just calling it with a string 335 | self.attach('existing-sr-uuid') 336 | return True 337 | 338 | if __name__ == '__main__': 339 | SRCommand.run(RBDSR, DRIVER_INFO) 340 | else: 341 | SR.registerSR(RBDSR) 342 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /pexpect.py: -------------------------------------------------------------------------------- 1 | """Pexpect is a Python module for spawning child applications and controlling 2 | them automatically. Pexpect can be used for automating interactive applications 3 | such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup 4 | scripts for duplicating software package installations on different servers. It 5 | can be used for automated software testing. Pexpect is in the spirit of Don 6 | Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python 7 | require TCL and Expect or require C extensions to be compiled. Pexpect does not 8 | use C, Expect, or TCL extensions. It should work on any platform that supports 9 | the standard Python pty module. The Pexpect interface focuses on ease of use so 10 | that simple tasks are easy. 11 | 12 | There are two main interfaces to Pexpect -- the function, run() and the class, 13 | spawn. You can call the run() function to execute a command and return the 14 | output. This is a handy replacement for os.system(). 15 | 16 | For example:: 17 | 18 | pexpect.run('ls -la') 19 | 20 | The more powerful interface is the spawn class. You can use this to spawn an 21 | external child command and then interact with the child by sending lines and 22 | expecting responses. 23 | 24 | For example:: 25 | 26 | child = pexpect.spawn('scp foo myname@host.example.com:.') 27 | child.expect ('Password:') 28 | child.sendline (mypassword) 29 | 30 | This works even for commands that ask for passwords or other input outside of 31 | the normal stdio streams. 32 | 33 | Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, 34 | Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids 35 | vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, 36 | Geoffrey Marshall, Francisco Lourenco, Glen Mabey, Karthik Gurusamy, Fernando 37 | Perez, Corey Minyard, Jon Cohen, Guillaume Chazarain, Andrew Ryan, Nick 38 | Craig-Wood, Andrew Stone, Jorgen Grahn (Let me know if I forgot anyone.) 39 | 40 | Free, open source, and all that good stuff. 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy of 43 | this software and associated documentation files (the "Software"), to deal in 44 | the Software without restriction, including without limitation the rights to 45 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 46 | of the Software, and to permit persons to whom the Software is furnished to do 47 | so, subject to the following conditions: 48 | 49 | The above copyright notice and this permission notice shall be included in all 50 | copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 57 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 58 | SOFTWARE. 59 | 60 | Pexpect Copyright (c) 2008 Noah Spurrier 61 | http://pexpect.sourceforge.net/ 62 | 63 | $Id: pexpect.py 507 2007-12-27 02:40:52Z noah $ 64 | """ 65 | 66 | try: 67 | import os, sys, time 68 | import select 69 | import string 70 | import re 71 | import struct 72 | import resource 73 | import types 74 | import pty 75 | import tty 76 | import termios 77 | import fcntl 78 | import errno 79 | import traceback 80 | import signal 81 | except ImportError, e: 82 | raise ImportError (str(e) + """ 83 | 84 | A critical module was not found. Probably this operating system does not 85 | support it. Pexpect is intended for UNIX-like operating systems.""") 86 | 87 | __version__ = '2.3' 88 | __revision__ = '$Revision: 399 $' 89 | __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'run', 'which', 90 | 'split_command_line', '__version__', '__revision__'] 91 | 92 | # Exception classes used by this module. 93 | class ExceptionPexpect(Exception): 94 | 95 | """Base class for all exceptions raised by this module. 96 | """ 97 | 98 | def __init__(self, value): 99 | 100 | self.value = value 101 | 102 | def __str__(self): 103 | 104 | return str(self.value) 105 | 106 | def get_trace(self): 107 | 108 | """This returns an abbreviated stack trace with lines that only concern 109 | the caller. In other words, the stack trace inside the Pexpect module 110 | is not included. """ 111 | 112 | tblist = traceback.extract_tb(sys.exc_info()[2]) 113 | #tblist = filter(self.__filter_not_pexpect, tblist) 114 | tblist = [item for item in tblist if self.__filter_not_pexpect(item)] 115 | tblist = traceback.format_list(tblist) 116 | return ''.join(tblist) 117 | 118 | def __filter_not_pexpect(self, trace_list_item): 119 | 120 | """This returns True if list item 0 the string 'pexpect.py' in it. """ 121 | 122 | if trace_list_item[0].find('pexpect.py') == -1: 123 | return True 124 | else: 125 | return False 126 | 127 | class EOF(ExceptionPexpect): 128 | 129 | """Raised when EOF is read from a child. This usually means the child has exited.""" 130 | 131 | class TIMEOUT(ExceptionPexpect): 132 | 133 | """Raised when a read time exceeds the timeout. """ 134 | 135 | ##class TIMEOUT_PATTERN(TIMEOUT): 136 | ## """Raised when the pattern match time exceeds the timeout. 137 | ## This is different than a read TIMEOUT because the child process may 138 | ## give output, thus never give a TIMEOUT, but the output 139 | ## may never match a pattern. 140 | ## """ 141 | ##class MAXBUFFER(ExceptionPexpect): 142 | ## """Raised when a scan buffer fills before matching an expected pattern.""" 143 | 144 | def run (command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None): 145 | 146 | """ 147 | This function runs the given command; waits for it to finish; then 148 | returns all output as a string. STDERR is included in output. If the full 149 | path to the command is not given then the path is searched. 150 | 151 | Note that lines are terminated by CR/LF (\\r\\n) combination even on 152 | UNIX-like systems because this is the standard for pseudo ttys. If you set 153 | 'withexitstatus' to true, then run will return a tuple of (command_output, 154 | exitstatus). If 'withexitstatus' is false then this returns just 155 | command_output. 156 | 157 | The run() function can often be used instead of creating a spawn instance. 158 | For example, the following code uses spawn:: 159 | 160 | from pexpect import * 161 | child = spawn('scp foo myname@host.example.com:.') 162 | child.expect ('(?i)password') 163 | child.sendline (mypassword) 164 | 165 | The previous code can be replace with the following:: 166 | 167 | from pexpect import * 168 | run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword}) 169 | 170 | Examples 171 | ======== 172 | 173 | Start the apache daemon on the local machine:: 174 | 175 | from pexpect import * 176 | run ("/usr/local/apache/bin/apachectl start") 177 | 178 | Check in a file using SVN:: 179 | 180 | from pexpect import * 181 | run ("svn ci -m 'automatic commit' my_file.py") 182 | 183 | Run a command and capture exit status:: 184 | 185 | from pexpect import * 186 | (command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1) 187 | 188 | Tricky Examples 189 | =============== 190 | 191 | The following will run SSH and execute 'ls -l' on the remote machine. The 192 | password 'secret' will be sent if the '(?i)password' pattern is ever seen:: 193 | 194 | run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\\n'}) 195 | 196 | This will start mencoder to rip a video from DVD. This will also display 197 | progress ticks every 5 seconds as it runs. For example:: 198 | 199 | from pexpect import * 200 | def print_ticks(d): 201 | print d['event_count'], 202 | run ("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events={TIMEOUT:print_ticks}, timeout=5) 203 | 204 | The 'events' argument should be a dictionary of patterns and responses. 205 | Whenever one of the patterns is seen in the command out run() will send the 206 | associated response string. Note that you should put newlines in your 207 | string if Enter is necessary. The responses may also contain callback 208 | functions. Any callback is function that takes a dictionary as an argument. 209 | The dictionary contains all the locals from the run() function, so you can 210 | access the child spawn object or any other variable defined in run() 211 | (event_count, child, and extra_args are the most useful). A callback may 212 | return True to stop the current run process otherwise run() continues until 213 | the next event. A callback may also return a string which will be sent to 214 | the child. 'extra_args' is not used by directly run(). It provides a way to 215 | pass data to a callback function through run() through the locals 216 | dictionary passed to a callback. """ 217 | 218 | if timeout == -1: 219 | child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env) 220 | else: 221 | child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, cwd=cwd, env=env) 222 | if events is not None: 223 | patterns = events.keys() 224 | responses = events.values() 225 | else: 226 | patterns=None # We assume that EOF or TIMEOUT will save us. 227 | responses=None 228 | child_result_list = [] 229 | event_count = 0 230 | while 1: 231 | try: 232 | index = child.expect (patterns) 233 | if type(child.after) in types.StringTypes: 234 | child_result_list.append(child.before + child.after) 235 | else: # child.after may have been a TIMEOUT or EOF, so don't cat those. 236 | child_result_list.append(child.before) 237 | if type(responses[index]) in types.StringTypes: 238 | child.send(responses[index]) 239 | elif type(responses[index]) is types.FunctionType: 240 | callback_result = responses[index](locals()) 241 | sys.stdout.flush() 242 | if type(callback_result) in types.StringTypes: 243 | child.send(callback_result) 244 | elif callback_result: 245 | break 246 | else: 247 | raise TypeError ('The callback must be a string or function type.') 248 | event_count = event_count + 1 249 | except TIMEOUT, e: 250 | child_result_list.append(child.before) 251 | break 252 | except EOF, e: 253 | child_result_list.append(child.before) 254 | break 255 | child_result = ''.join(child_result_list) 256 | if withexitstatus: 257 | child.close() 258 | return (child_result, child.exitstatus) 259 | else: 260 | return child_result 261 | 262 | class spawn (object): 263 | 264 | """This is the main class interface for Pexpect. Use this class to start 265 | and control child applications. """ 266 | 267 | def __init__(self, command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None): 268 | 269 | """This is the constructor. The command parameter may be a string that 270 | includes a command and any arguments to the command. For example:: 271 | 272 | child = pexpect.spawn ('/usr/bin/ftp') 273 | child = pexpect.spawn ('/usr/bin/ssh user@example.com') 274 | child = pexpect.spawn ('ls -latr /tmp') 275 | 276 | You may also construct it with a list of arguments like so:: 277 | 278 | child = pexpect.spawn ('/usr/bin/ftp', []) 279 | child = pexpect.spawn ('/usr/bin/ssh', ['user@example.com']) 280 | child = pexpect.spawn ('ls', ['-latr', '/tmp']) 281 | 282 | After this the child application will be created and will be ready to 283 | talk to. For normal use, see expect() and send() and sendline(). 284 | 285 | Remember that Pexpect does NOT interpret shell meta characters such as 286 | redirect, pipe, or wild cards (>, |, or *). This is a common mistake. 287 | If you want to run a command and pipe it through another command then 288 | you must also start a shell. For example:: 289 | 290 | child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > log_list.txt"') 291 | child.expect(pexpect.EOF) 292 | 293 | The second form of spawn (where you pass a list of arguments) is useful 294 | in situations where you wish to spawn a command and pass it its own 295 | argument list. This can make syntax more clear. For example, the 296 | following is equivalent to the previous example:: 297 | 298 | shell_cmd = 'ls -l | grep LOG > log_list.txt' 299 | child = pexpect.spawn('/bin/bash', ['-c', shell_cmd]) 300 | child.expect(pexpect.EOF) 301 | 302 | The maxread attribute sets the read buffer size. This is maximum number 303 | of bytes that Pexpect will try to read from a TTY at one time. Setting 304 | the maxread size to 1 will turn off buffering. Setting the maxread 305 | value higher may help performance in cases where large amounts of 306 | output are read back from the child. This feature is useful in 307 | conjunction with searchwindowsize. 308 | 309 | The searchwindowsize attribute sets the how far back in the incomming 310 | seach buffer Pexpect will search for pattern matches. Every time 311 | Pexpect reads some data from the child it will append the data to the 312 | incomming buffer. The default is to search from the beginning of the 313 | imcomming buffer each time new data is read from the child. But this is 314 | very inefficient if you are running a command that generates a large 315 | amount of data where you want to match The searchwindowsize does not 316 | effect the size of the incomming data buffer. You will still have 317 | access to the full buffer after expect() returns. 318 | 319 | The logfile member turns on or off logging. All input and output will 320 | be copied to the given file object. Set logfile to None to stop 321 | logging. This is the default. Set logfile to sys.stdout to echo 322 | everything to standard output. The logfile is flushed after each write. 323 | 324 | Example log input and output to a file:: 325 | 326 | child = pexpect.spawn('some_command') 327 | fout = file('mylog.txt','w') 328 | child.logfile = fout 329 | 330 | Example log to stdout:: 331 | 332 | child = pexpect.spawn('some_command') 333 | child.logfile = sys.stdout 334 | 335 | The logfile_read and logfile_send members can be used to separately log 336 | the input from the child and output sent to the child. Sometimes you 337 | don't want to see everything you write to the child. You only want to 338 | log what the child sends back. For example:: 339 | 340 | child = pexpect.spawn('some_command') 341 | child.logfile_read = sys.stdout 342 | 343 | To separately log output sent to the child use logfile_send:: 344 | 345 | self.logfile_send = fout 346 | 347 | The delaybeforesend helps overcome a weird behavior that many users 348 | were experiencing. The typical problem was that a user would expect() a 349 | "Password:" prompt and then immediately call sendline() to send the 350 | password. The user would then see that their password was echoed back 351 | to them. Passwords don't normally echo. The problem is caused by the 352 | fact that most applications print out the "Password" prompt and then 353 | turn off stdin echo, but if you send your password before the 354 | application turned off echo, then you get your password echoed. 355 | Normally this wouldn't be a problem when interacting with a human at a 356 | real keyboard. If you introduce a slight delay just before writing then 357 | this seems to clear up the problem. This was such a common problem for 358 | many users that I decided that the default pexpect behavior should be 359 | to sleep just before writing to the child application. 1/20th of a 360 | second (50 ms) seems to be enough to clear up the problem. You can set 361 | delaybeforesend to 0 to return to the old behavior. Most Linux machines 362 | don't like this to be below 0.03. I don't know why. 363 | 364 | Note that spawn is clever about finding commands on your path. 365 | It uses the same logic that "which" uses to find executables. 366 | 367 | If you wish to get the exit status of the child you must call the 368 | close() method. The exit or signal status of the child will be stored 369 | in self.exitstatus or self.signalstatus. If the child exited normally 370 | then exitstatus will store the exit return code and signalstatus will 371 | be None. If the child was terminated abnormally with a signal then 372 | signalstatus will store the signal value and exitstatus will be None. 373 | If you need more detail you can also read the self.status member which 374 | stores the status returned by os.waitpid. You can interpret this using 375 | os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. """ 376 | 377 | self.STDIN_FILENO = pty.STDIN_FILENO 378 | self.STDOUT_FILENO = pty.STDOUT_FILENO 379 | self.STDERR_FILENO = pty.STDERR_FILENO 380 | self.stdin = sys.stdin 381 | self.stdout = sys.stdout 382 | self.stderr = sys.stderr 383 | 384 | self.searcher = None 385 | self.ignorecase = False 386 | self.before = None 387 | self.after = None 388 | self.match = None 389 | self.match_index = None 390 | self.terminated = True 391 | self.exitstatus = None 392 | self.signalstatus = None 393 | self.status = None # status returned by os.waitpid 394 | self.flag_eof = False 395 | self.pid = None 396 | self.child_fd = -1 # initially closed 397 | self.timeout = timeout 398 | self.delimiter = EOF 399 | self.logfile = logfile 400 | self.logfile_read = None # input from child (read_nonblocking) 401 | self.logfile_send = None # output to send (send, sendline) 402 | self.maxread = maxread # max bytes to read at one time into buffer 403 | self.buffer = '' # This is the read buffer. See maxread. 404 | self.searchwindowsize = searchwindowsize # Anything before searchwindowsize point is preserved, but not searched. 405 | # Most Linux machines don't like delaybeforesend to be below 0.03 (30 ms). 406 | self.delaybeforesend = 0.05 # Sets sleep time used just before sending data to child. Time in seconds. 407 | self.delayafterclose = 0.1 # Sets delay in close() method to allow kernel time to update process status. Time in seconds. 408 | self.delayafterterminate = 0.1 # Sets delay in terminate() method to allow kernel time to update process status. Time in seconds. 409 | self.softspace = False # File-like object. 410 | self.name = '<' + repr(self) + '>' # File-like object. 411 | self.encoding = None # File-like object. 412 | self.closed = True # File-like object. 413 | self.cwd = cwd 414 | self.env = env 415 | self.__irix_hack = (sys.platform.lower().find('irix')>=0) # This flags if we are running on irix 416 | # Solaris uses internal __fork_pty(). All others use pty.fork(). 417 | if (sys.platform.lower().find('solaris')>=0) or (sys.platform.lower().find('sunos5')>=0): 418 | self.use_native_pty_fork = False 419 | else: 420 | self.use_native_pty_fork = True 421 | 422 | 423 | # allow dummy instances for subclasses that may not use command or args. 424 | if command is None: 425 | self.command = None 426 | self.args = None 427 | self.name = '' 428 | else: 429 | self._spawn (command, args) 430 | 431 | def __del__(self): 432 | 433 | """This makes sure that no system resources are left open. Python only 434 | garbage collects Python objects. OS file descriptors are not Python 435 | objects, so they must be handled explicitly. If the child file 436 | descriptor was opened outside of this class (passed to the constructor) 437 | then this does not close it. """ 438 | 439 | if not self.closed: 440 | # It is possible for __del__ methods to execute during the 441 | # teardown of the Python VM itself. Thus self.close() may 442 | # trigger an exception because os.close may be None. 443 | # -- Fernando Perez 444 | try: 445 | self.close() 446 | except AttributeError: 447 | pass 448 | 449 | def __str__(self): 450 | 451 | """This returns a human-readable string that represents the state of 452 | the object. """ 453 | 454 | s = [] 455 | s.append(repr(self)) 456 | s.append('version: ' + __version__ + ' (' + __revision__ + ')') 457 | s.append('command: ' + str(self.command)) 458 | s.append('args: ' + str(self.args)) 459 | s.append('searcher: ' + str(self.searcher)) 460 | s.append('buffer (last 100 chars): ' + str(self.buffer)[-100:]) 461 | s.append('before (last 100 chars): ' + str(self.before)[-100:]) 462 | s.append('after: ' + str(self.after)) 463 | s.append('match: ' + str(self.match)) 464 | s.append('match_index: ' + str(self.match_index)) 465 | s.append('exitstatus: ' + str(self.exitstatus)) 466 | s.append('flag_eof: ' + str(self.flag_eof)) 467 | s.append('pid: ' + str(self.pid)) 468 | s.append('child_fd: ' + str(self.child_fd)) 469 | s.append('closed: ' + str(self.closed)) 470 | s.append('timeout: ' + str(self.timeout)) 471 | s.append('delimiter: ' + str(self.delimiter)) 472 | s.append('logfile: ' + str(self.logfile)) 473 | s.append('logfile_read: ' + str(self.logfile_read)) 474 | s.append('logfile_send: ' + str(self.logfile_send)) 475 | s.append('maxread: ' + str(self.maxread)) 476 | s.append('ignorecase: ' + str(self.ignorecase)) 477 | s.append('searchwindowsize: ' + str(self.searchwindowsize)) 478 | s.append('delaybeforesend: ' + str(self.delaybeforesend)) 479 | s.append('delayafterclose: ' + str(self.delayafterclose)) 480 | s.append('delayafterterminate: ' + str(self.delayafterterminate)) 481 | return '\n'.join(s) 482 | 483 | def _spawn(self,command,args=[]): 484 | 485 | """This starts the given command in a child process. This does all the 486 | fork/exec type of stuff for a pty. This is called by __init__. If args 487 | is empty then command will be parsed (split on spaces) and args will be 488 | set to parsed arguments. """ 489 | 490 | # The pid and child_fd of this object get set by this method. 491 | # Note that it is difficult for this method to fail. 492 | # You cannot detect if the child process cannot start. 493 | # So the only way you can tell if the child process started 494 | # or not is to try to read from the file descriptor. If you get 495 | # EOF immediately then it means that the child is already dead. 496 | # That may not necessarily be bad because you may haved spawned a child 497 | # that performs some task; creates no stdout output; and then dies. 498 | 499 | # If command is an int type then it may represent a file descriptor. 500 | if type(command) == type(0): 501 | raise ExceptionPexpect ('Command is an int type. If this is a file descriptor then maybe you want to use fdpexpect.fdspawn which takes an existing file descriptor instead of a command string.') 502 | 503 | if type (args) != type([]): 504 | raise TypeError ('The argument, args, must be a list.') 505 | 506 | if args == []: 507 | self.args = split_command_line(command) 508 | self.command = self.args[0] 509 | else: 510 | self.args = args[:] # work with a copy 511 | self.args.insert (0, command) 512 | self.command = command 513 | 514 | command_with_path = which(self.command) 515 | if command_with_path is None: 516 | raise ExceptionPexpect ('The command was not found or was not executable: %s.' % self.command) 517 | self.command = command_with_path 518 | self.args[0] = self.command 519 | 520 | self.name = '<' + ' '.join (self.args) + '>' 521 | 522 | assert self.pid is None, 'The pid member should be None.' 523 | assert self.command is not None, 'The command member should not be None.' 524 | 525 | if self.use_native_pty_fork: 526 | try: 527 | self.pid, self.child_fd = pty.fork() 528 | except OSError, e: 529 | raise ExceptionPexpect('Error! pty.fork() failed: ' + str(e)) 530 | else: # Use internal __fork_pty 531 | self.pid, self.child_fd = self.__fork_pty() 532 | 533 | if self.pid == 0: # Child 534 | try: 535 | self.child_fd = sys.stdout.fileno() # used by setwinsize() 536 | self.setwinsize(24, 80) 537 | except: 538 | # Some platforms do not like setwinsize (Cygwin). 539 | # This will cause problem when running applications that 540 | # are very picky about window size. 541 | # This is a serious limitation, but not a show stopper. 542 | pass 543 | # Do not allow child to inherit open file descriptors from parent. 544 | max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] 545 | for i in range (3, max_fd): 546 | try: 547 | os.close (i) 548 | except OSError: 549 | pass 550 | 551 | # I don't know why this works, but ignoring SIGHUP fixes a 552 | # problem when trying to start a Java daemon with sudo 553 | # (specifically, Tomcat). 554 | signal.signal(signal.SIGHUP, signal.SIG_IGN) 555 | 556 | if self.cwd is not None: 557 | os.chdir(self.cwd) 558 | if self.env is None: 559 | os.execv(self.command, self.args) 560 | else: 561 | os.execvpe(self.command, self.args, self.env) 562 | 563 | # Parent 564 | self.terminated = False 565 | self.closed = False 566 | 567 | def __fork_pty(self): 568 | 569 | """This implements a substitute for the forkpty system call. This 570 | should be more portable than the pty.fork() function. Specifically, 571 | this should work on Solaris. 572 | 573 | Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to 574 | resolve the issue with Python's pty.fork() not supporting Solaris, 575 | particularly ssh. Based on patch to posixmodule.c authored by Noah 576 | Spurrier:: 577 | 578 | http://mail.python.org/pipermail/python-dev/2003-May/035281.html 579 | 580 | """ 581 | 582 | parent_fd, child_fd = os.openpty() 583 | if parent_fd < 0 or child_fd < 0: 584 | raise ExceptionPexpect, "Error! Could not open pty with os.openpty()." 585 | 586 | pid = os.fork() 587 | if pid < 0: 588 | raise ExceptionPexpect, "Error! Failed os.fork()." 589 | elif pid == 0: 590 | # Child. 591 | os.close(parent_fd) 592 | self.__pty_make_controlling_tty(child_fd) 593 | 594 | os.dup2(child_fd, 0) 595 | os.dup2(child_fd, 1) 596 | os.dup2(child_fd, 2) 597 | 598 | if child_fd > 2: 599 | os.close(child_fd) 600 | else: 601 | # Parent. 602 | os.close(child_fd) 603 | 604 | return pid, parent_fd 605 | 606 | def __pty_make_controlling_tty(self, tty_fd): 607 | 608 | """This makes the pseudo-terminal the controlling tty. This should be 609 | more portable than the pty.fork() function. Specifically, this should 610 | work on Solaris. """ 611 | 612 | child_name = os.ttyname(tty_fd) 613 | 614 | # Disconnect from controlling tty if still connected. 615 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY); 616 | if fd >= 0: 617 | os.close(fd) 618 | 619 | os.setsid() 620 | 621 | # Verify we are disconnected from controlling tty 622 | try: 623 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY); 624 | if fd >= 0: 625 | os.close(fd) 626 | raise ExceptionPexpect, "Error! We are not disconnected from a controlling tty." 627 | except: 628 | # Good! We are disconnected from a controlling tty. 629 | pass 630 | 631 | # Verify we can open child pty. 632 | fd = os.open(child_name, os.O_RDWR); 633 | if fd < 0: 634 | raise ExceptionPexpect, "Error! Could not open child pty, " + child_name 635 | else: 636 | os.close(fd) 637 | 638 | # Verify we now have a controlling tty. 639 | fd = os.open("/dev/tty", os.O_WRONLY) 640 | if fd < 0: 641 | raise ExceptionPexpect, "Error! Could not open controlling tty, /dev/tty" 642 | else: 643 | os.close(fd) 644 | 645 | def fileno (self): # File-like object. 646 | 647 | """This returns the file descriptor of the pty for the child. 648 | """ 649 | 650 | return self.child_fd 651 | 652 | def close (self, force=True): # File-like object. 653 | 654 | """This closes the connection with the child application. Note that 655 | calling close() more than once is valid. This emulates standard Python 656 | behavior with files. Set force to True if you want to make sure that 657 | the child is terminated (SIGKILL is sent if the child ignores SIGHUP 658 | and SIGINT). """ 659 | 660 | if not self.closed: 661 | self.flush() 662 | os.close (self.child_fd) 663 | time.sleep(self.delayafterclose) # Give kernel time to update process status. 664 | if self.isalive(): 665 | if not self.terminate(force): 666 | raise ExceptionPexpect ('close() could not terminate the child using terminate()') 667 | self.child_fd = -1 668 | self.closed = True 669 | #self.pid = None 670 | 671 | def flush (self): # File-like object. 672 | 673 | """This does nothing. It is here to support the interface for a 674 | File-like object. """ 675 | 676 | pass 677 | 678 | def isatty (self): # File-like object. 679 | 680 | """This returns True if the file descriptor is open and connected to a 681 | tty(-like) device, else False. """ 682 | 683 | return os.isatty(self.child_fd) 684 | 685 | def waitnoecho (self, timeout=-1): 686 | 687 | """This waits until the terminal ECHO flag is set False. This returns 688 | True if the echo mode is off. This returns False if the ECHO flag was 689 | not set False before the timeout. This can be used to detect when the 690 | child is waiting for a password. Usually a child application will turn 691 | off echo mode when it is waiting for the user to enter a password. For 692 | example, instead of expecting the "password:" prompt you can wait for 693 | the child to set ECHO off:: 694 | 695 | p = pexpect.spawn ('ssh user@example.com') 696 | p.waitnoecho() 697 | p.sendline(mypassword) 698 | 699 | If timeout is None then this method to block forever until ECHO flag is 700 | False. 701 | 702 | """ 703 | 704 | if timeout == -1: 705 | timeout = self.timeout 706 | if timeout is not None: 707 | end_time = time.time() + timeout 708 | while True: 709 | if not self.getecho(): 710 | return True 711 | if timeout < 0 and timeout is not None: 712 | return False 713 | if timeout is not None: 714 | timeout = end_time - time.time() 715 | time.sleep(0.1) 716 | 717 | def getecho (self): 718 | 719 | """This returns the terminal echo mode. This returns True if echo is 720 | on or False if echo is off. Child applications that are expecting you 721 | to enter a password often set ECHO False. See waitnoecho(). """ 722 | 723 | attr = termios.tcgetattr(self.child_fd) 724 | if attr[3] & termios.ECHO: 725 | return True 726 | return False 727 | 728 | def setecho (self, state): 729 | 730 | """This sets the terminal echo mode on or off. Note that anything the 731 | child sent before the echo will be lost, so you should be sure that 732 | your input buffer is empty before you call setecho(). For example, the 733 | following will work as expected:: 734 | 735 | p = pexpect.spawn('cat') 736 | p.sendline ('1234') # We will see this twice (once from tty echo and again from cat). 737 | p.expect (['1234']) 738 | p.expect (['1234']) 739 | p.setecho(False) # Turn off tty echo 740 | p.sendline ('abcd') # We will set this only once (echoed by cat). 741 | p.sendline ('wxyz') # We will set this only once (echoed by cat) 742 | p.expect (['abcd']) 743 | p.expect (['wxyz']) 744 | 745 | The following WILL NOT WORK because the lines sent before the setecho 746 | will be lost:: 747 | 748 | p = pexpect.spawn('cat') 749 | p.sendline ('1234') # We will see this twice (once from tty echo and again from cat). 750 | p.setecho(False) # Turn off tty echo 751 | p.sendline ('abcd') # We will set this only once (echoed by cat). 752 | p.sendline ('wxyz') # We will set this only once (echoed by cat) 753 | p.expect (['1234']) 754 | p.expect (['1234']) 755 | p.expect (['abcd']) 756 | p.expect (['wxyz']) 757 | """ 758 | 759 | self.child_fd 760 | attr = termios.tcgetattr(self.child_fd) 761 | if state: 762 | attr[3] = attr[3] | termios.ECHO 763 | else: 764 | attr[3] = attr[3] & ~termios.ECHO 765 | # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent 766 | # and blocked on some platforms. TCSADRAIN is probably ideal if it worked. 767 | termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) 768 | 769 | def read_nonblocking (self, size = 1, timeout = -1): 770 | 771 | """This reads at most size characters from the child application. It 772 | includes a timeout. If the read does not complete within the timeout 773 | period then a TIMEOUT exception is raised. If the end of file is read 774 | then an EOF exception will be raised. If a log file was set using 775 | setlog() then all data will also be written to the log file. 776 | 777 | If timeout is None then the read may block indefinitely. If timeout is -1 778 | then the self.timeout value is used. If timeout is 0 then the child is 779 | polled and if there was no data immediately ready then this will raise 780 | a TIMEOUT exception. 781 | 782 | The timeout refers only to the amount of time to read at least one 783 | character. This is not effected by the 'size' parameter, so if you call 784 | read_nonblocking(size=100, timeout=30) and only one character is 785 | available right away then one character will be returned immediately. 786 | It will not wait for 30 seconds for another 99 characters to come in. 787 | 788 | This is a wrapper around os.read(). It uses select.select() to 789 | implement the timeout. """ 790 | 791 | if self.closed: 792 | raise ValueError ('I/O operation on closed file in read_nonblocking().') 793 | 794 | if timeout == -1: 795 | timeout = self.timeout 796 | 797 | # Note that some systems such as Solaris do not give an EOF when 798 | # the child dies. In fact, you can still try to read 799 | # from the child_fd -- it will block forever or until TIMEOUT. 800 | # For this case, I test isalive() before doing any reading. 801 | # If isalive() is false, then I pretend that this is the same as EOF. 802 | if not self.isalive(): 803 | r,w,e = self.__select([self.child_fd], [], [], 0) # timeout of 0 means "poll" 804 | if not r: 805 | self.flag_eof = True 806 | raise EOF ('End Of File (EOF) in read_nonblocking(). Braindead platform.') 807 | elif self.__irix_hack: 808 | # This is a hack for Irix. It seems that Irix requires a long delay before checking isalive. 809 | # This adds a 2 second delay, but only when the child is terminated. 810 | r, w, e = self.__select([self.child_fd], [], [], 2) 811 | if not r and not self.isalive(): 812 | self.flag_eof = True 813 | raise EOF ('End Of File (EOF) in read_nonblocking(). Pokey platform.') 814 | 815 | r,w,e = self.__select([self.child_fd], [], [], timeout) 816 | 817 | if not r: 818 | if not self.isalive(): 819 | # Some platforms, such as Irix, will claim that their processes are alive; 820 | # then timeout on the select; and then finally admit that they are not alive. 821 | self.flag_eof = True 822 | raise EOF ('End of File (EOF) in read_nonblocking(). Very pokey platform.') 823 | else: 824 | raise TIMEOUT ('Timeout exceeded in read_nonblocking().') 825 | 826 | if self.child_fd in r: 827 | try: 828 | s = os.read(self.child_fd, size) 829 | except OSError, e: # Linux does this 830 | self.flag_eof = True 831 | raise EOF ('End Of File (EOF) in read_nonblocking(). Exception style platform.') 832 | if s == '': # BSD style 833 | self.flag_eof = True 834 | raise EOF ('End Of File (EOF) in read_nonblocking(). Empty string style platform.') 835 | 836 | if self.logfile is not None: 837 | self.logfile.write (s) 838 | self.logfile.flush() 839 | if self.logfile_read is not None: 840 | self.logfile_read.write (s) 841 | self.logfile_read.flush() 842 | 843 | return s 844 | 845 | raise ExceptionPexpect ('Reached an unexpected state in read_nonblocking().') 846 | 847 | def read (self, size = -1): # File-like object. 848 | 849 | """This reads at most "size" bytes from the file (less if the read hits 850 | EOF before obtaining size bytes). If the size argument is negative or 851 | omitted, read all data until EOF is reached. The bytes are returned as 852 | a string object. An empty string is returned when EOF is encountered 853 | immediately. """ 854 | 855 | if size == 0: 856 | return '' 857 | if size < 0: 858 | self.expect (self.delimiter) # delimiter default is EOF 859 | return self.before 860 | 861 | # I could have done this more directly by not using expect(), but 862 | # I deliberately decided to couple read() to expect() so that 863 | # I would catch any bugs early and ensure consistant behavior. 864 | # It's a little less efficient, but there is less for me to 865 | # worry about if I have to later modify read() or expect(). 866 | # Note, it's OK if size==-1 in the regex. That just means it 867 | # will never match anything in which case we stop only on EOF. 868 | cre = re.compile('.{%d}' % size, re.DOTALL) 869 | index = self.expect ([cre, self.delimiter]) # delimiter default is EOF 870 | if index == 0: 871 | return self.after ### self.before should be ''. Should I assert this? 872 | return self.before 873 | 874 | def readline (self, size = -1): # File-like object. 875 | 876 | """This reads and returns one entire line. A trailing newline is kept 877 | in the string, but may be absent when a file ends with an incomplete 878 | line. Note: This readline() looks for a \\r\\n pair even on UNIX 879 | because this is what the pseudo tty device returns. So contrary to what 880 | you may expect you will receive the newline as \\r\\n. An empty string 881 | is returned when EOF is hit immediately. Currently, the size argument is 882 | mostly ignored, so this behavior is not standard for a file-like 883 | object. If size is 0 then an empty string is returned. """ 884 | 885 | if size == 0: 886 | return '' 887 | index = self.expect (['\r\n', self.delimiter]) # delimiter default is EOF 888 | if index == 0: 889 | return self.before + '\r\n' 890 | else: 891 | return self.before 892 | 893 | def __iter__ (self): # File-like object. 894 | 895 | """This is to support iterators over a file-like object. 896 | """ 897 | 898 | return self 899 | 900 | def next (self): # File-like object. 901 | 902 | """This is to support iterators over a file-like object. 903 | """ 904 | 905 | result = self.readline() 906 | if result == "": 907 | raise StopIteration 908 | return result 909 | 910 | def readlines (self, sizehint = -1): # File-like object. 911 | 912 | """This reads until EOF using readline() and returns a list containing 913 | the lines thus read. The optional "sizehint" argument is ignored. """ 914 | 915 | lines = [] 916 | while True: 917 | line = self.readline() 918 | if not line: 919 | break 920 | lines.append(line) 921 | return lines 922 | 923 | def write(self, s): # File-like object. 924 | 925 | """This is similar to send() except that there is no return value. 926 | """ 927 | 928 | self.send (s) 929 | 930 | def writelines (self, sequence): # File-like object. 931 | 932 | """This calls write() for each element in the sequence. The sequence 933 | can be any iterable object producing strings, typically a list of 934 | strings. This does not add line separators There is no return value. 935 | """ 936 | 937 | for s in sequence: 938 | self.write (s) 939 | 940 | def send(self, s): 941 | 942 | """This sends a string to the child process. This returns the number of 943 | bytes written. If a log file was set then the data is also written to 944 | the log. """ 945 | 946 | time.sleep(self.delaybeforesend) 947 | if self.logfile is not None: 948 | self.logfile.write (s) 949 | self.logfile.flush() 950 | if self.logfile_send is not None: 951 | self.logfile_send.write (s) 952 | self.logfile_send.flush() 953 | c = os.write(self.child_fd, s) 954 | return c 955 | 956 | def sendline(self, s=''): 957 | 958 | """This is like send(), but it adds a line feed (os.linesep). This 959 | returns the number of bytes written. """ 960 | 961 | n = self.send(s) 962 | n = n + self.send (os.linesep) 963 | return n 964 | 965 | def sendcontrol(self, char): 966 | 967 | """This sends a control character to the child such as Ctrl-C or 968 | Ctrl-D. For example, to send a Ctrl-G (ASCII 7):: 969 | 970 | child.sendcontrol('g') 971 | 972 | See also, sendintr() and sendeof(). 973 | """ 974 | 975 | char = char.lower() 976 | a = ord(char) 977 | if a>=97 and a<=122: 978 | a = a - ord('a') + 1 979 | return self.send (chr(a)) 980 | d = {'@':0, '`':0, 981 | '[':27, '{':27, 982 | '\\':28, '|':28, 983 | ']':29, '}': 29, 984 | '^':30, '~':30, 985 | '_':31, 986 | '?':127} 987 | if char not in d: 988 | return 0 989 | return self.send (chr(d[char])) 990 | 991 | def sendeof(self): 992 | 993 | """This sends an EOF to the child. This sends a character which causes 994 | the pending parent output buffer to be sent to the waiting child 995 | program without waiting for end-of-line. If it is the first character 996 | of the line, the read() in the user program returns 0, which signifies 997 | end-of-file. This means to work as expected a sendeof() has to be 998 | called at the beginning of a line. This method does not send a newline. 999 | It is the responsibility of the caller to ensure the eof is sent at the 1000 | beginning of a line. """ 1001 | 1002 | ### Hmmm... how do I send an EOF? 1003 | ###C if ((m = write(pty, *buf, p - *buf)) < 0) 1004 | ###C return (errno == EWOULDBLOCK) ? n : -1; 1005 | #fd = sys.stdin.fileno() 1006 | #old = termios.tcgetattr(fd) # remember current state 1007 | #attr = termios.tcgetattr(fd) 1008 | #attr[3] = attr[3] | termios.ICANON # ICANON must be set to recognize EOF 1009 | #try: # use try/finally to ensure state gets restored 1010 | # termios.tcsetattr(fd, termios.TCSADRAIN, attr) 1011 | # if hasattr(termios, 'CEOF'): 1012 | # os.write (self.child_fd, '%c' % termios.CEOF) 1013 | # else: 1014 | # # Silly platform does not define CEOF so assume CTRL-D 1015 | # os.write (self.child_fd, '%c' % 4) 1016 | #finally: # restore state 1017 | # termios.tcsetattr(fd, termios.TCSADRAIN, old) 1018 | if hasattr(termios, 'VEOF'): 1019 | char = termios.tcgetattr(self.child_fd)[6][termios.VEOF] 1020 | else: 1021 | # platform does not define VEOF so assume CTRL-D 1022 | char = chr(4) 1023 | self.send(char) 1024 | 1025 | def sendintr(self): 1026 | 1027 | """This sends a SIGINT to the child. It does not require 1028 | the SIGINT to be the first character on a line. """ 1029 | 1030 | if hasattr(termios, 'VINTR'): 1031 | char = termios.tcgetattr(self.child_fd)[6][termios.VINTR] 1032 | else: 1033 | # platform does not define VINTR so assume CTRL-C 1034 | char = chr(3) 1035 | self.send (char) 1036 | 1037 | def eof (self): 1038 | 1039 | """This returns True if the EOF exception was ever raised. 1040 | """ 1041 | 1042 | return self.flag_eof 1043 | 1044 | def terminate(self, force=False): 1045 | 1046 | """This forces a child process to terminate. It starts nicely with 1047 | SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This 1048 | returns True if the child was terminated. This returns False if the 1049 | child could not be terminated. """ 1050 | 1051 | if not self.isalive(): 1052 | return True 1053 | try: 1054 | self.kill(signal.SIGHUP) 1055 | time.sleep(self.delayafterterminate) 1056 | if not self.isalive(): 1057 | return True 1058 | self.kill(signal.SIGCONT) 1059 | time.sleep(self.delayafterterminate) 1060 | if not self.isalive(): 1061 | return True 1062 | self.kill(signal.SIGINT) 1063 | time.sleep(self.delayafterterminate) 1064 | if not self.isalive(): 1065 | return True 1066 | if force: 1067 | self.kill(signal.SIGKILL) 1068 | time.sleep(self.delayafterterminate) 1069 | if not self.isalive(): 1070 | return True 1071 | else: 1072 | return False 1073 | return False 1074 | except OSError, e: 1075 | # I think there are kernel timing issues that sometimes cause 1076 | # this to happen. I think isalive() reports True, but the 1077 | # process is dead to the kernel. 1078 | # Make one last attempt to see if the kernel is up to date. 1079 | time.sleep(self.delayafterterminate) 1080 | if not self.isalive(): 1081 | return True 1082 | else: 1083 | return False 1084 | 1085 | def wait(self): 1086 | 1087 | """This waits until the child exits. This is a blocking call. This will 1088 | not read any data from the child, so this will block forever if the 1089 | child has unread output and has terminated. In other words, the child 1090 | may have printed output then called exit(); but, technically, the child 1091 | is still alive until its output is read. """ 1092 | 1093 | if self.isalive(): 1094 | pid, status = os.waitpid(self.pid, 0) 1095 | else: 1096 | raise ExceptionPexpect ('Cannot wait for dead child process.') 1097 | self.exitstatus = os.WEXITSTATUS(status) 1098 | if os.WIFEXITED (status): 1099 | self.status = status 1100 | self.exitstatus = os.WEXITSTATUS(status) 1101 | self.signalstatus = None 1102 | self.terminated = True 1103 | elif os.WIFSIGNALED (status): 1104 | self.status = status 1105 | self.exitstatus = None 1106 | self.signalstatus = os.WTERMSIG(status) 1107 | self.terminated = True 1108 | elif os.WIFSTOPPED (status): 1109 | raise ExceptionPexpect ('Wait was called for a child process that is stopped. This is not supported. Is some other process attempting job control with our child pid?') 1110 | return self.exitstatus 1111 | 1112 | def isalive(self): 1113 | 1114 | """This tests if the child process is running or not. This is 1115 | non-blocking. If the child was terminated then this will read the 1116 | exitstatus or signalstatus of the child. This returns True if the child 1117 | process appears to be running or False if not. It can take literally 1118 | SECONDS for Solaris to return the right status. """ 1119 | 1120 | if self.terminated: 1121 | return False 1122 | 1123 | if self.flag_eof: 1124 | # This is for Linux, which requires the blocking form of waitpid to get 1125 | # status of a defunct process. This is super-lame. The flag_eof would have 1126 | # been set in read_nonblocking(), so this should be safe. 1127 | waitpid_options = 0 1128 | else: 1129 | waitpid_options = os.WNOHANG 1130 | 1131 | try: 1132 | pid, status = os.waitpid(self.pid, waitpid_options) 1133 | except OSError, e: # No child processes 1134 | if e[0] == errno.ECHILD: 1135 | raise ExceptionPexpect ('isalive() encountered condition where "terminated" is 0, but there was no child process. Did someone else call waitpid() on our process?') 1136 | else: 1137 | raise e 1138 | 1139 | # I have to do this twice for Solaris. I can't even believe that I figured this out... 1140 | # If waitpid() returns 0 it means that no child process wishes to 1141 | # report, and the value of status is undefined. 1142 | if pid == 0: 1143 | try: 1144 | pid, status = os.waitpid(self.pid, waitpid_options) ### os.WNOHANG) # Solaris! 1145 | except OSError, e: # This should never happen... 1146 | if e[0] == errno.ECHILD: 1147 | raise ExceptionPexpect ('isalive() encountered condition that should never happen. There was no child process. Did someone else call waitpid() on our process?') 1148 | else: 1149 | raise e 1150 | 1151 | # If pid is still 0 after two calls to waitpid() then 1152 | # the process really is alive. This seems to work on all platforms, except 1153 | # for Irix which seems to require a blocking call on waitpid or select, so I let read_nonblocking 1154 | # take care of this situation (unfortunately, this requires waiting through the timeout). 1155 | if pid == 0: 1156 | return True 1157 | 1158 | if pid == 0: 1159 | return True 1160 | 1161 | if os.WIFEXITED (status): 1162 | self.status = status 1163 | self.exitstatus = os.WEXITSTATUS(status) 1164 | self.signalstatus = None 1165 | self.terminated = True 1166 | elif os.WIFSIGNALED (status): 1167 | self.status = status 1168 | self.exitstatus = None 1169 | self.signalstatus = os.WTERMSIG(status) 1170 | self.terminated = True 1171 | elif os.WIFSTOPPED (status): 1172 | raise ExceptionPexpect ('isalive() encountered condition where child process is stopped. This is not supported. Is some other process attempting job control with our child pid?') 1173 | return False 1174 | 1175 | def kill(self, sig): 1176 | 1177 | """This sends the given signal to the child application. In keeping 1178 | with UNIX tradition it has a misleading name. It does not necessarily 1179 | kill the child unless you send the right signal. """ 1180 | 1181 | # Same as os.kill, but the pid is given for you. 1182 | if self.isalive(): 1183 | os.kill(self.pid, sig) 1184 | 1185 | def compile_pattern_list(self, patterns): 1186 | 1187 | """This compiles a pattern-string or a list of pattern-strings. 1188 | Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of 1189 | those. Patterns may also be None which results in an empty list (you 1190 | might do this if waiting for an EOF or TIMEOUT condition without 1191 | expecting any pattern). 1192 | 1193 | This is used by expect() when calling expect_list(). Thus expect() is 1194 | nothing more than:: 1195 | 1196 | cpl = self.compile_pattern_list(pl) 1197 | return self.expect_list(cpl, timeout) 1198 | 1199 | If you are using expect() within a loop it may be more 1200 | efficient to compile the patterns first and then call expect_list(). 1201 | This avoid calls in a loop to compile_pattern_list():: 1202 | 1203 | cpl = self.compile_pattern_list(my_pattern) 1204 | while some_condition: 1205 | ... 1206 | i = self.expect_list(clp, timeout) 1207 | ... 1208 | """ 1209 | 1210 | if patterns is None: 1211 | return [] 1212 | if type(patterns) is not types.ListType: 1213 | patterns = [patterns] 1214 | 1215 | compile_flags = re.DOTALL # Allow dot to match \n 1216 | if self.ignorecase: 1217 | compile_flags = compile_flags | re.IGNORECASE 1218 | compiled_pattern_list = [] 1219 | for p in patterns: 1220 | if type(p) in types.StringTypes: 1221 | compiled_pattern_list.append(re.compile(p, compile_flags)) 1222 | elif p is EOF: 1223 | compiled_pattern_list.append(EOF) 1224 | elif p is TIMEOUT: 1225 | compiled_pattern_list.append(TIMEOUT) 1226 | elif type(p) is type(re.compile('')): 1227 | compiled_pattern_list.append(p) 1228 | else: 1229 | raise TypeError ('Argument must be one of StringTypes, EOF, TIMEOUT, SRE_Pattern, or a list of those type. %s' % str(type(p))) 1230 | 1231 | return compiled_pattern_list 1232 | 1233 | def expect(self, pattern, timeout = -1, searchwindowsize=None): 1234 | 1235 | """This seeks through the stream until a pattern is matched. The 1236 | pattern is overloaded and may take several types. The pattern can be a 1237 | StringType, EOF, a compiled re, or a list of any of those types. 1238 | Strings will be compiled to re types. This returns the index into the 1239 | pattern list. If the pattern was not a list this returns index 0 on a 1240 | successful match. This may raise exceptions for EOF or TIMEOUT. To 1241 | avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern 1242 | list. That will cause expect to match an EOF or TIMEOUT condition 1243 | instead of raising an exception. 1244 | 1245 | If you pass a list of patterns and more than one matches, the first match 1246 | in the stream is chosen. If more than one pattern matches at that point, 1247 | the leftmost in the pattern list is chosen. For example:: 1248 | 1249 | # the input is 'foobar' 1250 | index = p.expect (['bar', 'foo', 'foobar']) 1251 | # returns 1 ('foo') even though 'foobar' is a "better" match 1252 | 1253 | Please note, however, that buffering can affect this behavior, since 1254 | input arrives in unpredictable chunks. For example:: 1255 | 1256 | # the input is 'foobar' 1257 | index = p.expect (['foobar', 'foo']) 1258 | # returns 0 ('foobar') if all input is available at once, 1259 | # but returs 1 ('foo') if parts of the final 'bar' arrive late 1260 | 1261 | After a match is found the instance attributes 'before', 'after' and 1262 | 'match' will be set. You can see all the data read before the match in 1263 | 'before'. You can see the data that was matched in 'after'. The 1264 | re.MatchObject used in the re match will be in 'match'. If an error 1265 | occurred then 'before' will be set to all the data read so far and 1266 | 'after' and 'match' will be None. 1267 | 1268 | If timeout is -1 then timeout will be set to the self.timeout value. 1269 | 1270 | A list entry may be EOF or TIMEOUT instead of a string. This will 1271 | catch these exceptions and return the index of the list entry instead 1272 | of raising the exception. The attribute 'after' will be set to the 1273 | exception type. The attribute 'match' will be None. This allows you to 1274 | write code like this:: 1275 | 1276 | index = p.expect (['good', 'bad', pexpect.EOF, pexpect.TIMEOUT]) 1277 | if index == 0: 1278 | do_something() 1279 | elif index == 1: 1280 | do_something_else() 1281 | elif index == 2: 1282 | do_some_other_thing() 1283 | elif index == 3: 1284 | do_something_completely_different() 1285 | 1286 | instead of code like this:: 1287 | 1288 | try: 1289 | index = p.expect (['good', 'bad']) 1290 | if index == 0: 1291 | do_something() 1292 | elif index == 1: 1293 | do_something_else() 1294 | except EOF: 1295 | do_some_other_thing() 1296 | except TIMEOUT: 1297 | do_something_completely_different() 1298 | 1299 | These two forms are equivalent. It all depends on what you want. You 1300 | can also just expect the EOF if you are waiting for all output of a 1301 | child to finish. For example:: 1302 | 1303 | p = pexpect.spawn('/bin/ls') 1304 | p.expect (pexpect.EOF) 1305 | print p.before 1306 | 1307 | If you are trying to optimize for speed then see expect_list(). 1308 | """ 1309 | 1310 | compiled_pattern_list = self.compile_pattern_list(pattern) 1311 | return self.expect_list(compiled_pattern_list, timeout, searchwindowsize) 1312 | 1313 | def expect_list(self, pattern_list, timeout = -1, searchwindowsize = -1): 1314 | 1315 | """This takes a list of compiled regular expressions and returns the 1316 | index into the pattern_list that matched the child output. The list may 1317 | also contain EOF or TIMEOUT (which are not compiled regular 1318 | expressions). This method is similar to the expect() method except that 1319 | expect_list() does not recompile the pattern list on every call. This 1320 | may help if you are trying to optimize for speed, otherwise just use 1321 | the expect() method. This is called by expect(). If timeout==-1 then 1322 | the self.timeout value is used. If searchwindowsize==-1 then the 1323 | self.searchwindowsize value is used. """ 1324 | 1325 | return self.expect_loop(searcher_re(pattern_list), timeout, searchwindowsize) 1326 | 1327 | def expect_exact(self, pattern_list, timeout = -1, searchwindowsize = -1): 1328 | 1329 | """This is similar to expect(), but uses plain string matching instead 1330 | of compiled regular expressions in 'pattern_list'. The 'pattern_list' 1331 | may be a string; a list or other sequence of strings; or TIMEOUT and 1332 | EOF. 1333 | 1334 | This call might be faster than expect() for two reasons: string 1335 | searching is faster than RE matching and it is possible to limit the 1336 | search to just the end of the input buffer. 1337 | 1338 | This method is also useful when you don't want to have to worry about 1339 | escaping regular expression characters that you want to match.""" 1340 | 1341 | if type(pattern_list) in types.StringTypes or pattern_list in (TIMEOUT, EOF): 1342 | pattern_list = [pattern_list] 1343 | return self.expect_loop(searcher_string(pattern_list), timeout, searchwindowsize) 1344 | 1345 | def expect_loop(self, searcher, timeout = -1, searchwindowsize = -1): 1346 | 1347 | """This is the common loop used inside expect. The 'searcher' should be 1348 | an instance of searcher_re or searcher_string, which describes how and what 1349 | to search for in the input. 1350 | 1351 | See expect() for other arguments, return value and exceptions. """ 1352 | 1353 | self.searcher = searcher 1354 | 1355 | if timeout == -1: 1356 | timeout = self.timeout 1357 | if timeout is not None: 1358 | end_time = time.time() + timeout 1359 | if searchwindowsize == -1: 1360 | searchwindowsize = self.searchwindowsize 1361 | 1362 | try: 1363 | incoming = self.buffer 1364 | freshlen = len(incoming) 1365 | while True: # Keep reading until exception or return. 1366 | index = searcher.search(incoming, freshlen, searchwindowsize) 1367 | if index >= 0: 1368 | self.buffer = incoming[searcher.end : ] 1369 | self.before = incoming[ : searcher.start] 1370 | self.after = incoming[searcher.start : searcher.end] 1371 | self.match = searcher.match 1372 | self.match_index = index 1373 | return self.match_index 1374 | # No match at this point 1375 | if timeout < 0 and timeout is not None: 1376 | raise TIMEOUT ('Timeout exceeded in expect_any().') 1377 | # Still have time left, so read more data 1378 | c = self.read_nonblocking (self.maxread, timeout) 1379 | freshlen = len(c) 1380 | time.sleep (0.0001) 1381 | incoming = incoming + c 1382 | if timeout is not None: 1383 | timeout = end_time - time.time() 1384 | except EOF, e: 1385 | self.buffer = '' 1386 | self.before = incoming 1387 | self.after = EOF 1388 | index = searcher.eof_index 1389 | if index >= 0: 1390 | self.match = EOF 1391 | self.match_index = index 1392 | return self.match_index 1393 | else: 1394 | self.match = None 1395 | self.match_index = None 1396 | raise EOF (str(e) + '\n' + str(self)) 1397 | except TIMEOUT, e: 1398 | self.buffer = incoming 1399 | self.before = incoming 1400 | self.after = TIMEOUT 1401 | index = searcher.timeout_index 1402 | if index >= 0: 1403 | self.match = TIMEOUT 1404 | self.match_index = index 1405 | return self.match_index 1406 | else: 1407 | self.match = None 1408 | self.match_index = None 1409 | raise TIMEOUT (str(e) + '\n' + str(self)) 1410 | except: 1411 | self.before = incoming 1412 | self.after = None 1413 | self.match = None 1414 | self.match_index = None 1415 | raise 1416 | 1417 | def getwinsize(self): 1418 | 1419 | """This returns the terminal window size of the child tty. The return 1420 | value is a tuple of (rows, cols). """ 1421 | 1422 | TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912L) 1423 | s = struct.pack('HHHH', 0, 0, 0, 0) 1424 | x = fcntl.ioctl(self.fileno(), TIOCGWINSZ, s) 1425 | return struct.unpack('HHHH', x)[0:2] 1426 | 1427 | def setwinsize(self, r, c): 1428 | 1429 | """This sets the terminal window size of the child tty. This will cause 1430 | a SIGWINCH signal to be sent to the child. This does not change the 1431 | physical window size. It changes the size reported to TTY-aware 1432 | applications like vi or curses -- applications that respond to the 1433 | SIGWINCH signal. """ 1434 | 1435 | # Check for buggy platforms. Some Python versions on some platforms 1436 | # (notably OSF1 Alpha and RedHat 7.1) truncate the value for 1437 | # termios.TIOCSWINSZ. It is not clear why this happens. 1438 | # These platforms don't seem to handle the signed int very well; 1439 | # yet other platforms like OpenBSD have a large negative value for 1440 | # TIOCSWINSZ and they don't have a truncate problem. 1441 | # Newer versions of Linux have totally different values for TIOCSWINSZ. 1442 | # Note that this fix is a hack. 1443 | TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) 1444 | if TIOCSWINSZ == 2148037735L: # L is not required in Python >= 2.2. 1445 | TIOCSWINSZ = -2146929561 # Same bits, but with sign. 1446 | # Note, assume ws_xpixel and ws_ypixel are zero. 1447 | s = struct.pack('HHHH', r, c, 0, 0) 1448 | fcntl.ioctl(self.fileno(), TIOCSWINSZ, s) 1449 | 1450 | def interact(self, escape_character = chr(29), input_filter = None, output_filter = None): 1451 | 1452 | """This gives control of the child process to the interactive user (the 1453 | human at the keyboard). Keystrokes are sent to the child process, and 1454 | the stdout and stderr output of the child process is printed. This 1455 | simply echos the child stdout and child stderr to the real stdout and 1456 | it echos the real stdin to the child stdin. When the user types the 1457 | escape_character this method will stop. The default for 1458 | escape_character is ^]. This should not be confused with ASCII 27 -- 1459 | the ESC character. ASCII 29 was chosen for historical merit because 1460 | this is the character used by 'telnet' as the escape character. The 1461 | escape_character will not be sent to the child process. 1462 | 1463 | You may pass in optional input and output filter functions. These 1464 | functions should take a string and return a string. The output_filter 1465 | will be passed all the output from the child process. The input_filter 1466 | will be passed all the keyboard input from the user. The input_filter 1467 | is run BEFORE the check for the escape_character. 1468 | 1469 | Note that if you change the window size of the parent the SIGWINCH 1470 | signal will not be passed through to the child. If you want the child 1471 | window size to change when the parent's window size changes then do 1472 | something like the following example:: 1473 | 1474 | import pexpect, struct, fcntl, termios, signal, sys 1475 | def sigwinch_passthrough (sig, data): 1476 | s = struct.pack("HHHH", 0, 0, 0, 0) 1477 | a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ , s)) 1478 | global p 1479 | p.setwinsize(a[0],a[1]) 1480 | p = pexpect.spawn('/bin/bash') # Note this is global and used in sigwinch_passthrough. 1481 | signal.signal(signal.SIGWINCH, sigwinch_passthrough) 1482 | p.interact() 1483 | """ 1484 | 1485 | # Flush the buffer. 1486 | self.stdout.write (self.buffer) 1487 | self.stdout.flush() 1488 | self.buffer = '' 1489 | mode = tty.tcgetattr(self.STDIN_FILENO) 1490 | tty.setraw(self.STDIN_FILENO) 1491 | try: 1492 | self.__interact_copy(escape_character, input_filter, output_filter) 1493 | finally: 1494 | tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode) 1495 | 1496 | def __interact_writen(self, fd, data): 1497 | 1498 | """This is used by the interact() method. 1499 | """ 1500 | 1501 | while data != '' and self.isalive(): 1502 | n = os.write(fd, data) 1503 | data = data[n:] 1504 | 1505 | def __interact_read(self, fd): 1506 | 1507 | """This is used by the interact() method. 1508 | """ 1509 | 1510 | return os.read(fd, 1000) 1511 | 1512 | def __interact_copy(self, escape_character = None, input_filter = None, output_filter = None): 1513 | 1514 | """This is used by the interact() method. 1515 | """ 1516 | 1517 | while self.isalive(): 1518 | r,w,e = self.__select([self.child_fd, self.STDIN_FILENO], [], []) 1519 | if self.child_fd in r: 1520 | data = self.__interact_read(self.child_fd) 1521 | if output_filter: data = output_filter(data) 1522 | if self.logfile is not None: 1523 | self.logfile.write (data) 1524 | self.logfile.flush() 1525 | os.write(self.STDOUT_FILENO, data) 1526 | if self.STDIN_FILENO in r: 1527 | data = self.__interact_read(self.STDIN_FILENO) 1528 | if input_filter: data = input_filter(data) 1529 | i = data.rfind(escape_character) 1530 | if i != -1: 1531 | data = data[:i] 1532 | self.__interact_writen(self.child_fd, data) 1533 | break 1534 | self.__interact_writen(self.child_fd, data) 1535 | 1536 | def __select (self, iwtd, owtd, ewtd, timeout=None): 1537 | 1538 | """This is a wrapper around select.select() that ignores signals. If 1539 | select.select raises a select.error exception and errno is an EINTR 1540 | error then it is ignored. Mainly this is used to ignore sigwinch 1541 | (terminal resize). """ 1542 | 1543 | # if select() is interrupted by a signal (errno==EINTR) then 1544 | # we loop back and enter the select() again. 1545 | if timeout is not None: 1546 | end_time = time.time() + timeout 1547 | while True: 1548 | try: 1549 | return select.select (iwtd, owtd, ewtd, timeout) 1550 | except select.error, e: 1551 | if e[0] == errno.EINTR: 1552 | # if we loop back we have to subtract the amount of time we already waited. 1553 | if timeout is not None: 1554 | timeout = end_time - time.time() 1555 | if timeout < 0: 1556 | return ([],[],[]) 1557 | else: # something else caused the select.error, so this really is an exception 1558 | raise 1559 | 1560 | ############################################################################## 1561 | # The following methods are no longer supported or allowed. 1562 | 1563 | def setmaxread (self, maxread): 1564 | 1565 | """This method is no longer supported or allowed. I don't like getters 1566 | and setters without a good reason. """ 1567 | 1568 | raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the maxread member variable.') 1569 | 1570 | def setlog (self, fileobject): 1571 | 1572 | """This method is no longer supported or allowed. 1573 | """ 1574 | 1575 | raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the logfile member variable.') 1576 | 1577 | ############################################################################## 1578 | # End of spawn class 1579 | ############################################################################## 1580 | 1581 | class searcher_string (object): 1582 | 1583 | """This is a plain string search helper for the spawn.expect_any() method. 1584 | 1585 | Attributes: 1586 | 1587 | eof_index - index of EOF, or -1 1588 | timeout_index - index of TIMEOUT, or -1 1589 | 1590 | After a successful match by the search() method the following attributes 1591 | are available: 1592 | 1593 | start - index into the buffer, first byte of match 1594 | end - index into the buffer, first byte after match 1595 | match - the matching string itself 1596 | """ 1597 | 1598 | def __init__(self, strings): 1599 | 1600 | """This creates an instance of searcher_string. This argument 'strings' 1601 | may be a list; a sequence of strings; or the EOF or TIMEOUT types. """ 1602 | 1603 | self.eof_index = -1 1604 | self.timeout_index = -1 1605 | self._strings = [] 1606 | for n, s in zip(range(len(strings)), strings): 1607 | if s is EOF: 1608 | self.eof_index = n 1609 | continue 1610 | if s is TIMEOUT: 1611 | self.timeout_index = n 1612 | continue 1613 | self._strings.append((n, s)) 1614 | 1615 | def __str__(self): 1616 | 1617 | """This returns a human-readable string that represents the state of 1618 | the object.""" 1619 | 1620 | ss = [ (ns[0],' %d: "%s"' % ns) for ns in self._strings ] 1621 | ss.append((-1,'searcher_string:')) 1622 | if self.eof_index >= 0: 1623 | ss.append ((self.eof_index,' %d: EOF' % self.eof_index)) 1624 | if self.timeout_index >= 0: 1625 | ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index)) 1626 | ss.sort() 1627 | ss = zip(*ss)[1] 1628 | return '\n'.join(ss) 1629 | 1630 | def search(self, buffer, freshlen, searchwindowsize=None): 1631 | 1632 | """This searches 'buffer' for the first occurence of one of the search 1633 | strings. 'freshlen' must indicate the number of bytes at the end of 1634 | 'buffer' which have not been searched before. It helps to avoid 1635 | searching the same, possibly big, buffer over and over again. 1636 | 1637 | See class spawn for the 'searchwindowsize' argument. 1638 | 1639 | If there is a match this returns the index of that string, and sets 1640 | 'start', 'end' and 'match'. Otherwise, this returns -1. """ 1641 | 1642 | absurd_match = len(buffer) 1643 | first_match = absurd_match 1644 | 1645 | # 'freshlen' helps a lot here. Further optimizations could 1646 | # possibly include: 1647 | # 1648 | # using something like the Boyer-Moore Fast String Searching 1649 | # Algorithm; pre-compiling the search through a list of 1650 | # strings into something that can scan the input once to 1651 | # search for all N strings; realize that if we search for 1652 | # ['bar', 'baz'] and the input is '...foo' we need not bother 1653 | # rescanning until we've read three more bytes. 1654 | # 1655 | # Sadly, I don't know enough about this interesting topic. /grahn 1656 | 1657 | for index, s in self._strings: 1658 | if searchwindowsize is None: 1659 | # the match, if any, can only be in the fresh data, 1660 | # or at the very end of the old data 1661 | offset = -(freshlen+len(s)) 1662 | else: 1663 | # better obey searchwindowsize 1664 | offset = -searchwindowsize 1665 | n = buffer.find(s, offset) 1666 | if n >= 0 and n < first_match: 1667 | first_match = n 1668 | best_index, best_match = index, s 1669 | if first_match == absurd_match: 1670 | return -1 1671 | self.match = best_match 1672 | self.start = first_match 1673 | self.end = self.start + len(self.match) 1674 | return best_index 1675 | 1676 | class searcher_re (object): 1677 | 1678 | """This is regular expression string search helper for the 1679 | spawn.expect_any() method. 1680 | 1681 | Attributes: 1682 | 1683 | eof_index - index of EOF, or -1 1684 | timeout_index - index of TIMEOUT, or -1 1685 | 1686 | After a successful match by the search() method the following attributes 1687 | are available: 1688 | 1689 | start - index into the buffer, first byte of match 1690 | end - index into the buffer, first byte after match 1691 | match - the re.match object returned by a succesful re.search 1692 | 1693 | """ 1694 | 1695 | def __init__(self, patterns): 1696 | 1697 | """This creates an instance that searches for 'patterns' Where 1698 | 'patterns' may be a list or other sequence of compiled regular 1699 | expressions, or the EOF or TIMEOUT types.""" 1700 | 1701 | self.eof_index = -1 1702 | self.timeout_index = -1 1703 | self._searches = [] 1704 | for n, s in zip(range(len(patterns)), patterns): 1705 | if s is EOF: 1706 | self.eof_index = n 1707 | continue 1708 | if s is TIMEOUT: 1709 | self.timeout_index = n 1710 | continue 1711 | self._searches.append((n, s)) 1712 | 1713 | def __str__(self): 1714 | 1715 | """This returns a human-readable string that represents the state of 1716 | the object.""" 1717 | 1718 | ss = [ (n,' %d: re.compile("%s")' % (n,str(s.pattern))) for n,s in self._searches] 1719 | ss.append((-1,'searcher_re:')) 1720 | if self.eof_index >= 0: 1721 | ss.append ((self.eof_index,' %d: EOF' % self.eof_index)) 1722 | if self.timeout_index >= 0: 1723 | ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index)) 1724 | ss.sort() 1725 | ss = zip(*ss)[1] 1726 | return '\n'.join(ss) 1727 | 1728 | def search(self, buffer, freshlen, searchwindowsize=None): 1729 | 1730 | """This searches 'buffer' for the first occurence of one of the regular 1731 | expressions. 'freshlen' must indicate the number of bytes at the end of 1732 | 'buffer' which have not been searched before. 1733 | 1734 | See class spawn for the 'searchwindowsize' argument. 1735 | 1736 | If there is a match this returns the index of that string, and sets 1737 | 'start', 'end' and 'match'. Otherwise, returns -1.""" 1738 | 1739 | absurd_match = len(buffer) 1740 | first_match = absurd_match 1741 | # 'freshlen' doesn't help here -- we cannot predict the 1742 | # length of a match, and the re module provides no help. 1743 | if searchwindowsize is None: 1744 | searchstart = 0 1745 | else: 1746 | searchstart = max(0, len(buffer)-searchwindowsize) 1747 | for index, s in self._searches: 1748 | match = s.search(buffer, searchstart) 1749 | if match is None: 1750 | continue 1751 | n = match.start() 1752 | if n < first_match: 1753 | first_match = n 1754 | the_match = match 1755 | best_index = index 1756 | if first_match == absurd_match: 1757 | return -1 1758 | self.start = first_match 1759 | self.match = the_match 1760 | self.end = self.match.end() 1761 | return best_index 1762 | 1763 | def which (filename): 1764 | 1765 | """This takes a given filename; tries to find it in the environment path; 1766 | then checks if it is executable. This returns the full path to the filename 1767 | if found and executable. Otherwise this returns None.""" 1768 | 1769 | # Special case where filename already contains a path. 1770 | if os.path.dirname(filename) != '': 1771 | if os.access (filename, os.X_OK): 1772 | return filename 1773 | 1774 | if not os.environ.has_key('PATH') or os.environ['PATH'] == '': 1775 | p = os.defpath 1776 | else: 1777 | p = os.environ['PATH'] 1778 | 1779 | # Oddly enough this was the one line that made Pexpect 1780 | # incompatible with Python 1.5.2. 1781 | #pathlist = p.split (os.pathsep) 1782 | pathlist = string.split (p, os.pathsep) 1783 | 1784 | for path in pathlist: 1785 | f = os.path.join(path, filename) 1786 | if os.access(f, os.X_OK): 1787 | return f 1788 | return None 1789 | 1790 | def split_command_line(command_line): 1791 | 1792 | """This splits a command line into a list of arguments. It splits arguments 1793 | on spaces, but handles embedded quotes, doublequotes, and escaped 1794 | characters. It's impossible to do this with a regular expression, so I 1795 | wrote a little state machine to parse the command line. """ 1796 | 1797 | arg_list = [] 1798 | arg = '' 1799 | 1800 | # Constants to name the states we can be in. 1801 | state_basic = 0 1802 | state_esc = 1 1803 | state_singlequote = 2 1804 | state_doublequote = 3 1805 | state_whitespace = 4 # The state of consuming whitespace between commands. 1806 | state = state_basic 1807 | 1808 | for c in command_line: 1809 | if state == state_basic or state == state_whitespace: 1810 | if c == '\\': # Escape the next character 1811 | state = state_esc 1812 | elif c == r"'": # Handle single quote 1813 | state = state_singlequote 1814 | elif c == r'"': # Handle double quote 1815 | state = state_doublequote 1816 | elif c.isspace(): 1817 | # Add arg to arg_list if we aren't in the middle of whitespace. 1818 | if state == state_whitespace: 1819 | None # Do nothing. 1820 | else: 1821 | arg_list.append(arg) 1822 | arg = '' 1823 | state = state_whitespace 1824 | else: 1825 | arg = arg + c 1826 | state = state_basic 1827 | elif state == state_esc: 1828 | arg = arg + c 1829 | state = state_basic 1830 | elif state == state_singlequote: 1831 | if c == r"'": 1832 | state = state_basic 1833 | else: 1834 | arg = arg + c 1835 | elif state == state_doublequote: 1836 | if c == r'"': 1837 | state = state_basic 1838 | else: 1839 | arg = arg + c 1840 | 1841 | if arg != '': 1842 | arg_list.append(arg) 1843 | return arg_list 1844 | 1845 | # vi:ts=4:sw=4:expandtab:ft=python: 1846 | --------------------------------------------------------------------------------