Files
xen-orchestra/@vates/node-vsphere-soap/lib/client.js

232 lines
5.8 KiB
JavaScript
Raw Normal View History

'use strict'
2023-05-23 16:23:51 +02:00
/*
node-vsphere-soap
client.js
This file creates the Client class
- when the class is instantiated, a connection will be made to the ESXi/vCenter server to verify that the creds are good
- upon a bad login, the connnection will be terminated
*/
const EventEmitter = require('events').EventEmitter
const axios = require('axios')
const https = require('node:https')
const util = require('util')
const soap = require('soap')
const Cookie = require('soap-cookie') // required for session persistence
2023-05-23 16:23:51 +02:00
// Client class
// inherits from EventEmitter
// possible events: connect, error, ready
function Client(vCenterHostname, username, password, sslVerify) {
this.status = 'disconnected'
this.reconnectCount = 0
2023-05-23 16:23:51 +02:00
sslVerify = typeof sslVerify !== 'undefined' ? sslVerify : false
2023-05-23 16:23:51 +02:00
EventEmitter.call(this)
2023-05-23 16:23:51 +02:00
// sslVerify argument handling
if (sslVerify) {
this.clientopts = {}
2023-05-23 16:23:51 +02:00
} else {
this.clientopts = {
request: axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
}),
}
2023-05-23 16:23:51 +02:00
}
this.connectionInfo = {
host: vCenterHostname,
user: username,
password,
sslVerify,
}
2023-05-23 16:23:51 +02:00
this._loginArgs = {
userName: this.connectionInfo.user,
password: this.connectionInfo.password,
}
2023-05-23 16:23:51 +02:00
this._vcUrl = 'https://' + this.connectionInfo.host + '/sdk/vimService.wsdl'
2023-05-23 16:23:51 +02:00
// connect to the vCenter / ESXi host
this.on('connect', this._connect)
this.emit('connect')
2023-05-23 16:23:51 +02:00
// close session
this.on('close', this._close)
2023-05-23 16:23:51 +02:00
return this
}
2023-05-23 16:23:51 +02:00
util.inherits(Client, EventEmitter)
2023-05-23 16:23:51 +02:00
Client.prototype.runCommand = function (command, args) {
const self = this
let cmdargs
if (!args || args === null) {
cmdargs = {}
2023-05-23 16:23:51 +02:00
} else {
cmdargs = args
2023-05-23 16:23:51 +02:00
}
const emitter = new EventEmitter()
2023-05-23 16:23:51 +02:00
// check if client has successfully connected
if (self.status === 'ready' || self.status === 'connecting') {
self.client.VimService.VimPort[command](cmdargs, function (err, result, raw, soapHeader) {
if (err) {
_soapErrorHandler(self, emitter, command, cmdargs, err)
2023-05-23 16:23:51 +02:00
}
if (command === 'Logout') {
self.status = 'disconnected'
process.removeAllListeners('beforeExit')
2023-05-23 16:23:51 +02:00
}
emitter.emit('result', result, raw, soapHeader)
})
2023-05-23 16:23:51 +02:00
} else {
// if connection not ready or connecting, reconnect to instance
if (self.status === 'disconnected') {
self.emit('connect')
2023-05-23 16:23:51 +02:00
}
self.once('ready', function () {
self.client.VimService.VimPort[command](cmdargs, function (err, result, raw, soapHeader) {
if (err) {
_soapErrorHandler(self, emitter, command, cmdargs, err)
2023-05-23 16:23:51 +02:00
}
if (command === 'Logout') {
self.status = 'disconnected'
process.removeAllListeners('beforeExit')
2023-05-23 16:23:51 +02:00
}
emitter.emit('result', result, raw, soapHeader)
})
})
2023-05-23 16:23:51 +02:00
}
return emitter
}
2023-05-23 16:23:51 +02:00
Client.prototype.close = function () {
const self = this
2023-05-23 16:23:51 +02:00
self.emit('close')
}
2023-05-23 16:23:51 +02:00
Client.prototype._connect = function () {
const self = this
2023-05-23 16:23:51 +02:00
if (self.status !== 'disconnected') {
return
2023-05-23 16:23:51 +02:00
}
self.status = 'connecting'
2023-05-23 16:23:51 +02:00
soap.createClient(
self._vcUrl,
self.clientopts,
function (err, client) {
if (err) {
self.emit('error', err)
throw err
2023-05-23 16:23:51 +02:00
}
self.client = client // save client for later use
self
.runCommand('RetrieveServiceContent', { _this: 'ServiceInstance' })
.once('result', function (result, raw, soapHeader) {
if (!result.returnval) {
self.status = 'disconnected'
self.emit('error', raw)
return
}
self.serviceContent = result.returnval
self.sessionManager = result.returnval.sessionManager
const loginArgs = { _this: self.sessionManager, ...self._loginArgs }
self
.runCommand('Login', loginArgs)
.once('result', function (result, raw, soapHeader) {
self.authCookie = new Cookie(client.lastResponseHeaders)
self.client.setSecurity(self.authCookie) // needed since vSphere SOAP WS uses cookies
self.userName = result.returnval.userName
self.fullName = result.returnval.fullName
self.reconnectCount = 0
self.status = 'ready'
self.emit('ready')
process.once('beforeExit', self._close)
})
.once('error', function (err) {
self.status = 'disconnected'
self.emit('error', err)
})
})
.once('error', function (err) {
self.status = 'disconnected'
self.emit('error', err)
})
},
self._vcUrl
)
}
2023-05-23 16:23:51 +02:00
Client.prototype._close = function () {
const self = this
if (self.status === 'ready') {
self
.runCommand('Logout', { _this: self.sessionManager })
.once('result', function () {
self.status = 'disconnected'
2023-05-23 16:23:51 +02:00
})
.once('error', function () {
/* don't care of error during disconnection */
self.status = 'disconnected'
})
2023-05-23 16:23:51 +02:00
} else {
self.status = 'disconnected'
2023-05-23 16:23:51 +02:00
}
}
2023-05-23 16:23:51 +02:00
function _soapErrorHandler(self, emitter, command, args, err) {
err = err || { body: 'general error' }
2023-05-23 16:23:51 +02:00
if (err.body.match(/session is not authenticated/)) {
self.status = 'disconnected'
process.removeAllListeners('beforeExit')
if (self.reconnectCount < 10) {
self.reconnectCount += 1
self
.runCommand(command, args)
.once('result', function (result, raw, soapHeader) {
emitter.emit('result', result, raw, soapHeader)
})
.once('error', function (err) {
emitter.emit('error', err.body)
throw err
})
2023-05-23 16:23:51 +02:00
} else {
emitter.emit('error', err.body)
throw err
2023-05-23 16:23:51 +02:00
}
} else {
emitter.emit('error', err.body)
throw err
2023-05-23 16:23:51 +02:00
}
}
2023-05-23 16:23:51 +02:00
// end
exports.Client = Client