def test_bad_get_north_plugin_config(self, exc_name, log_exc_name, msg): with patch.object(_logger, log_exc_name) as patch_log_exc: with patch.object(common, 'load_and_fetch_python_plugin_info', side_effect=[exc_name]): PluginDiscovery.get_plugin_config("http", "north", False) assert 1 == patch_log_exc.call_count args, kwargs = patch_log_exc.call_args assert msg in args[0]
def test_fetch_c_hybrid_plugins_installed(self): info = { "version": "1.6.0", "name": "FlirAX8", "config": { "asset": { "description": "Default asset name", "default": "flir", "displayName": "Asset Name", "type": "string" }, "plugin": { "description": "A Modbus connected Flir AX8 infrared camera", "default": "FlirAX8", "readonly": "true", "type": "string" } } } with patch.object(utils, "find_c_plugin_libs", return_value=[("FlirAX8", "json") ]) as patch_plugin_lib: with patch.object(common, "load_and_fetch_c_hybrid_plugin_info", return_value=info) as patch_hybrid_plugin_info: PluginDiscovery.fetch_c_plugins_installed('south', True) patch_hybrid_plugin_info.assert_called_once_with( info['name'], True) patch_plugin_lib.assert_called_once_with('south')
def test_bad_fetch_c_north_plugin_installed(self, info, exc_count): with patch.object(_logger, "exception") as patch_log_exc: with patch.object(utils, "find_c_plugin_libs", return_value=[("PI_Server", "binary")]) as patch_plugin_lib: with patch.object(utils, "get_plugin_info", return_value=info) as patch_plugin_info: PluginDiscovery.fetch_c_plugins_installed("north", False) patch_plugin_info.assert_called_once_with('PI_Server', dir='north') patch_plugin_lib.assert_called_once_with('north') assert exc_count == patch_log_exc.call_count
def test_deprecated_python_plugins(self, info, warn_count, is_config=True): with patch.object(_logger, "warning") as patch_log_warn: with patch.object(common, 'load_and_fetch_python_plugin_info', side_effect=[info]): PluginDiscovery.get_plugin_config(info['name'], info['type'], is_config) assert warn_count == patch_log_warn.call_count if warn_count: args, kwargs = patch_log_warn.call_args assert '"{}" plugin is deprecated'.format(info['name']) == args[0]
def test_fetch_c_plugins_installed(self, info, dir_name): with patch.object(utils, "find_c_plugin_libs", return_value=[(info['name'], "binary") ]) as patch_plugin_lib: with patch.object(utils, "get_plugin_info", return_value=info) as patch_plugin_info: PluginDiscovery.fetch_c_plugins_installed(dir_name, True) patch_plugin_info.assert_called_once_with(info['name'], dir=dir_name) patch_plugin_lib.assert_called_once_with(dir_name)
def test_deprecated_c_plugins_installed(self, info, dir_name): info['flag'] = api_utils.DEPRECATED_BIT_MASK_VALUE with patch.object(_logger, "warning") as patch_log_warn: with patch.object(utils, "find_c_plugin_libs", return_value=[(info['name'], "binary")]) as patch_plugin_lib: with patch.object(utils, "get_plugin_info", return_value=info) as patch_plugin_info: PluginDiscovery.fetch_c_plugins_installed(dir_name, True) patch_plugin_info.assert_called_once_with(info['name'], dir=dir_name) patch_plugin_lib.assert_called_once_with(dir_name) assert 1 == patch_log_warn.call_count args, kwargs = patch_log_warn.call_args assert '"{}" plugin is deprecated'.format(info['name']) == args[0]
def test_get_plugin_config(self, info, expected, is_config): with patch.object(common, 'load_and_fetch_python_plugin_info', side_effect=[info]): actual = PluginDiscovery.get_plugin_config("modbus", "south", is_config) assert expected == actual
def test_get_rules_plugins_installed(self, mocker): @asyncio.coroutine def mock_folders(): yield TestPluginDiscovery.mock_py_rule_folders @asyncio.coroutine def mock_c_folders(): yield TestPluginDiscovery.mock_c_rule_folders mock_get_py_folders = mocker.patch.object(PluginDiscovery, "get_plugin_folders", return_value=next( mock_folders())) mock_get_py_plugin_config = mocker.patch.object( PluginDiscovery, "get_plugin_config", side_effect=TestPluginDiscovery.mock_py_rule_config) mock_get_c_folders = mocker.patch.object(utils, "find_c_plugin_libs", return_value=next( mock_c_folders())) mock_get_c_plugin_config = mocker.patch.object( utils, "get_plugin_info", side_effect=TestPluginDiscovery.mock_c_rule_config) plugins = PluginDiscovery.get_plugins_installed("notificationRule") # expected_plugin = TestPluginDiscovery.mock_c_plugins_config[4] # FIXME: ordering issue # assert expected_plugin == plugins assert 1 == mock_get_py_folders.call_count assert 1 == mock_get_py_plugin_config.call_count assert 1 == mock_get_c_folders.call_count assert 1 == mock_get_c_plugin_config.call_count
def test_get_plugins_installed_type_south(self, mocker): @asyncio.coroutine def mock_folders(): yield TestPluginDiscovery.mock_south_folders @asyncio.coroutine def mock_c_folders(): yield TestPluginDiscovery.mock_c_south_folders mock_get_folders = mocker.patch.object(PluginDiscovery, "get_plugin_folders", return_value=next( mock_folders())) mock_get_plugin_config = mocker.patch.object( PluginDiscovery, "get_plugin_config", side_effect=TestPluginDiscovery.mock_plugins_south_config) mock_get_c_folders = mocker.patch.object(utils, "find_c_plugin_libs", return_value=next( mock_c_folders())) mock_get_c_plugin_config = mocker.patch.object( utils, "get_plugin_info", side_effect=TestPluginDiscovery.mock_c_plugins_south_config) plugins = PluginDiscovery.get_plugins_installed("south") expected_plugin = TestPluginDiscovery.mock_plugins_south_config expected_plugin.extend(TestPluginDiscovery.mock_c_plugins_south_config) # FIXME: ordering issue # assert expected_plugin == plugins assert 1 == mock_get_folders.call_count assert 2 == mock_get_plugin_config.call_count assert 1 == mock_get_c_folders.call_count assert 1 == mock_get_c_plugin_config.call_count
async def remove_plugin(request): """ Remove installed plugin from fledge type: installed plugin type name: installed plugin name Example: curl -X DELETE http://localhost:8081/fledge/plugins/south/sinusoid curl -X DELETE http://localhost:8081/fledge/plugins/north/http_north curl -X DELETE http://localhost:8081/fledge/plugins/filter/expression curl -X DELETE http://localhost:8081/fledge/plugins/notificationDelivery/alexa curl -X DELETE http://localhost:8081/fledge/plugins/notificationRule/Average """ plugin_type = request.match_info.get('type', None) name = request.match_info.get('name', None) try: plugin_type = str(plugin_type).lower() if not str(plugin_type).startswith('notification') else plugin_type if plugin_type not in valid_plugin_types: raise ValueError("Invalid plugin type. Please provide valid type: {}".format(valid_plugin_types)) installed_plugin = PluginDiscovery.get_plugins_installed(plugin_type, False) if name not in [plugin['name'] for plugin in installed_plugin]: raise KeyError("Invalid plugin name {} or plugin is not installed".format(name)) if plugin_type in ['notificationDelivery', 'notificationRule']: notification_instances_plugin_used_in = await check_plugin_usage_in_notification_instances(name) if notification_instances_plugin_used_in: raise RuntimeError("{} cannot be removed. This is being used by {} instances". format(name, notification_instances_plugin_used_in)) plugin_type = 'notify' if plugin_type == 'notificationDelivery' else 'rule' else: get_tracked_plugins = await check_plugin_usage(plugin_type, name) if get_tracked_plugins: e = "{} cannot be removed. This is being used by {} instances".\ format(name, get_tracked_plugins[0]['service_list']) _logger.error(e) raise RuntimeError(e) else: _logger.info("No entry found for {name} plugin in asset tracker; or " "{name} plugin may have been added in disabled state & never used".format(name=name)) res, log_path, is_package = purge_plugin(plugin_type, name) if res != 0: e_msg = "Something went wrong. Please check log {}".format(log_path) _logger.error(e_msg) raise RuntimeError(e_msg) else: if is_package: storage_client = connect.get_storage_async() audit_log = AuditLogger(storage_client) audit_detail = {'package_name': "fledge-{}-{}".format(plugin_type, name)} await audit_log.information('PKGRM', audit_detail) except (ValueError, RuntimeError) as ex: raise web.HTTPBadRequest(reason=str(ex)) except KeyError as ex: raise web.HTTPNotFound(reason=str(ex)) except PackageError as e: msg = "Failed to remove package for plugin {}".format(name) raise web.HTTPBadRequest(body=json.dumps({"message": msg, "link": str(e)}), reason=msg) else: _logger.info('{} plugin removed successfully'.format(name)) return web.json_response({'message': '{} plugin removed successfully'.format(name)}, status=200)
async def update_plugin(request: web.Request) -> web.Response: """ update plugin :Example: curl -X PUT http://localhost:8081/fledge/plugins/south/sinusoid/update curl -X PUT http://localhost:8081/fledge/plugins/north/http_north/update """ _type = request.match_info.get('type', None) name = request.match_info.get('name', None) try: # TODO: FOGL-3063, FOGL-3064 _type = _type.lower() if _type not in ['north', 'south']: raise ValueError("Invalid plugin type. Must be 'north' or 'south'") # Check requested plugin name is installed or not installed_plugins = PluginDiscovery.get_plugins_installed(_type, False) installed_plugin_name = [ p_name["name"] for p_name in installed_plugins ] if name not in installed_plugin_name: raise KeyError( "{} plugin is not yet installed. So update is not possible.". format(name)) # Tracked plugins from asset tracker tracked_plugins = await _get_plugin_and_sch_name_from_asset_tracker( _type) sch_list = [] for p in tracked_plugins: if name == p['plugin']: sch_info = await _get_sch_id_and_enabled_by_name(p['service']) if sch_info[0]['enabled'] == 't': status, reason = await server.Server.scheduler.disable_schedule( uuid.UUID(sch_info[0]['id'])) if status: _logger.warning( "{} {} instance is disabled as {} plugin is updating.." .format(p['service'], _type, p['plugin'])) sch_list.append(sch_info[0]['id']) # Plugin update is running as a background task loop = request.loop request._type = _type request._name = name request._sch_list = sch_list loop.call_later(1, do_update, request) except KeyError as ex: raise web.HTTPNotFound(reason=ex) except ValueError as ex: raise web.HTTPBadRequest(reason=ex) except Exception as ex: raise web.HTTPInternalServerError(reason=ex) return web.json_response({ "message": "{} plugin update in process. Wait for few minutes to complete.". format(name) })
def test_get_plugin_folders(self, mocker): @asyncio.coroutine def mock_folders(): listdir = copy.deepcopy(TestPluginDiscovery.mock_north_folders) listdir.extend(["__init__", "empty", "common"]) yield listdir mocker.patch.object(os, "listdir", return_value=next(mock_folders())) mocker.patch.object(os.path, "isdir", return_value=True) plugin_folders = PluginDiscovery.get_plugin_folders("north") actual_plugin_folders = [] for dir_name in plugin_folders: actual_plugin_folders.append(dir_name.split('/')[-1]) assert TestPluginDiscovery.mock_north_folders == actual_plugin_folders
def test_fetch_plugins_installed(self, mocker): @asyncio.coroutine def mock_folders(): yield TestPluginDiscovery.mock_north_folders mock_get_folders = mocker.patch.object(PluginDiscovery, "get_plugin_folders", return_value=next(mock_folders())) mock_get_plugin_config = mocker.patch.object(PluginDiscovery, "get_plugin_config", side_effect=TestPluginDiscovery.mock_plugins_north_config) plugins = PluginDiscovery.fetch_plugins_installed("north", False) # FIXME: below line is failing when in suite # assert TestPluginDiscovery.mock_plugins_north_config == plugins assert 1 == mock_get_folders.call_count assert 2 == mock_get_plugin_config.call_count
def test_bad_get_plugin_config(self): mock_plugin_info = { 'name': "HTTP", 'version': "1.0.0", 'type': "north", 'interface': "1.0.0", 'config': { 'plugin': { 'description': "HTTP north plugin", 'type': 'string', 'default': 'http-north' } } } with patch.object(_logger, "warning") as patch_log_warn: with patch.object(common, 'load_and_fetch_python_plugin_info', side_effect=[mock_plugin_info]): actual = PluginDiscovery.get_plugin_config("http-north", "south", False) assert actual is None patch_log_warn.assert_called_once_with('Plugin http-north is discarded due to invalid type')
async def get_plugins_installed(request): """ get list of installed plugins :Example: curl -X GET http://localhost:8081/fledge/plugins/installed curl -X GET http://localhost:8081/fledge/plugins/installed?config=true curl -X GET http://localhost:8081/fledge/plugins/installed?type=north|south|filter|notificationDelivery|notificationRule curl -X 'GET http://localhost:8081/fledge/plugins/installed?type=north&config=true' """ plugin_type = None is_config = False if 'type' in request.query and request.query['type'] != '': plugin_type = request.query['type'].lower( ) if request.query['type'] not in [ 'notificationDelivery', 'notificationRule' ] else request.query['type'] if plugin_type is not None and plugin_type not in [ 'north', 'south', 'filter', 'notificationDelivery', 'notificationRule' ]: raise web.HTTPBadRequest( reason= "Invalid plugin type. Must be 'north' or 'south' or 'filter' or 'notificationDelivery' or 'notificationRule'." ) if 'config' in request.query: config = request.query['config'] if config not in ['true', 'false', True, False]: raise web.HTTPBadRequest(reason='Only "true", "false", true, false' ' are allowed for value of config.') is_config = True if ( (type(config) is str and config.lower() in ['true']) or ((type(config) is bool and config is True))) else False plugins_list = PluginDiscovery.get_plugins_installed( plugin_type, is_config) return web.json_response({"plugins": plugins_list})
async def update_plugin(request: web.Request) -> web.Response: """ update plugin :Example: curl -sX PUT http://localhost:8081/fledge/plugins/south/sinusoid/update curl -sX PUT http://localhost:8081/fledge/plugins/north/http_north/update curl -sX PUT http://localhost:8081/fledge/plugins/filter/metadata/update curl -sX PUT http://localhost:8081/fledge/plugins/notificationDelivery/asset/update curl -sX PUT http://localhost:8081/fledge/plugins/notificationRule/OutOfBound/update """ _type = request.match_info.get('type', None) name = request.match_info.get('name', None) try: _type = _type.lower( ) if not str(_type).startswith('notification') else _type if _type not in [ 'north', 'south', 'filter', 'notificationDelivery', 'notificationRule' ]: raise ValueError( "Invalid plugin type. Must be 'north', 'south', 'filter', 'notificationDelivery' " "or 'notificationRule'") # Check requested plugin name is installed or not installed_plugins = PluginDiscovery.get_plugins_installed(_type, False) installed_plugin_name = [ p_name["name"] for p_name in installed_plugins ] if name not in installed_plugin_name: raise KeyError( "{} plugin is not yet installed. So update is not possible.". format(name)) sch_list = [] notification_list = [] if _type in ['notificationDelivery', 'notificationRule']: # Check Notification service is enabled or not payload = PayloadBuilder().SELECT( "id", "enabled", "schedule_name").WHERE(['process_name', '=', 'notification_c']).payload() storage_client = connect.get_storage_async() result = await storage_client.query_tbl_with_payload( 'schedules', payload) sch_info = result['rows'] if sch_info and sch_info[0]['enabled'] == 't': # Find notification instances which are used by requested plugin name # If its config item 'enable' is true then update to false config_mgr = ConfigurationManager(storage_client) all_notifications = await config_mgr._read_all_child_category_names( "Notifications") for notification in all_notifications: notification_config = await config_mgr._read_category_val( notification['child']) notification_name = notification_config['name']['value'] channel = notification_config['channel']['value'] rule = notification_config['rule']['value'] is_enabled = True if notification_config['enable'][ 'value'] == 'true' else False if (channel == name and is_enabled) or (rule == name and is_enabled): _logger.warning( "Disabling {} notification instance, as {} {} plugin is updating..." .format(notification_name, name, _type)) await config_mgr.set_category_item_value_entry( notification_name, "enable", "false") notification_list.append(notification_name) _type = "notify" if _type == 'notificationDelivery' else "rule" else: # Tracked plugins from asset tracker tracked_plugins = await _get_plugin_and_sch_name_from_asset_tracker( _type) filters_used_by = [] if _type == 'filter': # In case of filter, for asset_tracker table we are inserting filter category_name in plugin column # instead of filter plugin name by Design # Hence below query is required to get actual plugin name from filters table storage_client = connect.get_storage_async() payload = PayloadBuilder().SELECT("name").WHERE( ['plugin', '=', name]).payload() result = await storage_client.query_tbl_with_payload( 'filters', payload) filters_used_by = [r['name'] for r in result['rows']] for p in tracked_plugins: if (name == p['plugin'] and not _type == 'filter') or ( p['plugin'] in filters_used_by and _type == 'filter'): sch_info = await _get_sch_id_and_enabled_by_name( p['service']) if sch_info[0]['enabled'] == 't': status, reason = await server.Server.scheduler.disable_schedule( uuid.UUID(sch_info[0]['id'])) if status: _logger.warning( "Disabling {} {} instance, as {} plugin is updating..." .format(p['service'], _type, name)) sch_list.append(sch_info[0]['id']) # Plugin update is running as a background task loop = request.loop request._type = _type request._name = name request._sch_list = sch_list request._notification_list = notification_list loop.call_later(1, do_update, request) except KeyError as ex: raise web.HTTPNotFound(reason=str(ex)) except ValueError as ex: raise web.HTTPBadRequest(reason=str(ex)) except Exception as ex: raise web.HTTPInternalServerError(reason=str(ex)) return web.json_response({ "message": "{} plugin update in process. Wait for few minutes to complete.". format(name) })
def _get_installed_plugins(): return PluginDiscovery.get_plugins_installed("south", False)