def install_napps(cls, napps): """Install local or remote NApps. This method is recursive, it will install each napps and your dependencies. """ mgr = NAppsManager() for napp in napps: mgr.set_napp(*napp) LOG.info(' NApp %s:', mgr.napp_id) if not mgr.is_installed(): try: cls.install_napp(mgr) if not mgr.is_enabled(): cls.enable_napp(mgr) napp_dependencies = mgr.dependencies() if napp_dependencies: LOG.info('Installing Dependencies:') cls.install_napps(napp_dependencies) else: LOG.warning(' Napp already enabled.') except KytosException: continue else: LOG.warning(' Napp already enabled.')
def install_napps(cls, napps): """Install local or remote NApps. This method is recursive, it will install each napps and your dependencies. """ mgr = NAppsManager() for napp in napps: mgr.set_napp(*napp) LOG.info(' NApp %s:', mgr.napp_id) try: if not mgr.is_installed(): # Try to install all NApps, even if # some of them fail. cls.install_napp(mgr) # Enable the NApp if not mgr.is_enabled(): cls.enable_napp(mgr) napp_dependencies = mgr.dependencies() if napp_dependencies: LOG.info('Installing Dependencies:') cls.install_napps(napp_dependencies) else: LOG.info(' Enabled.') else: LOG.warning(' Napp already installed.') except KytosException: LOG.error('Error installing NApp.') continue
class TestNapps(unittest.TestCase): """Test the class NAppsManager.""" def setUp(self): """Execute steps before each tests.""" self.napps_manager = NAppsManager() @staticmethod def get_napps_response_mock(napps=None): """Get mock to napps response.""" if napps is None: napps = [["kytos", "mef_eline"], ["kytos", "of_lldp"]] response = json.dumps({"napps": napps}) mock_response = MagicMock() mock_response.getcode.return_value = 200 mock_response.read.return_value = response mock_response.__enter__.return_value = mock_response return mock_response @patch('urllib.request.urlopen') def test_enabled_property(self, mock_urlopen): """Test enabled property.""" data = MagicMock() data.read.return_value = '{"napps": "ABC", "installed_napps": "DEF"}' mock_urlopen.return_value = data self.assertEqual(str(self.napps_manager._enabled), 'ABC') def test_enabled_property__error(self): """Test enabled property to error case.""" with self.assertRaises(SystemExit): # pylint: disable=pointless-statement self.napps_manager._enabled # pylint: enable=pointless-statement self.assertIsNone(self.napps_manager._NAppsManager__local_enabled) @patch('urllib.request.urlopen') def test_installed_property(self, mock_urlopen): """Test installed property.""" data = MagicMock() data.read.return_value = '{"napps": "ABC", "installed_napps": "DEF"}' mock_urlopen.return_value = data self.assertEqual(str(self.napps_manager._installed), 'DEF') def test_installed_property__error(self): """Test installed property to error case.""" with self.assertRaises(SystemExit): # pylint: disable=pointless-statement self.napps_manager._installed # pylint: enable=pointless-statement self.assertIsNone(self.napps_manager._NAppsManager__local_installed) def test_napp_id_property(self): """Test napp_id property.""" self.napps_manager.user = '******' self.napps_manager.napp = 'napp' self.assertEqual(self.napps_manager.napp_id, 'user/napp') def test_set_napp(self): """Test set_napp method.""" self.napps_manager.set_napp('user', 'napp', 'version') self.assertEqual(self.napps_manager.user, 'user') self.assertEqual(self.napps_manager.napp, 'napp') self.assertEqual(self.napps_manager.version, 'version') def test_get_napps(self): """Test method get_napps used to find enabled and installed napps. """ mock_path = Mock() def glob_side_effect(args): """Path.glob to mock finding paths with kytos.json file.""" self.assertEqual(args, "*/*/kytos.json") mock_path1 = Mock() mock_path1.parts = ['kytos', 'of_core', 'kytos.json'] mock_path2 = Mock() mock_path2.parts = ['kytos', 'of_lldp', 'kytos.json'] return [mock_path1, mock_path2] mock_path.glob = glob_side_effect # pylint: disable=protected-access get_return = self.napps_manager._get_napps(mock_path) self.assertEqual(get_return[0][0], 'kytos') self.assertEqual(get_return[0][1], 'of_core') self.assertEqual(get_return[1][0], 'kytos') self.assertEqual(get_return[1][1], 'of_lldp') def test_get_enabled_local(self): """Test get_enabled_local used to find enabled napps in local machine""" # Mock kytos.json path mock_path = Mock() def glob_side_effect(args): """Path.glob to mock finding paths with kytos.json file.""" self.assertEqual(args, "*/*/kytos.json") mock_path1 = Mock() mock_path1.parts = ['kytos', 'of_core', 'kytos.json'] return [mock_path1] mock_path.glob = glob_side_effect mock_prop_enabled = PropertyMock() with patch.object(NAppsManager, '_enabled', mock_prop_enabled): mock_prop_enabled.return_value = mock_path get_return = self.napps_manager.get_enabled_local() self.assertEqual(get_return[0][0], 'kytos') self.assertEqual(get_return[0][1], 'of_core') self.assertEqual(mock_prop_enabled.call_count, 1) def test_get_installed_local(self): """Test get_installed_local used to find installed napps in local machine""" # Mock kytos.json path mock_path = Mock() def glob_side_effect(args): """Path.glob to mock finding paths with kytos.json file.""" self.assertEqual(args, "*/*/kytos.json") mock_path1 = Mock() mock_path1.parts = ['kytos', 'of_core', 'kytos.json'] return [mock_path1] mock_path.glob = glob_side_effect mock_prop_installed = PropertyMock() with patch.object(NAppsManager, '_installed', mock_prop_installed): mock_prop_installed.return_value = mock_path get_return = self.napps_manager.get_installed_local() self.assertEqual(get_return[0][0], 'kytos') self.assertEqual(get_return[0][1], 'of_core') self.assertEqual(mock_prop_installed.call_count, 1) @patch('urllib.request.urlopen') def test_get_installed(self, mock_urlopen): """Test method get_installed to find all installed napps.""" mock_urlopen.return_value = self.get_napps_response_mock() installed_napps = self.napps_manager.get_installed() self.assertEqual(len(installed_napps), 2) self.assertEqual(installed_napps[0], ("kytos", "mef_eline")) self.assertEqual(installed_napps[1], ("kytos", "of_lldp")) def test_get_installed__connection_error(self): """Test method get_installed to connection error case.""" with self.assertRaises(KytosException) as context: self.napps_manager.get_installed() self.assertEqual('<urlopen error [Errno 111] Connection refused>', str(context.exception)) @patch('urllib.request.urlopen') def test_get_installed__error(self, mock_urlopen): """Test method get_installed with API error.""" mock_response = MagicMock() mock_response.getcode.return_value = 500 mock_urlopen.return_value = mock_response with self.assertRaises(KytosException) as context: self.napps_manager.get_installed() self.assertEqual('Error calling Kytos to check installed NApps.', str(context.exception)) @patch('urllib.request.urlopen') def test_get_enabled(self, mock_urlopen): """Test method get_enabled to find all enabled napps.""" mock_urlopen.return_value = self.get_napps_response_mock() installed_napps = self.napps_manager.get_enabled() self.assertEqual(len(installed_napps), 2) self.assertEqual(installed_napps[0], ("kytos", "mef_eline")) self.assertEqual(installed_napps[1], ("kytos", "of_lldp")) def test_get_enabled__connection_error(self): """Test method get_enabled to connection error case.""" with self.assertRaises(KytosException) as context: self.napps_manager.get_enabled() self.assertEqual('<urlopen error [Errno 111] Connection refused>', str(context.exception)) @patch('urllib.request.urlopen') def test_get_enabled__error(self, mock_urlopen): """Test method get_enabled with API error.""" mock_response = MagicMock() mock_response.getcode.return_value = 500 mock_urlopen.return_value = mock_response with self.assertRaises(KytosException) as context: self.napps_manager.get_enabled() self.assertEqual('Error calling Kytos to check enabled NApps.', str(context.exception)) @patch('urllib.request.urlopen') def test_is_enabled(self, mock_urlopen): """Test is_enabled method.""" mock_urlopen.return_value = self.get_napps_response_mock() self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' self.assertTrue(self.napps_manager.is_enabled()) @patch('urllib.request.urlopen') def test_is_installed(self, mock_urlopen): """Test is_installed method.""" mock_urlopen.return_value = self.get_napps_response_mock() self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' self.assertTrue(self.napps_manager.is_installed()) @patch('urllib.request.urlopen') def test_get_disabled(self, mock_urlopen): """Test get_disabled method.""" enabled = [["kytos", "mef_eline"]] mock_urlopen.side_effect = [ self.get_napps_response_mock(), self.get_napps_response_mock(enabled) ] disabled = self.napps_manager.get_disabled() self.assertEqual(disabled, [('kytos', 'of_lldp')]) @patch('urllib.request.urlopen') def test_dependencies(self, mock_urlopen): """Test dependencies method.""" napps = {"napp_dependencies": ["kytos/mef_eline", "kytos/of_lldp"]} data = MagicMock() data.read.return_value = json.dumps(napps) mock_urlopen.return_value = data dependencies = self.napps_manager.dependencies() expected_dependencies = [('kytos', 'mef_eline'), ('kytos', 'of_lldp')] self.assertEqual(dependencies, expected_dependencies) @patch('urllib.request.urlopen') def test_get_description(self, mock_urlopen): """Test get_description method.""" data = MagicMock() data.read.return_value = '{"description": "ABC"}' mock_urlopen.return_value = data description = self.napps_manager.get_description() self.assertEqual(description, 'ABC') @patch('urllib.request.urlopen') def test_get_version(self, mock_urlopen): """Test get_version method.""" data = MagicMock() data.read.return_value = '{"version": "123"}' mock_urlopen.return_value = data version = self.napps_manager.get_version() self.assertEqual(version, '123') @patch('urllib.request.urlopen') def test_get_napp_key(self, mock_urlopen): """Test _get_napp_key method.""" data = MagicMock() data.read.return_value = '{"key": "ABC"}' mock_urlopen.return_value = data self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' meta_key = self.napps_manager._get_napp_key('key') self.assertEqual(meta_key, 'ABC') @patch('urllib.request.urlopen') def test_disable(self, mock_urlopen): """Test disable method.""" data = MagicMock() data.read.return_value = '{}' mock_urlopen.return_value = data self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' self.napps_manager.disable() uri = self.napps_manager._kytos_api + self.napps_manager._NAPP_DISABLE uri = uri.format('kytos', 'mef_eline') mock_urlopen.assert_called_with(uri) @patch('kytos.utils.napps.LOG') @patch('urllib.request.urlopen') def test_disable__error(self, *args): """Test disable method to error case.""" (mock_urlopen, mock_logger) = args http_errors = [ HTTPError('url', 400, 'msg', 'hdrs', MagicMock()), HTTPError('url', 500, 'msg', 'hdrs', MagicMock()) ] mock_urlopen.side_effect = http_errors self.napps_manager.disable() self.napps_manager.disable() self.assertEqual(mock_logger.error.call_count, 2) @patch('urllib.request.urlopen') def test_enable(self, mock_urlopen): """Test enable method.""" data = MagicMock() data.read.return_value = '{}' mock_urlopen.return_value = data self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' self.napps_manager.enable() uri = self.napps_manager._kytos_api + self.napps_manager._NAPP_ENABLE uri = uri.format('kytos', 'mef_eline') mock_urlopen.assert_called_with(uri) @patch('kytos.utils.napps.LOG') @patch('urllib.request.urlopen') def test_enable__error(self, *args): """Test enable method to error case.""" (mock_urlopen, mock_logger) = args http_errors = [ HTTPError('url', 400, 'msg', 'hdrs', MagicMock()), HTTPError('url', 500, 'msg', 'hdrs', MagicMock()) ] mock_urlopen.side_effect = http_errors self.napps_manager.enable() self.napps_manager.enable() self.assertEqual(mock_logger.error.call_count, 2) @patch('urllib.request.urlopen') def test_enabled_dir(self, mock_urlopen): """Test enabled_dir method.""" data = MagicMock() data.read.return_value = '{"napps": "ABC", "installed_napps": "DEF"}' mock_urlopen.return_value = data self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' enabled_dir = self.napps_manager.enabled_dir() self.assertEqual(str(enabled_dir), 'ABC/kytos/mef_eline') @patch('urllib.request.urlopen') def test_installed_dir(self, mock_urlopen): """Test installed_dir method.""" data = MagicMock() data.read.return_value = '{"napps": "ABC", "installed_napps": "DEF"}' mock_urlopen.return_value = data self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' installed_dir = self.napps_manager.installed_dir() self.assertEqual(str(installed_dir), 'DEF/kytos/mef_eline') @patch('urllib.request.urlopen') def test_remote_uninstall(self, mock_urlopen): """Test remote_uninstall method.""" data = MagicMock() data.read.return_value = '{}' mock_urlopen.return_value = data self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' self.napps_manager.remote_uninstall() uninstall_uri = self.napps_manager._NAPP_UNINSTALL uri = self.napps_manager._kytos_api + uninstall_uri uri = uri.format('kytos', 'mef_eline') mock_urlopen.assert_called_with(uri) @patch('kytos.utils.napps.LOG') @patch('urllib.request.urlopen') def test_remote_uninstall__error(self, *args): """Test remote_uninstall method to error case.""" (mock_urlopen, mock_logger) = args http_errors = [ HTTPError('url', 400, 'msg', 'hdrs', MagicMock()), HTTPError('url', 500, 'msg', 'hdrs', MagicMock()) ] mock_urlopen.side_effect = http_errors self.napps_manager.remote_uninstall() self.napps_manager.remote_uninstall() self.assertEqual(mock_logger.error.call_count, 2) @patch('urllib.request.urlopen') def test_remote_install(self, mock_urlopen): """Test remote_install method.""" data = MagicMock() data.read.return_value = '{}' mock_urlopen.return_value = data self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' self.napps_manager.remote_install() install_uri = self.napps_manager._NAPP_INSTALL uri = self.napps_manager._kytos_api + install_uri uri = uri.format('kytos', 'mef_eline') mock_urlopen.assert_called_with(uri) def test_valid_name(self): """Test valid_name method.""" valid_name = self.napps_manager.valid_name('username') invalid_name = self.napps_manager.valid_name('_username') self.assertTrue(valid_name) self.assertFalse(invalid_name) @patch('jinja2.Environment.get_template') def test_render_template(self, mock_get_template): """Test render_template method.""" template = MagicMock() mock_get_template.return_value = template self.napps_manager.render_template('', 'filename', 'context') mock_get_template.assert_called_with('filename') template.render.assert_called_with('context') @patch('kytos.utils.napps.NAppsClient') def test_search(self, mock_napps_client): """Test search method.""" napp_1 = { 'username': '******', 'name': 'mef_eline', 'description': '', 'tags': ['A', 'B'] } napp_2 = { 'username': '******', 'name': 'any', 'description': '', 'tags': ['A', 'B'] } napps_client = MagicMock() napps_client.get_napps.return_value = [napp_1, napp_2] mock_napps_client.return_value = napps_client # pattern to match strings that start with letters pattern = re.compile('^[a-z]+') napps = self.napps_manager.search(pattern) self.assertEqual(napps, [napp_1]) @patch('os.makedirs') @patch('builtins.open') @patch('builtins.input') @patch('kytos.utils.napps.NAppsManager.render_template') def test_create_napp(self, *args): """Test create_napp method.""" (mock_render_template, mock_input, _, mock_mkdirs) = args mock_input.side_effect = ['username', 'napp', None] self.napps_manager.create_napp() tmpl_path = SKEL_PATH / 'napp-structure/username/napp' description = '# TODO: <<<< Insert your NApp description here >>>>' context = { 'username': '******', 'napp': 'napp', 'description': description } calls = [] for tmp in [ '__init__.py', 'main.py', '.gitignore', 'kytos.json', 'README.rst', 'settings.py' ]: calls.append(call(tmpl_path, '{}.template'.format(tmp), context)) calls.append( call('{}/ui'.format(tmpl_path), 'README.rst.template', context)) mock_mkdirs.assert_has_calls([ call('username', exist_ok=True), call('username/napp'), call('username/napp/ui/k-info-panel'), call('username/napp/ui/k-toolbar'), call('username/napp/ui/k-action-menu') ]) mock_render_template.assert_has_calls(calls, any_order=True) def test_check_module(self): """Test _check_module method.""" folder = MagicMock() folder.exists.return_value = False self.napps_manager._check_module(folder) folder.mkdir.assert_called() (folder / '__init__.py').touch.assert_called() @patch('pathspec.pathspec.PathSpec.match_tree') @patch('tarfile.TarFile.add') @patch('os.remove') @patch('os.walk') @patch('os.getcwd') @patch('builtins.open') def test_build_napp_package(self, *args): """Test build_napp_package method.""" (_, mock_getcwd, mock_walk, _, mock_add, mock_match_tree) = args with tempfile.TemporaryDirectory() as tmp_dir: mock_getcwd.return_value = tmp_dir files = ['username/napp/A', 'username/napp/B', 'username/napp/C'] mock_walk.return_value = [(tmp_dir, ['username/napp/.git'], files)] mock_match_tree.return_value = ['username/napp/C'] self.napps_manager.build_napp_package('username/napp') calls = [ call(PurePosixPath('username/napp/A')), call(PurePosixPath('username/napp/B')) ] mock_add.assert_has_calls(calls) @patch('ruamel.yaml.YAML.load', return_value='openapi') @patch('pathlib.Path.open') @patch('builtins.open') def test_create_metadata(self, *args): """Test create_metadata method.""" (mock_open, _, _) = args enter_file_1 = MagicMock() enter_file_1.read.return_value = '{}' enter_file_2 = MagicMock() enter_file_2.read.return_value = 'readme' mock_open.return_value.__enter__.side_effect = [ enter_file_1, enter_file_2 ] metadata = self.napps_manager.create_metadata() self.assertEqual(metadata, { 'readme': 'readme', 'OpenAPI_Spec': '"openapi"' }) @patch('kytos.utils.napps.NAppsClient') @patch('kytos.utils.napps.NAppsManager.build_napp_package') @patch('kytos.utils.napps.NAppsManager.create_metadata') @patch('kytos.utils.napps.NAppsManager.prepare') def test_upload(self, *args): """Test upload method.""" (mock_prepare, mock_create, mock_build, mock_napps_client) = args mock_create.return_value = {'name': 'ABC'} mock_build.return_value = 'package' napps_client = MagicMock() mock_napps_client.return_value = napps_client self.napps_manager.upload() mock_prepare.assert_called() mock_create.assert_called() mock_build.assert_called_with('ABC') napps_client.upload_napp.assert_called_with({'name': 'ABC'}, 'package') @patch('kytos.utils.napps.NAppsClient') def test_delete(self, mock_napps_client): """Test delete method.""" napps_client = MagicMock() mock_napps_client.return_value = napps_client self.napps_manager.user = '******' self.napps_manager.napp = 'mef_eline' self.napps_manager.delete() napps_client.delete.assert_called_with('kytos', 'mef_eline') @patch('sys.exit') @patch('kytos.utils.napps.OpenAPI') @patch('kytos.utils.napps.NAppsManager._ask_openapi', return_value=True) def test_prepare(self, *args): """Test prepare method.""" (_, mock_openapi, _) = args self.napps_manager.prepare() napp_path = Path() tpl_path = SKEL_PATH / 'napp-structure/username/napp' mock_openapi.assert_called_with(napp_path, tpl_path) mock_openapi.return_value.render_template.assert_called() @patch('pathlib.Path.exists') @patch('builtins.input') def test_ask_openapi(self, *args): """Test _ask_openapi method.""" (mock_input, mock_exists) = args mock_input.side_effect = ['', '', 'yes', 'no'] mock_exists.side_effect = [True, False, False, False] for expected in [False, True, True, False]: response = self.napps_manager._ask_openapi() self.assertEqual(response, expected) @patch('kytos.utils.napps.NAppsClient') def test_reload(self, mock_napps_client): """Test reload method.""" napps_client = MagicMock() mock_napps_client.return_value = napps_client napps = [] self.napps_manager.reload(napps) napps_client.reload_napps.assert_called_with(napps)