Merge pull request #150 from vatesfr/olivierlambert-configdrive
Generic CloudConfig Drive
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
"exec-promise": "^0.5.1",
|
||||
"express": "^4.13.3",
|
||||
"express-session": "^1.11.3",
|
||||
"fatfs": "^0.10.3",
|
||||
"fs-extra": "^0.26.2",
|
||||
"fs-promise": "^0.3.1",
|
||||
"got": "^5.0.0",
|
||||
|
||||
@@ -955,9 +955,14 @@ exports.getCloudInitConfig = getCloudInitConfig
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
createCloudInitConfigDrive = $coroutine ({vm, sr, config}) ->
|
||||
createCloudInitConfigDrive = $coroutine ({vm, sr, config, coreos}) ->
|
||||
xapi = @getXAPI vm
|
||||
yield xapi.createCloudInitConfigDrive(vm._xapiId, sr._xapiId, config)
|
||||
# CoreOS is a special CloudConfig drive created by XS plugin
|
||||
if coreos
|
||||
yield xapi.createCoreOsCloudInitConfigDrive(vm._xapiId, sr._xapiId, config)
|
||||
# use generic Cloud Init drive
|
||||
else
|
||||
yield xapi.createCloudInitConfigDrive(vm._xapiId, sr._xapiId, config)
|
||||
return true
|
||||
|
||||
createCloudInitConfigDrive.params = {
|
||||
|
||||
83
src/fatfs-buffer.js
Normal file
83
src/fatfs-buffer.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// Buffer driver for [fatfs](https://github.com/natevw/fatfs).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ```js
|
||||
// import fatfs from 'fatfs'
|
||||
// import fatfsBuffer, { init as fatfsBufferInit } from './fatfs-buffer'
|
||||
//
|
||||
// const buffer = fatfsBufferinit()
|
||||
//
|
||||
// const fs = fatfs.createFileSystem(fatfsBuffer(buffer))
|
||||
//
|
||||
// fs.writeFile('/foo', 'content of foo', function (err, content) {
|
||||
// if (err) {
|
||||
// console.error(err)
|
||||
// }
|
||||
// })
|
||||
|
||||
import { boot16 as fat16 } from 'fatfs/structs'
|
||||
|
||||
const SECTOR_SIZE = 512
|
||||
|
||||
// Creates a 10MB buffer and initializes it as a FAT 16 volume.
|
||||
export function init () {
|
||||
const buf = new Buffer(10 * 1024 * 1024) // 10MB
|
||||
|
||||
// https://github.com/natevw/fatfs/blob/master/structs.js
|
||||
fat16.pack({
|
||||
jmpBoot: new Buffer('eb3c90', 'hex'),
|
||||
OEMName: 'mkfs.fat',
|
||||
BytsPerSec: SECTOR_SIZE,
|
||||
SecPerClus: 4,
|
||||
ResvdSecCnt: 1,
|
||||
NumFATs: 2,
|
||||
RootEntCnt: 512,
|
||||
TotSec16: 20480,
|
||||
Media: 248,
|
||||
FATSz16: 20,
|
||||
SecPerTrk: 32,
|
||||
NumHeads: 64,
|
||||
HiddSec: 0,
|
||||
TotSec32: 0,
|
||||
DrvNum: 128,
|
||||
Reserved1: 0,
|
||||
BootSig: 41,
|
||||
VolID: 895111106,
|
||||
VolLab: 'NO NAME ',
|
||||
FilSysType: 'FAT16 '
|
||||
}, buf)
|
||||
|
||||
// End of sector.
|
||||
buf[0x1fe] = 0x55
|
||||
buf[0x1ff] = 0xaa
|
||||
|
||||
// Mark sector as reserved.
|
||||
buf[0x200] = 0xf8
|
||||
buf[0x201] = 0xff
|
||||
buf[0x202] = 0xff
|
||||
buf[0x203] = 0xff
|
||||
|
||||
// Mark sector as reserved.
|
||||
buf[0x2a00] = 0xf8
|
||||
buf[0x2a01] = 0xff
|
||||
buf[0x2a02] = 0xff
|
||||
buf[0x2a03] = 0xff
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
export default buffer => {
|
||||
return {
|
||||
sectorSize: SECTOR_SIZE,
|
||||
numSectors: Math.floor(buffer.length / SECTOR_SIZE),
|
||||
readSectors: (i, target, cb) => {
|
||||
buffer.copy(target, 0, i * SECTOR_SIZE)
|
||||
cb()
|
||||
},
|
||||
writeSectors: (i, source, cb) => {
|
||||
source.copy(buffer, i * SECTOR_SIZE, 0)
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/utils.js
21
src/utils.js
@@ -8,10 +8,31 @@ import multiKeyHashInt from 'multikey-hash'
|
||||
import xml2js from 'xml2js'
|
||||
import {promisify} from 'bluebird'
|
||||
import {randomBytes} from 'crypto'
|
||||
import { Readable } from 'stream'
|
||||
import {utcFormat as d3TimeFormat} from 'd3-time-format'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export function bufferToStream (buf) {
|
||||
const stream = new Readable()
|
||||
|
||||
let i = 0
|
||||
const { length } = buf
|
||||
stream._read = function (size) {
|
||||
if (i === length) {
|
||||
return this.push(null)
|
||||
}
|
||||
|
||||
const newI = Math.min(i + size, length)
|
||||
this.push(buf.slice(i, newI))
|
||||
i = newI
|
||||
}
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export function camelToSnakeCase (string) {
|
||||
return string.replace(
|
||||
/([a-z])([A-Z])/g,
|
||||
|
||||
41
src/xapi.js
41
src/xapi.js
@@ -6,6 +6,8 @@ import got from 'got'
|
||||
import includes from 'lodash.includes'
|
||||
import isFunction from 'lodash.isfunction'
|
||||
import sortBy from 'lodash.sortby'
|
||||
import fatfs from 'fatfs'
|
||||
import fatfsBuffer, { init as fatfsBufferInit } from './fatfs-buffer'
|
||||
import unzip from 'julien-f-unzip'
|
||||
import { PassThrough } from 'stream'
|
||||
import { request as httpRequest } from 'http'
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
|
||||
import {debounce} from './decorators'
|
||||
import {
|
||||
bufferToStream,
|
||||
camelToSnakeCase,
|
||||
createRawObject,
|
||||
ensureArray,
|
||||
@@ -26,6 +29,7 @@ import {
|
||||
parseSize,
|
||||
parseXml,
|
||||
pFinally,
|
||||
promisifyAll,
|
||||
pSettle
|
||||
} from './utils'
|
||||
import {JsonRpcError} from './api-errors'
|
||||
@@ -927,11 +931,9 @@ export default class Xapi extends XapiBase {
|
||||
) {
|
||||
await this.resizeVdi(vdi.$id, size)
|
||||
}
|
||||
|
||||
// if another SR is set
|
||||
// if another SR is set, move it there
|
||||
if (srId) {
|
||||
// TODO
|
||||
throw new Error('Not implemented')
|
||||
await this.moveVdi(vdi.$id, srId)
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -1334,7 +1336,7 @@ export default class Xapi extends XapiBase {
|
||||
position = undefined,
|
||||
type = 'Disk',
|
||||
readOnly = (type !== 'Disk')
|
||||
}) {
|
||||
} = {}) {
|
||||
if (position == null) {
|
||||
const allowed = await this.call('VM.get_allowed_VBD_devices', vm.$ref)
|
||||
const {length} = allowed
|
||||
@@ -1655,7 +1657,8 @@ export default class Xapi extends XapiBase {
|
||||
return config.slice(4) // FIXME remove the "True" string on the begining
|
||||
}
|
||||
|
||||
async createCloudInitConfigDrive (vmId, srId, config) {
|
||||
// Specific CoreOS Config Drive
|
||||
async createCoreOsCloudInitConfigDrive (vmId, srId, config) {
|
||||
const vm = this.getObject(vmId)
|
||||
const host = this.pool.$master
|
||||
const sr = this.getObject(srId)
|
||||
@@ -1667,6 +1670,32 @@ export default class Xapi extends XapiBase {
|
||||
})
|
||||
}
|
||||
|
||||
// Generic Config Drive
|
||||
async createCloudInitConfigDrive (vmId, srId, config) {
|
||||
const vm = this.getObject(vmId)
|
||||
const sr = this.getObject(srId)
|
||||
|
||||
// First, create a small VDI (10MB) which will become the ConfigDrive
|
||||
const buffer = fatfsBufferInit()
|
||||
const vdi = await this.createVdi(buffer.length, { name_label: 'XO CloudConfigDrive', name_description: undefined, sr: sr.$ref })
|
||||
// Then, generate a FAT fs
|
||||
const fs = promisifyAll(fatfs.createFileSystem(fatfsBuffer(buffer)))
|
||||
// Create Cloud config folders
|
||||
await fs.mkdirAsync('openstack')
|
||||
await fs.mkdirAsync('openstack/latest')
|
||||
// Create the meta_data file
|
||||
await fs.writeFileAsync('openstack/latest/meta_data.json', '{\n "uuid": "' + vm.uuid + '"\n}\n')
|
||||
// Create the user_data file
|
||||
await fs.writeFileAsync('openstack/latest/user_data', config)
|
||||
|
||||
// Transform the buffer into a stream
|
||||
const stream = bufferToStream(buffer)
|
||||
await this.importVdiContent(vdi.$id, stream, {
|
||||
format: VDI_FORMAT_RAW
|
||||
})
|
||||
await this._createVbd(vm, vdi)
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user