Merge pull request #150 from vatesfr/olivierlambert-configdrive

Generic CloudConfig Drive
This commit is contained in:
Julien Fontanet
2015-12-16 18:19:51 +01:00
5 changed files with 147 additions and 8 deletions

View File

@@ -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",

View File

@@ -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
View 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()
}
}
}

View File

@@ -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,

View File

@@ -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)
}
// =================================================================
}