universql icon indicating copy to clipboard operation
universql copied to clipboard

node client does not connect

Open dforsber opened this issue 1 year ago • 1 comments

I'm using snowflake-sdk with NodeJS to connect and run simple show tables query and it works when running directly against snowflake API. However, trying to run universql with docker-compose (no certs generated) and using host "localhost" the node client does not get connection id anymore.

If I comment out the accessUrl it works directly with snowflake API, but if I enable it, it does not get the connection Id.

HOST=localhost SERVER_PORT=8088 SNOWFLAKE_ACCOUNT='...' docker-compose up universql-server
import snowflake from "snowflake-sdk";

async function main() {
  const account = process.env["SNOWFLAKE_ACCOUNT"];
  const username = process.env["SNOWFLAKE_USER"];
  console.log("\n\n==== Connecting to Snowflake\n\n");
  const conn = snowflake.createConnection({
    account,
    username,
    password: process.env["SNOWFLAKE_PW"],
    application: "testing-nodejs",
    accessUrl: "http://localhost:8088",
    // host,
    // host: "localhost",
    // port: 8088,
    // protocol: "http",
    // proxyHost: "localhost",
    // proxyPort: 8088,
    // proxyProtocol: "http",
    // timeout: 10,
    // browserActionTimeout: 5,
    authenticator: "SNOWFLAKE",
  });
  console.log("\n\n==== Connected to Snowflake. Fetching connection Id..");
  const connection_ID = await new Promise((resolve, reject) => {
    conn.connect((err, conn) => {
      console.log("..... ");
      if (err) return reject(err);
      resolve(conn.getId());
    });
  });
  console.log("\n\n==== Successfully connected to Snowflake.", connection_ID);

  const res = await new Promise((resolve, reject) => {
    conn.execute({
      sqlText: "SHOW TABLES;",
      asyncExec: true,
      complete: async function (err, stmt, rows) {
        if (err) return reject(err);
        let queryId = stmt.getQueryId();
        if (!queryId) return reject("No query ID found!");
        console.log("\n\n==== queryId:", queryId);
        conn.getResultsFromQueryId({
          queryId,
          complete: async function (err, _stmt, rows) {
            if (err) return reject(err);
            resolve(rows);
          },
        });
      },
    });
  });
  const mapped = res
    .filter((r) => r.database_name == "BOILINGDATA")
    .map((r) => ({
      name: r.name,
      database_name: r.database_name,
      schema_name: r.schema_name,
      kind: r.kind,
      comment: r.comment,
    }));
  console.log(mapped);
}

main()
  .catch(console.error)
  .then(() => console.log("DONE"));

Success case (no accessUrl used)

% SNOWFLAKE_ACCOUNT='...' SNOWFLAKE_USER='...' SNOWFLAKE_PW='...' node src/index.mjs


==== Connecting to Snowflake


{"level":"INFO","message":"[1:37:29.695 PM]: Creating new connection object"}
{"level":"INFO","message":"[1:37:29.699 PM]: Creating Connection[id: 8ca3c0d4-fc66-4b79-8682-f360c3ee0a83] with host: mf75098.eu-west-1.snowflakecomputing.com, account: mf75098, accessUrl: https://mf75098.eu-west-1.snowflakecomputing.com, user: dforsber, password is provided, role: undefined, database: undefined, schema: undefined, warehouse: undefined, region: eu-west-1, authenticator: SNOWFLAKE, ocsp mode: FAIL_OPEN, os: darwin, os version: 23.6.0"}
{"level":"INFO","message":"[1:37:29.700 PM]: Connection[id: 8ca3c0d4-fc66-4b79-8682-f360c3ee0a83] additional details: passcode in password is provided, passcode is not provided, private key is not provided, application: testing-nodejs, client name: snowflake-sdk, client version: 1.15.0, retry timeout: 300, private key path: undefined, private key pass is not provided, client store temporary credential: false, browser response timeout: 120000"}
{"level":"INFO","message":"[1:37:29.700 PM]: Connection[id: 8ca3c0d4-fc66-4b79-8682-f360c3ee0a83] - connection object created successfully."}


