arangodb / arangojs

The official ArangoDB JavaScript driver.
https://arangodb.github.io/arangojs
Apache License 2.0
600 stars 106 forks source link

Existence check throws error inside docker-compose network #736

Closed Myrmod closed 2 years ago

Myrmod commented 3 years ago

Hello,

I am using arangojs the following way:

import { Database } from 'arangojs'
import { collections as neededCollections } from './structure.js'
import dotenv from 'dotenv'

const { DB_PORT, DB_NAME, DB_URL, DB_USERNAME, DB_PASSWORD } = dotenv.config().parsed!

export default class DB {
  private static instance: DB
  private static database: Database | undefined

  private constructor() {
    try {
      if (!DB_URL) throw new Error('no database url provided')
      if (!DB_PORT) throw new Error('no database port provided')
      if (!DB_NAME) throw new Error('no database name provided')
      if (!DB_USERNAME) throw new Error('no database username provided')
      if (!DB_PASSWORD) throw new Error('no database password provided')

      DB.database = new Database({
        url: `${DB_URL}:${DB_PORT}`,
        auth: {
          username: DB_USERNAME,
          password: DB_PASSWORD,
        },
      })
    } catch (error) {
      console.error('DB.constructor', error)
    }
  }

  public static async getInstance() {
    if (!DB.instance) DB.instance = new DB()

    return DB.instance
  }

  public async initializeDB(): Promise<void> {
    if (!DB.database) return
    console.info('initialize db...')
    const defaultDB = DB.database

    DB.database = DB.database.database(DB_NAME)
    console.info('checking if database exists...')
    // error comes from DB.database.exists()
    const databaseExists = await DB.database.exists().catch(error => console.error("Couldn't verify database existence", error)) || null

    if (databaseExists === null) return

    if (!databaseExists) {
      console.log('creating new database...')
      try {
        DB.database = await defaultDB.createDatabase(DB_NAME)
      } catch (error) {
        console.error("Couldn't create database,", error)
      }
    } else {
      console.info(`database "${DB_NAME}" already exists.`)
    }

    await this.createCollections()
  }

  private async createCollections() {
    console.info('creating missing collections...')
    let created = false
    try {
      neededCollections.forEach(async collection => {
        if (!DB.database) return

        const coll = DB.database.collection(collection.name)
        const exists = await coll.exists()

        if (!exists) {
          coll.create({ schema: collection.schema })
          console.info(`created collection ${collection.name}`)
          created = true
        }
      })
    } catch (error) {
      console.error('DB.createCollections:', error)
    }
    if (created) {
      console.info('finished creating missing collections.')
    } else {
      console.info('no collections had to be created.')
    }
  }

  public getDatabase() {
    return DB.database
  }
}

This code will be run inside an express server within docker-compose

version: '3.9'
services:
  db:
    build:
      context: ./database # FROM arangodb/arangodb:latest
    environment:
      ARANGO_ROOT_PASSWORD: ${DB_PASSWORD:-root}
      DB_PORT: ${DB_PORT}
      DB_NAME: ${DB_NAME}
      DB_USERNAME: ${DB_USERNAME:-root}
      DB_PASSWORD: ${DB_PASSWORD:-root}
      DB_URL: ${DB_URL}
    ports:
      - 8529:8529
    volumes:
      - arangodb_data_container:/var/lib/arangodb3
      - arangodb_apps_data_container:/var/lib/arangodb3-apps
    network_mode: bridge

  nodeserver:
    build:
      context: .
    depends_on:
      - db
    environment:
      DB_PORT: ${DB_PORT}
      DB_NAME: ${DB_NAME}
      DB_USERNAME: ${DB_USERNAME:-root}
      DB_PASSWORD: ${DB_PASSWORD:-root}
      DB_URL: ${DB_URL}
    restart: unless-stopped
    network_mode: bridge

volumes:
  arangodb_data_container:
  arangodb_apps_data_container:
  certs:

This throws the following error

