feat(xosan): license management (#628)
This commit is contained in:
parent
892139908b
commit
54772fad33
@ -68,6 +68,9 @@ export async function destroy ({sr}) {
|
||||
await xapi.forgetSr(sr._xapiId)
|
||||
await asyncMap(config.nodes, node => xapi.deleteVm(node.vm.id))
|
||||
await xapi.deleteNetwork(config.network)
|
||||
if (sr.SR_type === 'xosan') {
|
||||
await this.unbindXosanLicense({ srId: sr.id })
|
||||
}
|
||||
}
|
||||
|
||||
destroy.params = {
|
||||
|
220
src/api/xosan.js
220
src/api/xosan.js
@ -8,7 +8,6 @@ import { invalidParameters } from 'xo-common/api-errors'
|
||||
import { v4 as generateUuid } from 'uuid'
|
||||
import {
|
||||
includes,
|
||||
isArray,
|
||||
remove,
|
||||
filter,
|
||||
find,
|
||||
@ -16,7 +15,7 @@ import {
|
||||
} from 'lodash'
|
||||
|
||||
import { asInteger } from '../xapi/utils'
|
||||
import { asyncMap, parseXml } from '../utils'
|
||||
import { asyncMap, parseXml, ensureArray } from '../utils'
|
||||
|
||||
const debug = createLogger('xo:xosan')
|
||||
|
||||
@ -27,7 +26,7 @@ const HOST_FIRST_NUMBER = 1
|
||||
const GIGABYTE = 1024 * 1024 * 1024
|
||||
const XOSAN_VM_SYSTEM_DISK_SIZE = 10 * GIGABYTE
|
||||
const XOSAN_DATA_DISK_USEAGE_RATIO = 0.99
|
||||
const XOSAN_MAX_DISK_SIZE = 2093050 * 1024 * 1024 // a bit under 2To
|
||||
const XOSAN_LICENSE_QUOTA = 50 * GIGABYTE
|
||||
|
||||
const CURRENT_POOL_OPERATIONS = {}
|
||||
|
||||
@ -83,15 +82,13 @@ async function rateLimitedRetry (action, shouldRetry, retryCount = 20) {
|
||||
return result
|
||||
}
|
||||
|
||||
export async function getVolumeInfo ({sr, infoType}) {
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(sr)
|
||||
|
||||
function createVolumeInfoTypes () {
|
||||
function parseHeal (parsed) {
|
||||
const bricks = []
|
||||
parsed['healInfo']['bricks']['brick'].forEach(brick => {
|
||||
bricks.push(brick)
|
||||
if (brick['file'] && !isArray(brick['file'])) {
|
||||
brick['file'] = [brick['file']]
|
||||
if (brick.file) {
|
||||
brick.file = ensureArray(brick.file)
|
||||
}
|
||||
})
|
||||
return {commandStatus: true, result: {bricks}}
|
||||
@ -110,23 +107,52 @@ export async function getVolumeInfo ({sr, infoType}) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseInfo (parsed) {
|
||||
async function parseInfo (parsed) {
|
||||
const volume = parsed['volInfo']['volumes']['volume']
|
||||
volume['bricks'] = volume['bricks']['brick']
|
||||
volume['options'] = volume['options']['option']
|
||||
return {commandStatus: true, result: volume}
|
||||
}
|
||||
|
||||
function sshInfoType (command, handler) {
|
||||
return async () => {
|
||||
const cmdShouldRetry = result => !result['commandStatus'] && result.parsed && result.parsed['cliOutput']['opErrno'] === '30802'
|
||||
const sshInfoType = (command, handler) => {
|
||||
return async function (sr) {
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(sr)
|
||||
const cmdShouldRetry = result =>
|
||||
!result['commandStatus'] &&
|
||||
((result.parsed && result.parsed['cliOutput']['opErrno'] === '30802') ||
|
||||
result.stderr.match(/Another transaction is in progress/))
|
||||
const runCmd = async () => glusterCmd(glusterEndpoint, 'volume ' + command, true)
|
||||
const commandResult = await rateLimitedRetry(runCmd, cmdShouldRetry)
|
||||
return commandResult['commandStatus'] ? handler(commandResult.parsed['cliOutput']) : commandResult
|
||||
const commandResult = await rateLimitedRetry(runCmd, cmdShouldRetry, 30)
|
||||
return commandResult['commandStatus'] ? this::handler(commandResult.parsed['cliOutput'], sr) : commandResult
|
||||
}
|
||||
}
|
||||
|
||||
function checkHosts () {
|
||||
async function profileType (sr) {
|
||||
async function parseProfile (parsed) {
|
||||
const volume = parsed['volProfile']
|
||||
volume['bricks'] = ensureArray(volume['brick'])
|
||||
delete volume['brick']
|
||||
return {commandStatus: true, result: volume}
|
||||
}
|
||||
|
||||
return this::(sshInfoType('profile xosan info', parseProfile))(sr)
|
||||
}
|
||||
|
||||
async function profileTopType (sr) {
|
||||
async function parseTop (parsed) {
|
||||
const volume = parsed['volTop']
|
||||
volume['bricks'] = ensureArray(volume['brick'])
|
||||
delete volume['brick']
|
||||
return {commandStatus: true, result: volume}
|
||||
}
|
||||
|
||||
const topTypes = ['open', 'read', 'write', 'opendir', 'readdir']
|
||||
return asyncMap(topTypes, async type => ({
|
||||
type, result: await this::(sshInfoType(`top xosan ${type}`, parseTop))(sr),
|
||||
}))
|
||||
}
|
||||
|
||||
function checkHosts (sr) {
|
||||
const xapi = this.getXapi(sr)
|
||||
const data = getXosanConfig(sr, xapi)
|
||||
const network = xapi.getObject(data.network)
|
||||
@ -134,22 +160,33 @@ export async function getVolumeInfo ({sr, infoType}) {
|
||||
return badPifs.map(pif => ({pif, host: pif.$host.$id}))
|
||||
}
|
||||
|
||||
const infoTypes = {
|
||||
return {
|
||||
heal: sshInfoType('heal xosan info', parseHeal),
|
||||
status: sshInfoType('status xosan', parseStatus),
|
||||
statusDetail: sshInfoType('status xosan detail', parseStatus),
|
||||
statusMem: sshInfoType('status xosan mem', parseStatus),
|
||||
info: sshInfoType('info xosan', parseInfo),
|
||||
hosts: this::checkHosts,
|
||||
profile: profileType,
|
||||
profileTop: profileTopType,
|
||||
hosts: checkHosts,
|
||||
}
|
||||
}
|
||||
|
||||
const VOLUME_INFO_TYPES = createVolumeInfoTypes()
|
||||
|
||||
export async function getVolumeInfo ({sr, infoType}) {
|
||||
await this.checkXosanLicense({ srId: sr.uuid })
|
||||
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(sr)
|
||||
|
||||
if (glusterEndpoint == null) {
|
||||
return null
|
||||
}
|
||||
const foundType = infoTypes[infoType]
|
||||
const foundType = VOLUME_INFO_TYPES[infoType]
|
||||
if (!foundType) {
|
||||
throw new Error('getVolumeInfo(): "' + infoType + '" is an invalid type')
|
||||
}
|
||||
return foundType()
|
||||
return this::foundType(sr)
|
||||
}
|
||||
|
||||
getVolumeInfo.description = 'info on gluster volume'
|
||||
@ -161,18 +198,49 @@ getVolumeInfo.params = {
|
||||
},
|
||||
infoType: {
|
||||
type: 'string',
|
||||
eq: Object.keys(VOLUME_INFO_TYPES),
|
||||
},
|
||||
}
|
||||
getVolumeInfo.resolve = {
|
||||
sr: ['sr', 'SR', 'administrate'],
|
||||
}
|
||||
|
||||
export async function profileStatus ({sr, changeStatus = null}) {
|
||||
await this.checkXosanLicense({ srId: sr.uuid })
|
||||
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(sr)
|
||||
if (changeStatus === false) {
|
||||
await glusterCmd(glusterEndpoint, 'volume profile xosan stop')
|
||||
return null
|
||||
}
|
||||
if (changeStatus === true) {
|
||||
await glusterCmd(glusterEndpoint, 'volume profile xosan start')
|
||||
}
|
||||
return this::getVolumeInfo({sr: sr, infoType: 'profile'})
|
||||
}
|
||||
|
||||
profileStatus.description = 'activate, deactivate, or interrogate profile data'
|
||||
profileStatus.permission = 'admin'
|
||||
profileStatus.params = {
|
||||
sr: {
|
||||
type: 'string',
|
||||
},
|
||||
changeStatus: {
|
||||
type: 'bool', optional: true,
|
||||
},
|
||||
}
|
||||
profileStatus.resolve = {
|
||||
sr: ['sr', 'SR', 'administrate'],
|
||||
}
|
||||
|
||||
function reconfigurePifIP (xapi, pif, newIP) {
|
||||
xapi.call('PIF.reconfigure_ip', pif.$ref, 'Static', newIP, '255.255.255.0', '', '')
|
||||
}
|
||||
|
||||
// this function should probably become fixSomething(thingToFix, parmas)
|
||||
export async function fixHostNotInNetwork ({xosanSr, host}) {
|
||||
await this.checkXosanLicense({ srId: xosanSr.uuid })
|
||||
|
||||
const xapi = this.getXapi(xosanSr)
|
||||
const data = getXosanConfig(xosanSr, xapi)
|
||||
const network = xapi.getObject(data.network)
|
||||
@ -400,6 +468,7 @@ async function configureGluster (redundancy, ipAndHosts, glusterEndpoint, gluste
|
||||
await glusterCmd(glusterEndpoint, volumeCreation)
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan network.remote-dio enable')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan cluster.eager-lock enable')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan cluster.locking-scheme granular')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan performance.io-cache off')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan performance.read-ahead off')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan performance.quick-read off')
|
||||
@ -408,17 +477,30 @@ async function configureGluster (redundancy, ipAndHosts, glusterEndpoint, gluste
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan server.event-threads 8')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan performance.io-thread-count 64')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan performance.stat-prefetch on')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan performance.low-prio-threads 32')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan features.shard on')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan features.shard-block-size 512MB')
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan user.cifs off')
|
||||
for (const confChunk of configByType[glusterType].extra) {
|
||||
await glusterCmd(glusterEndpoint, confChunk)
|
||||
}
|
||||
await glusterCmd(glusterEndpoint, 'volume start xosan')
|
||||
await _setQuota(glusterEndpoint)
|
||||
}
|
||||
|
||||
async function _setQuota (glusterEndpoint) {
|
||||
await glusterCmd(glusterEndpoint, 'volume quota xosan enable', true)
|
||||
await glusterCmd(glusterEndpoint, 'volume set xosan quota-deem-statfs on', true)
|
||||
await glusterCmd(glusterEndpoint, `volume quota xosan limit-usage / ${XOSAN_LICENSE_QUOTA}B`, true)
|
||||
}
|
||||
|
||||
async function _removeQuota (glusterEndpoint) {
|
||||
await glusterCmd(glusterEndpoint, 'volume quota xosan disable', true)
|
||||
}
|
||||
|
||||
export const createSR = defer(async function ($defer, {
|
||||
template, pif, vlan, srs, glusterType,
|
||||
redundancy, brickSize = this::computeBrickSize(srs), memorySize = 2 * GIGABYTE, ipRange = DEFAULT_NETWORK_PREFIX + '.0',
|
||||
redundancy, brickSize = this::computeBrickSize(srs), memorySize = 4 * GIGABYTE, ipRange = DEFAULT_NETWORK_PREFIX + '.0',
|
||||
}) {
|
||||
const OPERATION_OBJECT = {
|
||||
operation: 'createSr',
|
||||
@ -432,9 +514,7 @@ export const createSR = defer(async function ($defer, {
|
||||
if (srs.length < 1) {
|
||||
return // TODO: throw an error
|
||||
}
|
||||
// '172.31.100.0' -> '172.31.100.'
|
||||
const networkPrefix = ipRange.split('.').slice(0, 3).join('.') + '.'
|
||||
let vmIpLastNumber = VM_FIRST_NUMBER
|
||||
|
||||
const xapi = this.getXapi(srs[0])
|
||||
const poolId = xapi.pool.$id
|
||||
if (CURRENT_POOL_OPERATIONS[poolId]) {
|
||||
@ -442,6 +522,15 @@ export const createSR = defer(async function ($defer, {
|
||||
}
|
||||
|
||||
CURRENT_POOL_OPERATIONS[poolId] = {...OPERATION_OBJECT, state: 0}
|
||||
|
||||
const tmpBoundObjectId = srs.join(',')
|
||||
const license = await this.createBoundXosanTrialLicense({ boundObjectId: tmpBoundObjectId })
|
||||
$defer.onFailure(() => this.unbindXosanLicense({ srId: tmpBoundObjectId }))
|
||||
|
||||
// '172.31.100.0' -> '172.31.100.'
|
||||
const networkPrefix = ipRange.split('.').slice(0, 3).join('.') + '.'
|
||||
let vmIpLastNumber = VM_FIRST_NUMBER
|
||||
|
||||
try {
|
||||
const xosanNetwork = await createNetworkAndInsertHosts(xapi, pif, vlan, networkPrefix)
|
||||
$defer.onFailure(() => xapi.deleteNetwork(xosanNetwork))
|
||||
@ -511,6 +600,7 @@ export const createSR = defer(async function ($defer, {
|
||||
}))
|
||||
await xapi.xo.setData(xosanSrRef, 'xosan_config', {
|
||||
version: 'beta2',
|
||||
creationDate: new Date().toISOString(),
|
||||
nodes: nodes,
|
||||
template: template,
|
||||
network: xosanNetwork.$id,
|
||||
@ -521,6 +611,11 @@ export const createSR = defer(async function ($defer, {
|
||||
CURRENT_POOL_OPERATIONS[poolId] = {...OPERATION_OBJECT, state: 6}
|
||||
debug('scanning new SR')
|
||||
await xapi.call('SR.scan', xosanSrRef)
|
||||
await this.rebindLicense({
|
||||
licenseId: license.id,
|
||||
oldBoundObjectId: tmpBoundObjectId,
|
||||
newBoundObjectId: xapi.getObject(xosanSrRef).uuid,
|
||||
})
|
||||
} finally {
|
||||
delete CURRENT_POOL_OPERATIONS[poolId]
|
||||
}
|
||||
@ -568,13 +663,14 @@ async function umountDisk (localEndpoint, diskMountPoint) {
|
||||
async function createVDIOnLVMWithoutSizeLimit (xapi, lvmSr, diskSize) {
|
||||
const VG_PREFIX = 'VG_XenStorage-'
|
||||
const LV_PREFIX = 'LV-'
|
||||
if (lvmSr.type !== 'lvm') {
|
||||
throw new Error('expecting a lvm sr type, got"' + lvmSr.type + '"')
|
||||
const { type, uuid: srUuid, $PBDs } = xapi.getObject(lvmSr)
|
||||
if (type !== 'lvm') {
|
||||
throw new Error('expecting a lvm sr type, got"' + type + '"')
|
||||
}
|
||||
const uuid = generateUuid()
|
||||
const lvName = LV_PREFIX + uuid
|
||||
const vgName = VG_PREFIX + lvmSr.uuid
|
||||
const host = lvmSr.$PBDs[0].$host
|
||||
const vgName = VG_PREFIX + srUuid
|
||||
const host = $PBDs[0].$host
|
||||
const sizeMb = Math.ceil(diskSize / 1024 / 1024)
|
||||
const result = await callPlugin(xapi, host, 'run_lvcreate', {sizeMb: asInteger(sizeMb), lvName, vgName})
|
||||
if (result.exit !== 0) {
|
||||
@ -651,6 +747,8 @@ async function replaceBrickOnSameVM (xosansr, previousBrick, newLvmSr, brickSize
|
||||
}
|
||||
|
||||
export async function replaceBrick ({xosansr, previousBrick, newLvmSr, brickSize, onSameVM = true}) {
|
||||
await this.checkXosanLicense({ srId: xosansr.uuid })
|
||||
|
||||
const OPERATION_OBJECT = {
|
||||
operation: 'replaceBrick',
|
||||
states: ['insertingNewVm', 'swapingBrick', 'deletingVm', 'scaningSr'],
|
||||
@ -746,10 +844,6 @@ async function _prepareGlusterVm (xapi, lvmSr, newVM, xosanNetwork, ipAddress, {
|
||||
await xapi.editVm(newVM, {
|
||||
name_label: `XOSAN - ${lvmSr.name_label} - ${host.name_label} ${labelSuffix}`,
|
||||
name_description: 'Xosan VM storage',
|
||||
// https://bugs.xenserver.org/browse/XSO-762
|
||||
memoryMax: memorySize,
|
||||
memoryMin: memorySize,
|
||||
memoryStaticMax: memorySize,
|
||||
memory: memorySize,
|
||||
})
|
||||
await xapi.call('VM.set_xenstore_data', newVM.$ref, xenstoreData)
|
||||
@ -764,8 +858,7 @@ async function _prepareGlusterVm (xapi, lvmSr, newVM, xosanNetwork, ipAddress, {
|
||||
const localEndpoint = {xapi: xapi, hosts: [host], addresses: [ip]}
|
||||
const srFreeSpace = sr.physical_size - sr.physical_utilisation
|
||||
// we use a percentage because it looks like the VDI overhead is proportional
|
||||
const newSize = Math.min(floor2048(Math.min(maxDiskSize - rootDiskSize, srFreeSpace * XOSAN_DATA_DISK_USEAGE_RATIO)),
|
||||
XOSAN_MAX_DISK_SIZE)
|
||||
const newSize = floor2048(Math.min(maxDiskSize - rootDiskSize, srFreeSpace * XOSAN_DATA_DISK_USEAGE_RATIO))
|
||||
const smallDiskSize = 1073741824
|
||||
const deviceFile = await createNewDisk(xapi, lvmSr, newVM, increaseDataDisk ? newSize : smallDiskSize)
|
||||
const brickName = await mountNewDisk(localEndpoint, ip, deviceFile)
|
||||
@ -837,6 +930,8 @@ const insertNewGlusterVm = defer(async function ($defer, xapi, xosansr, lvmsrId,
|
||||
})
|
||||
|
||||
export const addBricks = defer(async function ($defer, {xosansr, lvmsrs, brickSize}) {
|
||||
await this.checkXosanLicense({ srId: xosansr.uuid })
|
||||
|
||||
const OPERATION_OBJECT = {
|
||||
operation: 'addBricks',
|
||||
states: ['insertingNewVms', 'addingBricks', 'scaningSr'],
|
||||
@ -902,25 +997,27 @@ addBricks.resolve = {
|
||||
}
|
||||
|
||||
export const removeBricks = defer(async function ($defer, {xosansr, bricks}) {
|
||||
await this.checkXosanLicense({ srId: xosansr.uuid })
|
||||
|
||||
const xapi = this.getXapi(xosansr)
|
||||
if (CURRENT_POOL_OPERATIONS[xapi.pool.$id]) {
|
||||
throw new Error('this there is already a XOSAN operation running on this pool')
|
||||
}
|
||||
CURRENT_POOL_OPERATIONS[xapi.pool.$id] = true
|
||||
try {
|
||||
const data = getXosanConfig(xosansr, xapi)
|
||||
const data = getXosanConfig(xosansr.id, xapi)
|
||||
// IPV6
|
||||
const ips = map(bricks, b => b.split(':')[0])
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(xosansr)
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(xosansr.id)
|
||||
// "peer detach" doesn't allow removal of locahost
|
||||
remove(glusterEndpoint.addresses, ip => ips.includes(ip))
|
||||
const dict = _getIPToVMDict(xapi, xosansr)
|
||||
const dict = _getIPToVMDict(xapi, xosansr.id)
|
||||
const brickVMs = map(bricks, b => dict[b])
|
||||
await glusterCmd(glusterEndpoint, `volume remove-brick xosan ${bricks.join(' ')} force`)
|
||||
await asyncMap(ips, ip => glusterCmd(glusterEndpoint, 'peer detach ' + ip, true))
|
||||
remove(data.nodes, node => ips.includes(node.vm.ip))
|
||||
await xapi.xo.setData(xosansr, 'xosan_config', data)
|
||||
await xapi.call('SR.scan', xapi.getObject(xosansr).$ref)
|
||||
await xapi.xo.setData(xosansr.id, 'xosan_config', data)
|
||||
await xapi.call('SR.scan', xapi.getObject(xosansr._xapiId).$ref)
|
||||
await asyncMap(brickVMs, vm => xapi.deleteVm(vm.vm, true))
|
||||
} finally {
|
||||
delete CURRENT_POOL_OPERATIONS[xapi.pool.$id]
|
||||
@ -936,6 +1033,7 @@ removeBricks.params = {
|
||||
items: {type: 'string'},
|
||||
},
|
||||
}
|
||||
removeBricks.resolve = { xosansr: ['sr', 'SR', 'administrate'] }
|
||||
|
||||
export function checkSrCurrentState ({poolId}) {
|
||||
return CURRENT_POOL_OPERATIONS[poolId]
|
||||
@ -948,33 +1046,40 @@ checkSrCurrentState.params = {poolId: {type: 'string'}}
|
||||
const POSSIBLE_CONFIGURATIONS = {}
|
||||
POSSIBLE_CONFIGURATIONS[2] = [{layout: 'replica_arbiter', redundancy: 3, capacity: 1}]
|
||||
POSSIBLE_CONFIGURATIONS[3] = [
|
||||
{layout: 'replica', redundancy: 3, capacity: 1},
|
||||
{layout: 'disperse', redundancy: 1, capacity: 2},
|
||||
{layout: 'replica', redundancy: 3, capacity: 1}]
|
||||
]
|
||||
POSSIBLE_CONFIGURATIONS[4] = [{layout: 'replica', redundancy: 2, capacity: 2}]
|
||||
POSSIBLE_CONFIGURATIONS[5] = [{layout: 'disperse', redundancy: 1, capacity: 4}]
|
||||
POSSIBLE_CONFIGURATIONS[6] = [
|
||||
{layout: 'disperse', redundancy: 2, capacity: 4},
|
||||
{layout: 'replica', redundancy: 2, capacity: 3},
|
||||
{layout: 'replica', redundancy: 3, capacity: 2}]
|
||||
{layout: 'replica', redundancy: 3, capacity: 2},
|
||||
{layout: 'disperse', redundancy: 2, capacity: 4},
|
||||
]
|
||||
POSSIBLE_CONFIGURATIONS[7] = [{layout: 'disperse', redundancy: 3, capacity: 4}]
|
||||
POSSIBLE_CONFIGURATIONS[8] = [{layout: 'replica', redundancy: 2, capacity: 4}]
|
||||
POSSIBLE_CONFIGURATIONS[9] = [
|
||||
{layout: 'replica', redundancy: 3, capacity: 3},
|
||||
{layout: 'disperse', redundancy: 1, capacity: 8},
|
||||
{layout: 'replica', redundancy: 3, capacity: 3}]
|
||||
]
|
||||
POSSIBLE_CONFIGURATIONS[10] = [
|
||||
{layout: 'replica', redundancy: 2, capacity: 5},
|
||||
{layout: 'disperse', redundancy: 2, capacity: 8},
|
||||
{layout: 'replica', redundancy: 2, capacity: 5}]
|
||||
]
|
||||
POSSIBLE_CONFIGURATIONS[11] = [{layout: 'disperse', redundancy: 3, capacity: 8}]
|
||||
POSSIBLE_CONFIGURATIONS[12] = [
|
||||
{layout: 'replica', redundancy: 2, capacity: 6},
|
||||
{layout: 'disperse', redundancy: 4, capacity: 8},
|
||||
{layout: 'replica', redundancy: 2, capacity: 6}]
|
||||
]
|
||||
POSSIBLE_CONFIGURATIONS[13] = [{layout: 'disperse', redundancy: 5, capacity: 8}]
|
||||
POSSIBLE_CONFIGURATIONS[14] = [
|
||||
{layout: 'replica', redundancy: 2, capacity: 7},
|
||||
{layout: 'disperse', redundancy: 6, capacity: 8},
|
||||
{layout: 'replica', redundancy: 2, capacity: 7}]
|
||||
]
|
||||
POSSIBLE_CONFIGURATIONS[15] = [
|
||||
{layout: 'replica', redundancy: 3, capacity: 5},
|
||||
{layout: 'disperse', redundancy: 7, capacity: 8},
|
||||
{layout: 'replica', redundancy: 3, capacity: 5}]
|
||||
]
|
||||
POSSIBLE_CONFIGURATIONS[16] = [{layout: 'replica', redundancy: 2, capacity: 8}]
|
||||
|
||||
function computeBrickSize (srs, brickSize = Infinity) {
|
||||
@ -1011,6 +1116,29 @@ computeXosanPossibleOptions.params = {
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
export async function unlock ({ licenseId, sr }) {
|
||||
await this.unlockXosanLicense({ licenseId, srId: sr.id })
|
||||
|
||||
const glusterEndpoint = this::_getGlusterEndpoint(sr.id)
|
||||
await _removeQuota(glusterEndpoint)
|
||||
await glusterEndpoint.xapi.call('SR.scan', glusterEndpoint.xapi.getObject(sr).$ref)
|
||||
}
|
||||
|
||||
unlock.description = 'Unlock XOSAN SR functionalities by binding it to a paid license'
|
||||
|
||||
unlock.permission = 'admin'
|
||||
|
||||
unlock.params = {
|
||||
licenseId: { type: 'string' },
|
||||
sr: { type: 'string' },
|
||||
}
|
||||
|
||||
unlock.resolve = {
|
||||
sr: ['sr', 'SR', 'administrate'],
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
export async function downloadAndInstallXosanPack ({id, version, pool}) {
|
||||
if (!this.requestResource) {
|
||||
throw new Error('requestResource is not a function')
|
||||
|
Loading…
Reference in New Issue
Block a user