==== Connected to Snowflake. Fetching connection Id..
{"level":"INFO","message":"[1:37:29.700 PM]: Connection[id: 8ca3c0d4-fc66-4b79-8682-f360c3ee0a83] - connecting. Associated Snowflake domain: GLOBAL"}
{"level":"INFO","message":"[1:37:29.700 PM]: Connection[id: 8ca3c0d4-fc66-4b79-8682-f360c3ee0a83] - authentication successful using: SNOWFLAKE"}
{"level":"INFO","message":"[1:37:29.701 PM]: Trying to initialize Easy Logging"}
{"level":"INFO","message":"[1:37:29.702 PM]: No client config detected."}
{"level":"INFO","message":"[1:37:29.702 PM]: No config file path found. Client config will not be used."}
{"level":"INFO","message":"[1:37:29.702 PM]: Easy Logging is disabled as no config has been found"}
{"level":"INFO","message":"[1:37:29.704 PM]: Connection[id: 8ca3c0d4-fc66-4b79-8682-f360c3ee0a83] - connected successfully after 3.3667080104351044 milliseconds"}
..... 


==== Successfully connected to Snowflake. 8ca3c0d4-fc66-4b79-8682-f360c3ee0a83


==== queryId: 01b85d59-0205-65f5-0000-000276ae37b5
[
  {
    name: 'CUSTOMER',
    database_name: 'BOILINGDATA',
    schema_name: 'TPCH_SF1',
    kind: 'TABLE',
    comment: ''
  },
  {
    name: 'DYNAMIC_ICEBERG_TABLE_CUSTOMER',
    database_name: 'BOILINGDATA',
    schema_name: 'TPCH_SF1',
    kind: 'TABLE',
    comment: ''
  }
]
DONE