nodeserver_1  |    connect EINVAL 0.0.33.81:80 - Local (0.0.0.0:0)
nodeserver_1  |     at internalConnect (node:net:905:16)
nodeserver_1  |     at defaultTriggerAsyncIdScope (node:internal/async_hooks:431:18)
nodeserver_1  |     at GetAddrInfoReqWrap.emitLookup [as callback] (node:net:1051:9)
nodeserver_1  |     at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:8) {
nodeserver_1  |   errno: -22,
nodeserver_1  |   code: 'EINVAL',
nodeserver_1  |   syscall: 'connect',
nodeserver_1  |   address: '0.0.33.81',
nodeserver_1  |   port: 80,
nodeserver_1  |   request: <ref *1> ClientRequest {
nodeserver_1  |     _events: [Object: null prototype] {
nodeserver_1  |       response: [Function],
nodeserver_1  |       timeout: [Function (anonymous)],
nodeserver_1  |       error: [Function (anonymous)]
nodeserver_1  |     },
nodeserver_1  |     _eventsCount: 3,
nodeserver_1  |     _maxListeners: undefined,
nodeserver_1  |     outputData: [],
nodeserver_1  |     outputSize: 0,
nodeserver_1  |     writable: true,
nodeserver_1  |     destroyed: false,
nodeserver_1  |     _last: true,
nodeserver_1  |     chunkedEncoding: false,
nodeserver_1  |     shouldKeepAlive: true,
nodeserver_1  |     _defaultKeepAlive: true,
nodeserver_1  |     useChunkedEncodingByDefault: false,
nodeserver_1  |     sendDate: false,
nodeserver_1  |     _removedConnection: false,
nodeserver_1  |     _removedContLen: false,
nodeserver_1  |     _removedTE: false,
nodeserver_1  |     _contentLength: 0,
nodeserver_1  |     _hasBody: true,
nodeserver_1  |     _trailer: '',
nodeserver_1  |     finished: true,
nodeserver_1  |     _headerSent: true,
nodeserver_1  |     _closed: false,
nodeserver_1  |     socket: Socket {
nodeserver_1  |       connecting: false,
nodeserver_1  |       _hadError: true,
nodeserver_1  |       _parent: null,
nodeserver_1  |       _host: '8529',
nodeserver_1  |       _readableState: [ReadableState],
nodeserver_1  |       _events: [Object: null prototype],
nodeserver_1  |       _eventsCount: 8,
nodeserver_1  |       _maxListeners: undefined,
nodeserver_1  |       _writableState: [WritableState],
nodeserver_1  |       allowHalfOpen: false,
nodeserver_1  |       _sockname: null,
nodeserver_1  |       _pendingData: 'GET /_db/mydb/_api/database/current HTTP/1.1\r\n' +
nodeserver_1  |         'authorization: Basic cm9vdDpyb290\r\n' +
nodeserver_1  |         'content-type: text/plain\r\n' +
nodeserver_1  |         'x-arango-version: 30400\r\n' +
nodeserver_1  |         'Host: 8529\r\n' +
nodeserver_1  |         'Connection: keep-alive\r\n' +
nodeserver_1  |         '\r\n',
nodeserver_1  |       _pendingEncoding: 'latin1',
nodeserver_1  |       server: null,
nodeserver_1  |       _server: null,
nodeserver_1  |       parser: null,
nodeserver_1  |       _httpMessage: [Circular *1],
nodeserver_1  |       [Symbol(async_id_symbol)]: 90,
nodeserver_1  |       [Symbol(kHandle)]: null,
nodeserver_1  |       [Symbol(kSetNoDelay)]: false,
nodeserver_1  |       [Symbol(lastWriteQueueSize)]: 0,
nodeserver_1  |       [Symbol(timeout)]: null,
nodeserver_1  |       [Symbol(kBuffer)]: null,
nodeserver_1  |       [Symbol(kBufferCb)]: null,
nodeserver_1  |       [Symbol(kBufferGen)]: null,
nodeserver_1  |       [Symbol(kCapture)]: false,
nodeserver_1  |       [Symbol(kBytesRead)]: 0,
nodeserver_1  |       [Symbol(kBytesWritten)]: 0
nodeserver_1  |     },
nodeserver_1  |     _header: 'GET /_db/mydb/_api/database/current HTTP/1.1\r\n' +
nodeserver_1  |       'authorization: Basic cm9vdDpyb290\r\n' +
nodeserver_1  |       'content-type: text/plain\r\n' +
nodeserver_1  |       'x-arango-version: 30400\r\n' +
nodeserver_1  |       'Host: 8529\r\n' +
nodeserver_1  |       'Connection: keep-alive\r\n' +
nodeserver_1  |       '\r\n',
nodeserver_1  |     _keepAliveTimeout: 0,
nodeserver_1  |     _onPendingData: [Function: nop],
nodeserver_1  |     agent: Agent {
nodeserver_1  |       _events: [Object: null prototype],
nodeserver_1  |       _eventsCount: 2,
nodeserver_1  |       _maxListeners: undefined,
nodeserver_1  |       defaultPort: 80,
nodeserver_1  |       protocol: 'http:',
nodeserver_1  |       options: [Object: null prototype],
nodeserver_1  |       requests: [Object: null prototype] {},
nodeserver_1  |       sockets: [Object: null prototype],
nodeserver_1  |       freeSockets: [Object: null prototype] {},
nodeserver_1  |       keepAliveMsecs: 1000,
nodeserver_1  |       keepAlive: true,
nodeserver_1  |       maxSockets: 3,
nodeserver_1  |       maxFreeSockets: 256,
nodeserver_1  |       scheduling: 'lifo',
nodeserver_1  |       maxTotalSockets: Infinity,
nodeserver_1  |       totalSocketCount: 1,
nodeserver_1  |       [Symbol(kCapture)]: false
nodeserver_1  |     },
nodeserver_1  |     socketPath: undefined,
nodeserver_1  |     method: 'GET',
nodeserver_1  |     maxHeaderSize: undefined,
nodeserver_1  |     insecureHTTPParser: undefined,
nodeserver_1  |     path: '/_db/mydb/_api/database/current',
nodeserver_1  |     _ended: false,
nodeserver_1  |     res: null,
nodeserver_1  |     aborted: false,
nodeserver_1  |     timeoutCb: null,
nodeserver_1  |     upgradeOrConnect: false,
nodeserver_1  |     parser: null,
nodeserver_1  |     maxHeadersCount: null,
nodeserver_1  |     reusedSocket: false,
nodeserver_1  |     host: '8529',
nodeserver_1  |     protocol: 'http:',
nodeserver_1  |     [Symbol(kCapture)]: false,
nodeserver_1  |     [Symbol(kNeedDrain)]: false,
nodeserver_1  |     [Symbol(corked)]: 0,
nodeserver_1  |     [Symbol(kOutHeaders)]: [Object: null prototype] {
nodeserver_1  |       authorization: [Array],
nodeserver_1  |       'content-type': [Array],
nodeserver_1  |       'x-arango-version': [Array],
nodeserver_1  |       host: [Array]
nodeserver_1  |     }
nodeserver_1  |   }
nodeserver_1  | }

Starting the database the same way and connecting via http://localhost:8529 works as expected, but when I use the service name db of my arangodb container, the error occurs.

I don't know how to debug this. I don't kow if the error originates from arangojs directly. If I cannot fix this, I will have to change the database. So it's make or break for me.

Best regards

pluma commented 2 years ago

Looking at your logs this seems suspicious:

The IP of the database is 0.0.33.81 (which seems nonsensical), the port is 80 (which is the default for HTTP if no port is given) and the host is indicated as 8529.

I'm fairly certain you're accidentally passing the DB_PORT as DB_URL. IPv4 addresses can be give as integers and 8529 happens to be the integer representaton of 0.0.33.81 (33 * 256 + 81 = 8529).

Feel free to reopen this issue if you think this is unrelated to your problem.