feat(remotes): allow setting NFS mount options (#3353)

Fixes #1793
This commit is contained in:
Pierre Donias 2018-08-23 11:34:56 +02:00 committed by Julien Fontanet
parent ecc33f46ab
commit dfe4a934e9
8 changed files with 61 additions and 16 deletions

View File

@ -4,6 +4,8 @@ import { forEach } from 'lodash'
import LocalHandler from './local'
const DEFAULT_NFS_OPTIONS = 'vers=3'
export default class NfsHandler extends LocalHandler {
get type () {
return 'nfs'
@ -52,12 +54,12 @@ export default class NfsHandler extends LocalHandler {
async _mount () {
await fs.ensureDir(this._getRealPath())
const { host, path, port } = this._remote
const { host, path, port, options } = this._remote
return execa('mount', [
'-t',
'nfs',
'-o',
'vers=3',
DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
])

View File

@ -17,6 +17,7 @@
- [New VM] Display an error when the getting of the coreOS default template fails [#3227](https://github.com/vatesfr/xen-orchestra/issues/3227) (PR [#3343](https://github.com/vatesfr/xen-orchestra/pull/3343))
- [Backup NG form] Set default retention to 1 [#3134](https://github.com/vatesfr/xen-orchestra/issues/3134) (PR [#3290](https://github.com/vatesfr/xen-orchestra/pull/3290))
- [Backup NG] New logs are searchable by job name [#3272](https://github.com/vatesfr/xen-orchestra/issues/3272) (PR [#3351](https://github.com/vatesfr/xen-orchestra/pull/3351))
- [Remotes] Add a field for NFS remotes to set mount options [#1793](https://github.com/vatesfr/xen-orchestra/issues/1793) (PR [#3353](https://github.com/vatesfr/xen-orchestra/pull/3353))
### Bug fixes

View File

@ -35,8 +35,8 @@ list.params = {
id: { type: 'string' },
}
export async function create ({ name, url }) {
return this.createRemote({ name, url })
export async function create ({ name, url, options }) {
return this.createRemote({ name, url, options })
}
create.permission = 'admin'
@ -44,10 +44,11 @@ create.description = 'Creates a new fs remote point'
create.params = {
name: { type: 'string' },
url: { type: 'string' },
options: { type: 'string', optional: true },
}
export async function set ({ id, name, url, enabled }) {
await this.updateRemote(id, { name, url, enabled })
export async function set ({ id, name, url, options, enabled }) {
await this.updateRemote(id, { name, url, options, enabled })
}
set.permission = 'admin'
@ -56,6 +57,7 @@ set.params = {
id: { type: 'string' },
name: { type: 'string', optional: true },
url: { type: 'string', optional: true },
options: { type: ['string', 'null'], optional: true },
enabled: { type: 'boolean', optional: true },
}

View File

@ -88,17 +88,18 @@ export default class {
return remote.properties
}
async createRemote ({ name, url }) {
async createRemote ({ name, url, options }) {
const remote = await this._remotes.add({
name,
url,
options,
enabled: false,
error: '',
})
return /* await */ this.updateRemote(remote.get('id'), { enabled: true })
}
updateRemote (id, { name, url, enabled }) {
updateRemote (id, { name, url, options, enabled }) {
const handlers = this._handlers
const handler = handlers[id]
if (handler !== undefined) {
@ -106,7 +107,12 @@ export default class {
ignoreErrors.call(handler.forget())
}
return this._updateRemote(id, { name, url, enabled })
return this._updateRemote(id, {
name,
url,
options,
enabled,
})
}
@synchronized()

View File

@ -465,6 +465,7 @@ const messages = {
remotePath: 'Path',
remoteState: 'State',
remoteDevice: 'Device',
remoteOptions: 'Options',
remoteShare: 'Share',
remoteAction: 'Action',
remoteAuth: 'Auth',
@ -482,6 +483,7 @@ const messages = {
remoteNfsPlaceHolderHost: 'host *',
remoteNfsPlaceHolderPort: 'Port',
remoteNfsPlaceHolderPath: 'path/to/backup',
remoteNfsPlaceHolderOptions: 'Custom mount options',
remoteSmbPlaceHolderRemotePath: 'subfolder [path\\\\to\\\\backup]',
remoteSmbPlaceHolderUsername: 'Username',
remoteSmbPlaceHolderPassword: 'Password',

View File

@ -1948,8 +1948,10 @@ export const getRemote = remote =>
error(_('getRemote'), err.message || String(err))
)
export const createRemote = (name, url) =>
_call('remote.create', { name, url })::tap(subscribeRemotes.forceRefresh)
export const createRemote = (name, url, options) =>
_call('remote.create', { name, url, options })::tap(
subscribeRemotes.forceRefresh
)
export const deleteRemote = remote =>
_call('remote.delete', { id: resolveId(remote) })::tap(
@ -1980,8 +1982,8 @@ export const disableRemote = remote =>
subscribeRemotes.forceRefresh
)
export const editRemote = (remote, { name, url }) =>
_call('remote.set', resolveIds({ remote, name, url }))::tap(
export const editRemote = (remote, { name, url, options }) =>
_call('remote.set', resolveIds({ remote, name, url, options }))::tap(
subscribeRemotes.forceRefresh
)

View File

@ -30,12 +30,14 @@ const _changeUrlElement = (value, { remote, element }) =>
url: format({ ...remote, [element]: value === null ? undefined : value }),
})
const _showError = remote => alert(_('remoteConnectionFailed'), remote.error)
const _editRemote = (name, { remote }) => editRemote(remote, { name })
const _editRemoteName = (name, { remote }) => editRemote(remote, { name })
const _editRemoteOptions = (options, { remote }) =>
editRemote(remote, { options: options !== '' ? options : null })
const COLUMN_NAME = {
itemRenderer: (remote, { formatMessage }) => (
<Text
data-remote={remote}
onChange={_editRemote}
onChange={_editRemoteName}
placeholder={formatMessage(messages.remoteMyNamePlaceHolder)}
value={remote.name}
/>
@ -137,6 +139,16 @@ const COLUMNS_NFS_REMOTE = [
name: _('remoteDevice'),
},
{
name: _('remoteOptions'),
itemRenderer: remote => (
<Text
data-remote={remote}
onChange={_editRemoteOptions}
value={remote.options || ''}
/>
),
},
COLUMN_STATE,
]
const COLUMNS_SMB_REMOTE = [

View File

@ -28,6 +28,7 @@ export default [
host: undefined,
inputTypeId: generateRandomId(),
name: undefined,
options: undefined,
password: undefined,
path: undefined,
port: undefined,
@ -45,6 +46,7 @@ export default [
domain = remote.domain,
host = remote.host,
name,
options = remote.options,
password = remote.password,
path = remote.path,
port = remote.port,
@ -62,6 +64,7 @@ export default [
type,
username,
}),
options,
}).then(reset)
},
createRemote: ({ reset }) => async (state, { remotes }) => {
@ -78,6 +81,7 @@ export default [
domain,
host,
name,
options,
password,
path,
port,
@ -103,7 +107,7 @@ export default [
}
const url = format(urlParams)
return createRemote(name, url)
return createRemote(name, url, options)
.then(reset)
.catch(err => error('Create Remote', err.message || String(err)))
},
@ -119,6 +123,7 @@ export default [
domain = remote.domain || '',
host = remote.host || '',
name = remote.name || '',
options = remote.options || '',
password = remote.password || '',
parsedPath,
path = parsedPath || '',
@ -212,6 +217,19 @@ export default [
value={path}
/>
</div>
<div className='input-group form-group'>
<span className='input-group-addon'>-o</span>
<input
className='form-control'
name='options'
onChange={effects.linkState}
placeholder={formatMessage(
messages.remoteNfsPlaceHolderOptions
)}
type='text'
value={options}
/>
</div>
</fieldset>
)}
{type === 'smb' && (