1

I have MySQL database on a remote server and before accessing it I would need to do port forwarding using ssh tunnel. Without python I usually execute the below command and type in my password in the shell. Once authenticated I access the database in mysql workbench by connecting to localhost:3306. I do everything from my Mac OS X El Capitan

ssh -L 3306:remote.database.host:3306 xxxx@xxxx.com

I would now like to access the database from Python and here is the script I used:

import pexpect
import time
import sqlalchemy as sql
from sqlalchemy.engine.url import URL

myDB = URL(drivername='mysql+pymysql', host='localhost',
database='test_db',
username='roott',
password='xxxxxx',
port=3306)

child = pexpect.spawnu('ssh -L 3306:remote.database.host:3306 xxxx@xxxx.com')
child.expect (u'password:')
child.sendline ('xxxxx')

while child.isalive():
    try:
    engine = sql.create_engine(name_or_url=myDB)
    connection = engine.connect()
    connection.close()
    break
finally:
    child.close()

Above script is giving me the following error:

sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on 'localhost' ([Errno 61] Connection refused)")

I also tried using sshtunnelforwarder by referring to jsjc's solution at Python - SSH Tunnel Setup and MySQL DB Access and it doesn't work either.

from sshtunnel import SSHTunnelForwarder
from sqlalchemy import create_engine
from sqlalchemy.engine.url import URL

with SSHTunnelForwarder(('ssh_host', 22),
                    ssh_username='xxxxx',
                    ssh_pkey='/path/to/key/file',
                    local_bind_address=('127.0.0.1', 3306),
                    remote_bind_address=('127.0.0.1', 3306)) as server:


myDB = URL(drivername='mysql+pymysql', host='127.0.0.1',
           database='test_db',
           username='roott',
           password='xxxxx',
           port=3306
           )

engine = create_engine(name_or_url=myDB)
connection = engine.connect()    
connection.close()

I get the below information logged into the console and nothing happens, it just hangs in there, I have to manually kill the program.

DEBUG:paramiko.transport:starting thread (client mode): 0xbe25940
DEBUG:paramiko.transport:Local version/idstring: SSH-2.0-paramiko_1.16.0
DEBUG:paramiko.transport:Remote version/idstring: SSH-2.0-OpenSSH_6.2
INFO:paramiko.transport:Connected (version 2.0, client OpenSSH_6.2)
DEBUG:paramiko.transport:........
DEBUG:paramiko.transport:Kex agreed: diffie-hellman-group1-sha1
DEBUG:paramiko.transport:Cipher agreed: aes128-ctr
DEBUG:paramiko.transport:MAC agreed: hmac-sha2-256
DEBUG:paramiko.transport:Compression agreed: none
DEBUG:paramiko.transport:kex engine KexGroup1 specified hash_algo <built-in function openssl_sha1>
DEBUG:paramiko.transport:Switch to new keys ...
DEBUG:paramiko.transport:Attempting public-key auth...
DEBUG:paramiko.transport:userauth is OK
INFO:paramiko.transport:Auth banner: b'Amazon Linux AMI release 2014.09\nKernel \\r on an \\m\n'
INFO:paramiko.transport:Authentication continues...
DEBUG:paramiko.transport:Methods: ['password']
DEBUG:paramiko.transport:[chan 0] Max packet in: 32768 bytes
WARNING:paramiko.transport:Oops, unhandled type 3
Community
  • 1
  • 1
user2714753
  • 115
  • 3
  • 11

2 Answers2

0

This is an issue that I have encountered before. The issue is that when using localhost as the database server, the driver insists on connecting to the local unix socket rather than connecting via the loopback interface using the specified port.

The workaround is to change the database server to be something besides localhost. This will cause the driver to use the server/port you specified rather than attempting to connect to the local socket.

myDB = URL(drivername='mysql+pymysql', host='127.0.0.1', ...)
myDB = URL(drivername='mysql+pymysql', host='your.ip.add.ress', ...)
myDB = URL(drivername='mysql+pymysql', host='your.hostname', ...)
Suever
  • 64,497
  • 14
  • 82
  • 101
  • I only have read access to the database server and its not possible to change it to something other than localhost. Is there another alternative? – user2714753 Mar 18 '16 at 17:15
  • @user2714753 I'm not talking about changing the database *server*. I'm talking about changing your input params to your driver here in the python script so that it actually uses the port you ask it to. – Suever Mar 18 '16 at 17:20
  • sorry for the confusion. I tired with my ipaddress and also with hostname, the terminal just hangs for a while and then throws this error. sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2013, 'Lost connection to MySQL server during query') – user2714753 Mar 18 '16 at 21:54
0

Idk if OP has figured out the problem (has been 3 years so I hope so), but my solution was extremely simple. I just had to indent myDB, engine, and connection definitions inside the "with" clause. Otherwise, the ssh tunnel would close before you have any chance to connect to the database:

with SSHTunnelForwarder(('ipforsshing', sshport), ssh_username='sshuser', ssh_pkey='privatekeypath', local_bind_address=('127.0.0.1', 3305), remote_bind_address=('127.0.0.1', 3306)) as server:
    adcrawl_db = URL(drivername='mysql+pymysql', host='127.0.0.1', database='dbname', username='dbusername',password='dbpassword', port=3305)

    engine = create_engine(name_or_url=adcrawl_db)
    connection = engine.connect()
    print("CONNECTED!")
    connection.close()

Hope this helps anyone in the future.

pregenRobot
  • 147
  • 1
  • 13