Error case (accessUrl as http://localhost:8088/)

Hangs..

% NODE_DEBUG=http,http2,tls SNOWFLAKE_ACCOUNT='...' SNOWFLAKE_USER='...' SNOWFLAKE_PW='...' node src/index.mjs

==== Connecting to Snowflake


{"level":"INFO","message":"[1:40:15.132 PM]: Creating new connection object"}
{"level":"INFO","message":"[1:40:15.137 PM]: Creating Connection[id: cbf656e5-d022-4544-a017-1ff970bc23d3] with host: localhost, account: mf75098, accessUrl: http://localhost:8088, user: dforsber, password is provided, role: undefined, database: undefined, schema: undefined, warehouse: undefined, region: eu-west-1, authenticator: SNOWFLAKE, ocsp mode: FAIL_OPEN, os: darwin, os version: 23.6.0"}
{"level":"INFO","message":"[1:40:15.137 PM]: Connection[id: cbf656e5-d022-4544-a017-1ff970bc23d3] additional details: passcode in password is provided, passcode is not provided, private key is not provided, application: testing-nodejs, client name: snowflake-sdk, client version: 1.15.0, retry timeout: 300, private key path: undefined, private key pass is not provided, client store temporary credential: false, browser response timeout: 120000"}
{"level":"INFO","message":"[1:40:15.137 PM]: Connection[id: cbf656e5-d022-4544-a017-1ff970bc23d3] - connection object created successfully."}


==== Connected to Snowflake. Fetching connection Id..
{"level":"INFO","message":"[1:40:15.138 PM]: Connection[id: cbf656e5-d022-4544-a017-1ff970bc23d3] - connecting. Associated Snowflake domain: GLOBAL"}
{"level":"INFO","message":"[1:40:15.138 PM]: Connection[id: cbf656e5-d022-4544-a017-1ff970bc23d3] - authentication successful using: SNOWFLAKE"}
{"level":"INFO","message":"[1:40:15.138 PM]: Trying to initialize Easy Logging"}
{"level":"INFO","message":"[1:40:15.140 PM]: No client config detected."}
{"level":"INFO","message":"[1:40:15.140 PM]: No config file path found. Client config will not be used."}
{"level":"INFO","message":"[1:40:15.140 PM]: Easy Logging is disabled as no config has been found"}
{"level":"INFO","message":"[1:40:15.141 PM]: Connection[id: cbf656e5-d022-4544-a017-1ff970bc23d3] - connected successfully after 3.3304579854011536 milliseconds"}
HTTP 58242: call onSocket 0 0
HTTP 58242: createConnection localhost:8088: [Object: null prototype] {
  maxRedirects: 21,
  maxBodyLength: Infinity,
  protocol: 'http:',
  path: null,
  method: 'POST',
  headers: [Object: null prototype] {
    Accept: 'application/json',
    'Content-Type': 'application/json',
    'user-agent': 'JavaScript/1.15.0 (darwin-arm64) NodeJS/20.9.0',
    CLIENT_APP_VERSION: '1.15.0',
    CLIENT_APP_ID: 'JavaScript',
    'Content-Length': '807',
    'Accept-Encoding': 'gzip, compress, deflate, br'
  },
  agents: {
    http: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 80,
      protocol: 'http:',
      options: [Object: null prototype],
      requests: [Object: null prototype] {},
      sockets: [Object: null prototype],
      freeSockets: [Object: null prototype] {},
      keepAliveMsecs: 1000,
      keepAlive: true,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'lifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 0,
      [Symbol(kCapture)]: false
    },
    https: undefined
  },
  auth: undefined,
  family: undefined,
  beforeRedirect: [Function: dispatchBeforeRedirect],
  beforeRedirects: { proxy: [Function: beforeRedirect] },
  hostname: 'localhost',
  port: '8088',
  agent: Agent {
    _events: [Object: null prototype] {
      free: [Function (anonymous)],
      newListener: [Function: maybeEnableKeylog]
    },
    _eventsCount: 2,
    _maxListeners: undefined,
    defaultPort: 80,
    protocol: 'http:',
    options: [Object: null prototype] {
      protocol: 'http:',
      hostname: 'localhost',
      keepAlive: true,
      noDelay: true,
      path: null
    },
    requests: [Object: null prototype] {},
    sockets: [Object: null prototype] { 'localhost:8088:': [] },
    freeSockets: [Object: null prototype] {},
    keepAliveMsecs: 1000,
    keepAlive: true,
    maxSockets: Infinity,
    maxFreeSockets: 256,
    scheduling: 'lifo',
    maxTotalSockets: Infinity,
    totalSocketCount: 0,
    [Symbol(kCapture)]: false
  },
  nativeProtocols: {
    'http:': {
      _connectionListener: [Function: connectionListener],
      METHODS: [Array],
      STATUS_CODES: [Object],
      Agent: [Function],
      ClientRequest: [Function: ClientRequest],
      IncomingMessage: [Function: IncomingMessage],
      OutgoingMessage: [Function: OutgoingMessage],
      Server: [Function: Server],
      ServerResponse: [Function: ServerResponse],
      createServer: [Function: createServer],
      validateHeaderName: [Function: __node_internal_],
      validateHeaderValue: [Function: __node_internal_],
      get: [Function: get],
      request: [Function: request],
      setMaxIdleHTTPParsers: [Function: setMaxIdleHTTPParsers],
      maxHeaderSize: [Getter],
      globalAgent: [Getter/Setter]
    },
    'https:': {
      Agent: [Function: Agent],
      globalAgent: [Agent],
      Server: [Function: Server],
      createServer: [Function: createServer],
      get: [Function: get],
      request: [Function: request]
    }
  },
  pathname: '/session/v1/login-request',
  search: '?requestId=91661290-0feb-488a-aab3-6042f2bf87fc',
  host: 'localhost',
  keepAlive: true,
  noDelay: true,
  servername: 'localhost',
  _agentKey: 'localhost:8088:'
}
HTTP 58242: sockets localhost:8088: 1 1
HTTP 58242: write ret = true
(node:58242) Warning: Setting the NODE_DEBUG environment variable to 'http' can expose sensitive data (such as passwords, tokens and authentication headers) in the resulting log.
(Use `node --trace-warnings ...` to show where the warning was created)
HTTP 58242: outgoing message end.
HTTP 58242: CLIENT socket onClose
HTTP 58242: removeSocket localhost:8088: writable: false
HTTP 58242: HTTP socket close
...
<retries...>

dforsber avatar Nov 14 '24 11:11 dforsber

Using HOST pointing to localhost is not supported because Snowflake clients require signed certificates and self-signed certificates don't work unfortunately.

Universql ships the SSL certificate for a public domain localhostcomputing.com, that points to localhost. That way no data will be sent outside of your localhost and Snowflake clients can work seamlessly.

If you would like to use your domain, you can set your public domain as HOST and enable Universql to use your cert as follows: https://github.com/buremba/universql/blob/main/universql/main.py#L58

Let me know if that works for you or if you have any alternative approach!

buremba avatar Nov 15 '24 22:11 buremba