salt/tests/unit/modules/test_parallels.py
Erik Johnson 3184168365 Use explicit unicode strings + break up salt.utils
This PR is part of what will be an ongoing effort to use explicit
unicode strings in Salt. Because Python 3 does not suport Python 2's raw
unicode string syntax (i.e. `ur'\d+'`), we must use
`salt.utils.locales.sdecode()` to ensure that the raw string is unicode.
However, because of how `salt/utils/__init__.py` has evolved into the
hulking monstrosity it is today, this means importing a large module in
places where it is not needed, which could negatively impact
performance. For this reason, this PR also breaks out some of the
functions from `salt/utils/__init__.py` into new/existing modules under
`salt/utils/`. The long term goal will be that the modules within this
directory do not depend on importing `salt.utils`.

A summary of the changes in this PR is as follows:

* Moves the following functions from `salt.utils` to new locations
  (including a deprecation warning if invoked from `salt.utils`):
  `to_bytes`, `to_str`, `to_unicode`, `str_to_num`, `is_quoted`,
  `dequote`, `is_hex`, `is_bin_str`, `rand_string`,
  `contains_whitespace`, `clean_kwargs`, `invalid_kwargs`, `which`,
  `which_bin`, `path_join`, `shlex_split`, `rand_str`, `is_windows`,
  `is_proxy`, `is_linux`, `is_darwin`, `is_sunos`, `is_smartos`,
  `is_smartos_globalzone`, `is_smartos_zone`, `is_freebsd`, `is_netbsd`,
  `is_openbsd`, `is_aix`
* Moves the functions already deprecated by @rallytime to the bottom of
  `salt/utils/__init__.py` for better organization, so we can keep the
  deprecated ones separate from the ones yet to be deprecated as we
  continue to break up `salt.utils`
* Updates `salt/*.py` and all files under `salt/client/` to use explicit
  unicode string literals.
* Gets rid of implicit imports of `salt.utils` (e.g. `from salt.utils
  import foo` becomes `import salt.utils.foo as foo`).
* Renames the `test.rand_str` function to `test.random_hash` to more
  accurately reflect what it does
* Modifies `salt.utils.stringutils.random()` (née `salt.utils.rand_string()`)
  such that it returns a string matching the passed size. Previously
  this function would get `size` bytes from `os.urandom()`,
  base64-encode it, and return the result, which would in most cases not
  be equal to the passed size.
2017-08-08 13:33:43 -05:00

