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' import LocalHandler from './local'
const DEFAULT_NFS_OPTIONS = 'vers=3'
export default class NfsHandler extends LocalHandler { export default class NfsHandler extends LocalHandler {
get type () { get type () {
return 'nfs' return 'nfs'
@ -52,12 +54,12 @@ export default class NfsHandler extends LocalHandler {
async _mount () { async _mount () {
await fs.ensureDir(this._getRealPath()) await fs.ensureDir(this._getRealPath())
const { host, path, port } = this._remote const { host, path, port, options } = this._remote
return execa('mount', [ return execa('mount', [
'-t', '-t',
'nfs', 'nfs',
'-o', '-o',
'vers=3', DEFAULT_NFS_OPTIONS + (options !== undefined ? `,${options}` : ''),
`${host}${port !== undefined ? ':' + port : ''}:${path}`, `${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(), 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)) - [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 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)) - [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 ### Bug fixes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,7 @@ export default [
host: undefined, host: undefined,
inputTypeId: generateRandomId(), inputTypeId: generateRandomId(),
name: undefined, name: undefined,
options: undefined,
password: undefined, password: undefined,
path: undefined, path: undefined,
port: undefined, port: undefined,
@ -45,6 +46,7 @@ export default [
domain = remote.domain, domain = remote.domain,
host = remote.host, host = remote.host,
name, name,
options = remote.options,
password = remote.password, password = remote.password,
path = remote.path, path = remote.path,
port = remote.port, port = remote.port,
@ -62,6 +64,7 @@ export default [
type, type,
username, username,
}), }),
options,
}).then(reset) }).then(reset)
}, },
createRemote: ({ reset }) => async (state, { remotes }) => { createRemote: ({ reset }) => async (state, { remotes }) => {
@ -78,6 +81,7 @@ export default [
domain, domain,
host, host,
name, name,
options,
password, password,
path, path,
port, port,
@ -103,7 +107,7 @@ export default [
} }
const url = format(urlParams) const url = format(urlParams)
return createRemote(name, url) return createRemote(name, url, options)
.then(reset) .then(reset)
.catch(err => error('Create Remote', err.message || String(err))) .catch(err => error('Create Remote', err.message || String(err)))
}, },
@ -119,6 +123,7 @@ export default [
domain = remote.domain || '', domain = remote.domain || '',
host = remote.host || '', host = remote.host || '',
name = remote.name || '', name = remote.name || '',
options = remote.options || '',
password = remote.password || '', password = remote.password || '',
parsedPath, parsedPath,
path = parsedPath || '', path = parsedPath || '',
@ -212,6 +217,19 @@ export default [
value={path} value={path}
/> />
</div> </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> </fieldset>
)} )}
{type === 'smb' && ( {type === 'smb' && (