605 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
# Import python libs
from __future__ import absolute_import
import textwrap
# Import Salt Libs
import salt.modules.parallels as parallels
from salt.exceptions import SaltInvocationError
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import MagicMock, patch, NO_MOCK, NO_MOCK_REASON
# Import third party libs
from salt.ext import six
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ParallelsTestCase(TestCase, LoaderModuleMockMixin):
'''
Test parallels desktop execution module functions
'''
def setup_loader_modules(self):
return {parallels: {}}
def test___virtual__(self):
'''
Test parallels.__virtual__
'''
mock_true = MagicMock(return_value=True)
mock_false = MagicMock(return_value=False)
# Validate false return
with patch('salt.utils.path.which', mock_false):
ret = parallels.__virtual__()
self.assertTrue(isinstance(ret, tuple))
self.assertEqual(len(ret), 2)
self.assertFalse(ret[0])
self.assertTrue(isinstance(ret[1], six.string_types))
# Validate true return
with patch('salt.utils.path.which', mock_true):
ret = parallels.__virtual__()
self.assertTrue(ret)
self.assertEqual(ret, 'parallels')
def test__normalize_args(self):
'''
Test parallels._normalize_args
'''
def _validate_ret(ret):
'''
Assert that the returned data is a list of strings
'''
self.assertTrue(isinstance(ret, list))
for arg in ret:
self.assertTrue(isinstance(arg, six.string_types))
# Validate string arguments
str_args = 'electrolytes --aqueous --anion hydroxide --cation=ammonium free radicals -- hydrogen'
_validate_ret(parallels._normalize_args(str_args))
# Validate list arguments
list_args = ' '.join(str_args)
_validate_ret(parallels._normalize_args(list_args))
# Validate tuple arguments
tuple_args = tuple(list_args)
_validate_ret(parallels._normalize_args(tuple_args))
# Validate dictionary arguments
other_args = {'anion': 'hydroxide', 'cation': 'ammonium'}
_validate_ret(parallels._normalize_args(other_args))
def test__find_guids(self):
'''
Test parallels._find_guids
'''
guid_str = textwrap.dedent('''
PARENT_SNAPSHOT_ID SNAPSHOT_ID
{a5b8999f-5d95-4aff-82de-e515b0101b66}
{a5b8999f-5d95-4aff-82de-e515b0101b66} *{a7345be5-ab66-478c-946e-a6c2caf14909}
''')
guids = ['a5b8999f-5d95-4aff-82de-e515b0101b66',
'a7345be5-ab66-478c-946e-a6c2caf14909']
self.assertEqual(parallels._find_guids(guid_str), guids)
def test_prlsrvctl(self):
'''
Test parallels.prlsrvctl
'''
runas = 'macdev'
# Validate 'prlsrvctl info'
info_cmd = ['prlsrvctl', 'info']
info_fcn = MagicMock()
with patch.dict(parallels.__salt__, {'cmd.run': info_fcn}):
parallels.prlsrvctl('info', runas=runas)
info_fcn.assert_called_once_with(info_cmd, runas=runas)
# Validate 'prlsrvctl usb list'
usb_cmd = ['prlsrvctl', 'usb', 'list']
usb_fcn = MagicMock()
with patch.dict(parallels.__salt__, {'cmd.run': usb_fcn}):
parallels.prlsrvctl('usb', 'list', runas=runas)
usb_fcn.assert_called_once_with(usb_cmd, runas=runas)
# Validate 'prlsrvctl set "--mem-limit auto"'
set_cmd = ['prlsrvctl', 'set', '--mem-limit', 'auto']
set_fcn = MagicMock()
with patch.dict(parallels.__salt__, {'cmd.run': set_fcn}):
parallels.prlsrvctl('set', '--mem-limit auto', runas=runas)
set_fcn.assert_called_once_with(set_cmd, runas=runas)
def test_prlctl(self):
'''
Test parallels.prlctl
'''
runas = 'macdev'
# Validate 'prlctl user list'
user_cmd = ['prlctl', 'user', 'list']
user_fcn = MagicMock()
with patch.dict(parallels.__salt__, {'cmd.run': user_fcn}):
parallels.prlctl('user', 'list', runas=runas)
user_fcn.assert_called_once_with(user_cmd, runas=runas)
# Validate 'prlctl exec "macvm uname"'
exec_cmd = ['prlctl', 'exec', 'macvm', 'uname']
exec_fcn = MagicMock()
with patch.dict(parallels.__salt__, {'cmd.run': exec_fcn}):
parallels.prlctl('exec', 'macvm uname', runas=runas)
exec_fcn.assert_called_once_with(exec_cmd, runas=runas)
# Validate 'prlctl capture "macvm --file macvm.display.png"'
cap_cmd = ['prlctl', 'capture', 'macvm', '--file', 'macvm.display.png']
cap_fcn = MagicMock()
with patch.dict(parallels.__salt__, {'cmd.run': cap_fcn}):
parallels.prlctl('capture', 'macvm --file macvm.display.png', runas=runas)
cap_fcn.assert_called_once_with(cap_cmd, runas=runas)
def test_list_vms(self):
'''
Test parallels.list_vms
'''
runas = 'macdev'
# Validate a simple list
mock_plain = MagicMock()
with patch.object(parallels, 'prlctl', mock_plain):
parallels.list_vms(runas=runas)
mock_plain.assert_called_once_with('list', [], runas=runas)
# Validate listing a single VM
mock_name = MagicMock()
with patch.object(parallels, 'prlctl', mock_name):
parallels.list_vms(name='macvm', runas=runas)
mock_name.assert_called_once_with('list',
['macvm'],
runas=runas)
# Validate listing templates
mock_templ = MagicMock()
with patch.object(parallels, 'prlctl', mock_templ):
parallels.list_vms(template=True, runas=runas)
mock_templ.assert_called_once_with('list',
['--template'],
runas=runas)
# Validate listing extra info
mock_info = MagicMock()
with patch.object(parallels, 'prlctl', mock_info):
parallels.list_vms(info=True, runas=runas)
mock_info.assert_called_once_with('list',
['--info'],
runas=runas)
# Validate listing with extra options
mock_complex = MagicMock()
with patch.object(parallels, 'prlctl', mock_complex):
parallels.list_vms(args=' -o uuid,status', all=True, runas=runas)
mock_complex.assert_called_once_with('list',
['-o', 'uuid,status', '--all'],
runas=runas)
def test_clone(self):
'''
Test parallels.clone
'''
name = 'macvm'
runas = 'macdev'
# Validate clone
mock_clone = MagicMock()
with patch.object(parallels, 'prlctl', mock_clone):
parallels.clone(name, 'macvm_new', runas=runas)
mock_clone.assert_called_once_with('clone',
[name, '--name', 'macvm_new'],
runas=runas)
# Validate linked clone
mock_linked = MagicMock()
with patch.object(parallels, 'prlctl', mock_linked):
parallels.clone(name, 'macvm_link', linked=True, runas=runas)
mock_linked.assert_called_once_with('clone',
[name, '--name', 'macvm_link', '--linked'],
runas=runas)
# Validate template clone
mock_template = MagicMock()
with patch.object(parallels, 'prlctl', mock_template):
parallels.clone(name, 'macvm_templ', template=True, runas=runas)
mock_template.assert_called_once_with('clone',
[name, '--name', 'macvm_templ', '--template'],
runas=runas)
def test_delete(self):
'''
Test parallels.delete
'''
name = 'macvm'
runas = 'macdev'
# Validate delete
mock_delete = MagicMock()
with patch.object(parallels, 'prlctl', mock_delete):
parallels.delete(name, runas=runas)
mock_delete.assert_called_once_with('delete', name, runas=runas)
def test_exists(self):
'''
Test parallels.exists
'''
name = 'macvm'
runas = 'macdev'
# Validate exists
mock_list = MagicMock(return_value='Name: {0}\nState: running'.format(name))
with patch.object(parallels, 'list_vms', mock_list):
self.assertTrue(parallels.exists(name, runas=runas))
# Validate not exists
mock_list = MagicMock(return_value='Name: {0}\nState: running'.format(name))
with patch.object(parallels, 'list_vms', mock_list):
self.assertFalse(parallels.exists('winvm', runas=runas))
def test_start(self):
'''
Test parallels.start
'''
name = 'macvm'
runas = 'macdev'
mock_start = MagicMock()
with patch.object(parallels, 'prlctl', mock_start):
parallels.start(name, runas=runas)
mock_start.assert_called_once_with('start', name, runas=runas)
def test_stop(self):
'''
Test parallels.stop
'''
name = 'macvm'
runas = 'macdev'
# Validate stop
mock_stop = MagicMock()
with patch.object(parallels, 'prlctl', mock_stop):
parallels.stop(name, runas=runas)
mock_stop.assert_called_once_with('stop', [name], runas=runas)
# Validate immediate stop
mock_kill = MagicMock()
with patch.object(parallels, 'prlctl', mock_kill):
parallels.stop(name, kill=True, runas=runas)
mock_kill.assert_called_once_with('stop', [name, '--kill'], runas=runas)
def test_restart(self):
'''
Test parallels.restart
'''
name = 'macvm'
runas = 'macdev'
mock_start = MagicMock()
with patch.object(parallels, 'prlctl', mock_start):
parallels.restart(name, runas=runas)
mock_start.assert_called_once_with('restart', name, runas=runas)
def test_reset(self):
'''
Test parallels.reset
'''
name = 'macvm'
runas = 'macdev'
mock_start = MagicMock()
with patch.object(parallels, 'prlctl', mock_start):
parallels.reset(name, runas=runas)
mock_start.assert_called_once_with('reset', name, runas=runas)
def test_status(self):
'''
Test parallels.status
'''
name = 'macvm'
runas = 'macdev'
mock_start = MagicMock()
with patch.object(parallels, 'prlctl', mock_start):
parallels.status(name, runas=runas)
mock_start.assert_called_once_with('status', name, runas=runas)
def test_exec_(self):
'''
Test parallels.exec_
'''
name = 'macvm'
runas = 'macdev'
mock_start = MagicMock()
with patch.object(parallels, 'prlctl', mock_start):
parallels.exec_(name, 'find /etc/paths.d', runas=runas)
mock_start.assert_called_once_with('exec',
[name, 'find', '/etc/paths.d'],
runas=runas)
def test_snapshot_id_to_name(self):
'''
Test parallels.snapshot_id_to_name
'''
name = 'macvm'
snap_id = 'a5b8999f-5d95-4aff-82de-e515b0101b66'
# Invalid GUID raises error
self.assertRaises(SaltInvocationError,
parallels.snapshot_id_to_name,
name,
'{8-4-4-4-12}')
# Empty return from prlctl raises error (name/snap_id mismatch?)
mock_no_data = MagicMock(return_value='')
with patch.object(parallels, 'prlctl', mock_no_data):
self.assertRaises(SaltInvocationError,
parallels.snapshot_id_to_name,
name,
snap_id)
# Data returned from prlctl is invalid YAML
mock_invalid_data = MagicMock(return_value='[string theory is falsifiable}')
with patch.object(parallels, 'prlctl', mock_invalid_data):
snap_name = parallels.snapshot_id_to_name(name, snap_id)
self.assertEqual(snap_name, '')
# Data returned from prlctl does not render as a dictionary
mock_unknown_data = MagicMock(return_value="['sfermions', 'bosinos']")
with patch.object(parallels, 'prlctl', mock_unknown_data):
snap_name = parallels.snapshot_id_to_name(name, snap_id)
self.assertEqual(snap_name, '')
# Snapshot is unnamed
mock_no_name = MagicMock(return_value='Name:')
with patch.object(parallels, 'prlctl', mock_no_name):
snap_name = parallels.snapshot_id_to_name(name, snap_id)
self.assertEqual(snap_name, '')
# If strict, then raise an error when name is not found
mock_no_name = MagicMock(return_value='Name:')
with patch.object(parallels, 'prlctl', mock_no_name):
self.assertRaises(SaltInvocationError,
parallels.snapshot_id_to_name,
name,
snap_id,
strict=True)
# Return name when found
mock_yes_name = MagicMock(return_value='Name: top')
with patch.object(parallels, 'prlctl', mock_yes_name):
snap_name = parallels.snapshot_id_to_name(name, snap_id)
self.assertEqual(snap_name, 'top')
def test_snapshot_name_to_id(self):
'''
Test parallels.snapshot_name_to_id
'''
name = 'macvm'
snap_ids = ['a5b8999f-5d95-4aff-82de-e515b0101b66',
'a7345be5-ab66-478c-946e-a6c2caf14909']
snap_id = snap_ids[0]
guid_str = textwrap.dedent('''
PARENT_SNAPSHOT_ID SNAPSHOT_ID
{a5b8999f-5d95-4aff-82de-e515b0101b66}
{a5b8999f-5d95-4aff-82de-e515b0101b66} *{a7345be5-ab66-478c-946e-a6c2caf14909}
''')
mock_guids = MagicMock(return_value=guid_str)
# Raise error when no IDs found for snapshot name
with patch.object(parallels, 'prlctl', mock_guids):
mock_no_names = MagicMock(return_value=[])
with patch.object(parallels, 'snapshot_id_to_name', mock_no_names):
self.assertRaises(SaltInvocationError,
parallels.snapshot_name_to_id,
name,
'graviton')
# Validate singly-valued name
with patch.object(parallels, 'prlctl', mock_guids):
mock_one_name = MagicMock(side_effect=[u'', u'ν_e'])
with patch.object(parallels, 'snapshot_id_to_name', mock_one_name):
self.assertEqual(parallels.snapshot_name_to_id(name, u'ν_e'), snap_ids[1])
# Validate multiply-valued name
with patch.object(parallels, 'prlctl', mock_guids):
mock_many_names = MagicMock(side_effect=[u'J/Ψ', u'J/Ψ'])
with patch.object(parallels, 'snapshot_id_to_name', mock_many_names):
self.assertEqual(sorted(parallels.snapshot_name_to_id(name, u'J/Ψ')),
sorted(snap_ids))
# Raise error for multiply-valued name
with patch.object(parallels, 'prlctl', mock_guids):
mock_many_names = MagicMock(side_effect=[u'J/Ψ', u'J/Ψ'])
with patch.object(parallels, 'snapshot_id_to_name', mock_many_names):
self.assertRaises(SaltInvocationError,
parallels.snapshot_name_to_id,
name,
u'J/Ψ',
strict=True)
def test__validate_snap_name(self):
'''
Test parallels._validate_snap_name
'''
name = 'macvm'
snap_id = 'a5b8999f-5d95-4aff-82de-e515b0101b66'
# Validate a GUID passthrough
self.assertEqual(parallels._validate_snap_name(name, snap_id), snap_id)
# Validate an unicode name
mock_snap_symb = MagicMock(return_value=snap_id)
with patch.object(parallels, 'snapshot_name_to_id', mock_snap_symb):
self.assertEqual(parallels._validate_snap_name(name, u'π'), snap_id)
mock_snap_symb.assert_called_once_with(name, u'π', strict=True, runas=None)
# Validate an ascii name
mock_snap_name = MagicMock(return_value=snap_id)
with patch.object(parallels, 'snapshot_name_to_id', mock_snap_name):
self.assertEqual(parallels._validate_snap_name(name, 'pion'), snap_id)
mock_snap_name.assert_called_once_with(name, 'pion', strict=True, runas=None)
# Validate a numerical name
mock_snap_numb = MagicMock(return_value=snap_id)
with patch.object(parallels, 'snapshot_name_to_id', mock_snap_numb):
self.assertEqual(parallels._validate_snap_name(name, '3.14159'), snap_id)
mock_snap_numb.assert_called_once_with(name, u'3.14159', strict=True, runas=None)
# Validate not strict (think neutrino oscillation)
mock_snap_non_strict = MagicMock(return_value=snap_id)
with patch.object(parallels, 'snapshot_name_to_id', mock_snap_non_strict):
self.assertEqual(parallels._validate_snap_name(name, u'e_ν', strict=False), snap_id)
mock_snap_non_strict.assert_called_once_with(name, u'e_ν', strict=False, runas=None)
def test_list_snapshots(self):
'''
Test parallels.list_snapshots
'''
name = 'macvm'
guid_str = textwrap.dedent('''
PARENT_SNAPSHOT_ID SNAPSHOT_ID
{a5b8999f-5d95-4aff-82de-e515b0101b66}
{a5b8999f-5d95-4aff-82de-e515b0101b66} *{a7345be5-ab66-478c-946e-a6c2caf14909}
{a5b8999f-5d95-4aff-82de-e515b0101b66} {5da9faef-cb0e-466d-9b41-e5571b62ac2a}
''')
# Validate listing all snapshots for the VM
mock_prlctl = MagicMock(return_value=guid_str)
with patch.object(parallels, 'prlctl', mock_prlctl):
parallels.list_snapshots(name)
mock_prlctl.assert_called_once_with('snapshot-list', [name], runas=None)
# Validate listing all snapshots in tree mode
mock_prlctl = MagicMock(return_value=guid_str)
with patch.object(parallels, 'prlctl', mock_prlctl):
parallels.list_snapshots(name, tree=True)
mock_prlctl.assert_called_once_with('snapshot-list', [name, '--tree'], runas=None)
# Validate listing a single snapshot
snap_name = 'muon'
mock_snap_name = MagicMock(return_value=snap_name)
with patch.object(parallels, '_validate_snap_name', mock_snap_name):
mock_prlctl = MagicMock(return_value=guid_str)
with patch.object(parallels, 'prlctl', mock_prlctl):
parallels.list_snapshots(name, snap_name)
mock_prlctl.assert_called_once_with('snapshot-list',
[name, '--id', snap_name],
runas=None)
# Validate listing snapshot ID and name pairs
snap_names = ['electron', 'muon', 'tauon']
mock_snap_name = MagicMock(side_effect=snap_names)
with patch.object(parallels, 'snapshot_id_to_name', mock_snap_name):
mock_prlctl = MagicMock(return_value=guid_str)
with patch.object(parallels, 'prlctl', mock_prlctl):
ret = parallels.list_snapshots(name, names=True)
for snap_name in snap_names:
self.assertIn(snap_name, ret)
mock_prlctl.assert_called_once_with('snapshot-list', [name], runas=None)
def test_snapshot(self):
'''
Test parallels.snapshot
'''
name = 'macvm'
# Validate creating a snapshot
mock_snap = MagicMock(return_value='')
with patch.object(parallels, 'prlctl', mock_snap):
parallels.snapshot(name)
mock_snap.assert_called_once_with('snapshot', [name], runas=None)
# Validate creating a snapshot with a name
snap_name = 'h_0'
mock_snap_name = MagicMock(return_value='')
with patch.object(parallels, 'prlctl', mock_snap_name):
parallels.snapshot(name, snap_name)
mock_snap_name.assert_called_once_with('snapshot',
[name, '--name', snap_name],
runas=None)
# Validate creating a snapshot with a name and a description
snap_name = 'h_0'
snap_desc = textwrap.dedent('The ground state particle of the higgs '
'multiplet family of bosons')
mock_snap_name = MagicMock(return_value='')
with patch.object(parallels, 'prlctl', mock_snap_name):
parallels.snapshot(name, snap_name, snap_desc)
mock_snap_name.assert_called_once_with('snapshot',
[name,
'--name', snap_name,
'--description', snap_desc],
runas=None)
def test_delete_snapshot(self):
'''
Test parallels.delete_snapshot
'''
delete_message = ('Delete the snapshot...\n'
'The snapshot has been successfully deleted.')
# Validate single ID
name = 'macvm'
snap_name = 'kaon'
snap_id = 'c2eab062-a635-4ccd-b9ae-998370f898b5'
mock_snap_name = MagicMock(return_value=snap_id)
with patch.object(parallels, '_validate_snap_name', mock_snap_name):
mock_delete = MagicMock(return_value=delete_message)
with patch.object(parallels, 'prlctl', mock_delete):
ret = parallels.delete_snapshot(name, snap_name)
self.assertEqual(ret, delete_message)
mock_delete.assert_called_once_with('snapshot-delete',
[name, '--id', snap_id],
runas=None)
# Validate multiple IDs
name = 'macvm'
snap_name = 'higgs doublet'
snap_ids = ['c2eab062-a635-4ccd-b9ae-998370f898b5',
'8aca07c5-a0e1-4dcb-ba75-cb154d46d516']
mock_snap_ids = MagicMock(return_value=snap_ids)
with patch.object(parallels, '_validate_snap_name', mock_snap_ids):
mock_delete = MagicMock(return_value=delete_message)
with patch.object(parallels, 'prlctl', mock_delete):
ret = parallels.delete_snapshot(name, snap_name, all=True)
mock_ret = {snap_ids[0]: delete_message,
snap_ids[1]: delete_message}
self.assertDictEqual(ret, mock_ret)
mock_delete.assert_any_call('snapshot-delete',
[name, '--id', snap_ids[0]],
runas=None)
mock_delete.assert_any_call('snapshot-delete',
[name, '--id', snap_ids[1]],
runas=None)
def test_revert_snapshot(self):
'''
Test parallels.revert_snapshot
'''
name = 'macvm'
snap_name = 'k-bar'
snap_id = 'c2eab062-a635-4ccd-b9ae-998370f898b5'
mock_snap_name = MagicMock(return_value=snap_id)
with patch.object(parallels, '_validate_snap_name', mock_snap_name):
mock_delete = MagicMock(return_value='')
with patch.object(parallels, 'prlctl', mock_delete):
parallels.revert_snapshot(name, snap_name)
mock_delete.assert_called_once_with('snapshot-switch',
[name, '--id', snap_id],
runas=None)