Exemple #1
0
async def put_notification(request):
    """
    Update an existing notification

    :Example:
             curl -X PUT http://localhost:8081/foglamp/notification/<notification_name> -d '{"description":"Test Notification modified"}'
             curl -X PUT http://localhost:8081/foglamp/notification/<notification_name> -d '{"rule": "threshold", "channel": "email"}'
             curl -X PUT http://localhost:8081/foglamp/notification/<notification_name> -d '{"notification_type": "one shot", "enabled": false}'
             curl -X PUT http://localhost:8081/foglamp/notification/<notification_name> -d '{"enabled": false}'
             curl -X PUT http://localhost:8081/foglamp/notification/<notification_name> -d '{"description":"Test Notification", "rule": "threshold", "channel": "email", "notification_type": "one shot", "enabled": false, "rule_config": {}, "delivery_config": {}}'
    """
    try:
        notification_service = ServiceRegistry.get(s_type=ServiceRecord.Type.Notification.name)
        _address, _port = notification_service[0]._address, notification_service[0]._port
    except service_registry_exceptions.DoesNotExist:
        raise web.HTTPNotFound(reason="No Notification service available.")

    try:
        notif = request.match_info.get('notification_name', None)
        if notif is None:
            raise ValueError("Notification name is required for updation.")

        # TODO: Stop notification before update

        data = await request.json()
        if not isinstance(data, dict):
            raise ValueError('Data payload must be a valid JSON')

        description = data.get('description', None)
        rule = data.get('rule', None)
        channel = data.get('channel', None)
        notification_type = data.get('notification_type', None)
        enabled = data.get('enabled', None)
        rule_config = data.get('rule_config', {})
        delivery_config = data.get('delivery_config', {})

        if utils.check_reserved(notif) is False:
            raise ValueError('Invalid notification name parameter.')
        if rule is not None and utils.check_reserved(rule) is False:
            raise ValueError('Invalid rule property in payload.')
        if channel is not None and utils.check_reserved(channel) is False:
            raise ValueError('Invalid channel property in payload.')
        if notification_type is not None and notification_type not in NOTIFICATION_TYPE:
            raise ValueError('Invalid notification_type property in payload.')

        if enabled is not None:
            if enabled not in ['true', 'false', True, False]:
                raise ValueError('Only "true", "false", true, false are allowed for value of enabled.')
        is_enabled = "true" if ((type(enabled) is str and enabled.lower() in ['true']) or (
            (type(enabled) is bool and enabled is True))) else "false"

        storage = connect.get_storage_async()
        config_mgr = ConfigurationManager(storage)

        current_config = await config_mgr._read_category_val(notif)
        rule_changed = True if rule is not None and rule != current_config['rule']['value'] else False
        channel_changed = True if channel is not None and channel != current_config['channel']['value'] else False

        try:
            # Get default config for rule and channel plugins
            url = str(request.url)
            url_parts = url.split("/foglamp/notification")
            url = '{}/foglamp/notification/plugin'.format(url_parts[0])
            list_plugins = json.loads(await _hit_get_url(url))

            search_rule = rule if rule_changed else current_config['rule']['value']
            r = list(filter(lambda rules: rules['name'] == search_rule, list_plugins['rules']))
            if len(r) == 0: raise KeyError
            rule_plugin_config = r[0]['config']

            search_channel = channel if channel_changed else current_config['channel']['value']
            c = list(filter(lambda channels: channels['name'] == search_channel, list_plugins['delivery']))
            if len(c) == 0: raise KeyError
            delivery_plugin_config = c[0]['config']
        except KeyError:
            raise ValueError("Invalid rule plugin:{} and/or delivery plugin:{} supplied.".format(rule, channel))

        # Verify if rule_config contains valid keys
        if rule_config != {}:
            for k, v in rule_config.items():
                if k not in rule_plugin_config:
                    raise ValueError("Invalid key:{} in rule plugin:{}".format(k, rule_plugin_config))

        # Verify if delivery_config contains valid keys
        if delivery_config != {}:
            for k, v in delivery_config.items():
                if k not in delivery_plugin_config:
                    raise ValueError(
                        "Invalid key:{} in delivery plugin:{}".format(k, delivery_plugin_config))

        if rule_changed:  # A new rule has been supplied
            category_desc = rule_plugin_config['plugin']['description']
            category_name = "rule{}".format(notif)
            await config_mgr.create_category(category_name=category_name,
                                             category_description=category_desc,
                                             category_value=rule_plugin_config,
                                             keep_original_items=False)
        if channel_changed:  # A new delivery has been supplied
            category_desc = delivery_plugin_config['plugin']['description']
            category_name = "delivery{}".format(notif)
            await config_mgr.create_category(category_name=category_name,
                                             category_description=category_desc,
                                             category_value=delivery_plugin_config,
                                             keep_original_items=False)
        notification_config = {}
        if description is not None:
            notification_config.update({"description": description})
        if rule is not None:
            notification_config.update({"rule": rule})
        if channel is not None:
            notification_config.update({"channel": channel})
        if notification_type is not None:
            notification_config.update({"notification_type": notification_type})
        if enabled is not None:
            notification_config.update({"enable": is_enabled})
        await _update_configurations(config_mgr, notif, notification_config, rule_config, delivery_config)
    except ValueError as ex:
        raise web.HTTPBadRequest(reason=str(ex))
    except Exception as e:
        raise web.HTTPInternalServerError(reason=str(e))
    else:
        # TODO: Start notification after update
        return web.json_response({'result': "Notification {} updated successfully".format(notif)})
Exemple #2
0
async def post_notification(request):
    """
    Create a new notification to run a specific plugin

    :Example:
             curl -X POST http://localhost:8081/foglamp/notification -d '{"name": "Test Notification", "description":"Test Notification", "rule": "threshold", "channel": "email", "notification_type": "one shot", "enabled": false}'
             curl -X POST http://localhost:8081/foglamp/notification -d '{"name": "Test Notification", "description":"Test Notification", "rule": "threshold", "channel": "email", "notification_type": "one shot", "enabled": false, "rule_config": {}, "delivery_config": {}}'
    """
    try:
        notification_service = ServiceRegistry.get(s_type=ServiceRecord.Type.Notification.name)
        _address, _port = notification_service[0]._address, notification_service[0]._port
    except service_registry_exceptions.DoesNotExist:
        raise web.HTTPNotFound(reason="No Notification service available.")

    try:
        data = await request.json()
        if not isinstance(data, dict):
            raise ValueError('Data payload must be a valid JSON')

        name = data.get('name', None)
        description = data.get('description', None)
        rule = data.get('rule', None)
        channel = data.get('channel', None)
        notification_type = data.get('notification_type', None)
        enabled = data.get('enabled', None)
        rule_config = data.get('rule_config', {})
        delivery_config = data.get('delivery_config', {})

        if name is None or name.strip() == "":
            raise ValueError('Missing name property in payload.')
        if description is None:
            raise ValueError('Missing description property in payload.')
        if rule is None:
            raise ValueError('Missing rule property in payload.')
        if channel is None:
            raise ValueError('Missing channel property in payload.')
        if notification_type is None:
            raise ValueError('Missing notification_type property in payload.')

        if utils.check_reserved(name) is False:
            raise ValueError('Invalid name property in payload.')
        if utils.check_reserved(rule) is False:
            raise ValueError('Invalid rule property in payload.')
        if utils.check_reserved(channel) is False:
            raise ValueError('Invalid channel property in payload.')
        if notification_type not in NOTIFICATION_TYPE:
            raise ValueError('Invalid notification_type property in payload.')

        if enabled is not None:
            if enabled not in ['true', 'false', True, False]:
                raise ValueError('Only "true", "false", true, false are allowed for value of enabled.')
        is_enabled = "true" if ((type(enabled) is str and enabled.lower() in ['true']) or (
            (type(enabled) is bool and enabled is True))) else "false"

        try:
            # Get default config for rule and channel plugins
            url = '{}/plugin'.format(request.url)
            list_plugins = json.loads(await _hit_get_url(url))
            r = list(filter(lambda rules: rules['name'] == rule, list_plugins['rules']))
            c = list(filter(lambda channels: channels['name'] == channel, list_plugins['delivery']))
            if len(r) == 0 or len(c) == 0: raise KeyError
            rule_plugin_config = r[0]['config']
            delivery_plugin_config = c[0]['config']
        except KeyError:
            raise ValueError("Invalid rule plugin:[{}] and/or delivery plugin:[{}] supplied.".format(rule, channel))

        # Verify if rule_config contains valid keys
        if rule_config != {}:
            for k, v in rule_config.items():
                if k not in rule_plugin_config:
                    raise ValueError("Invalid key:[{}] in rule_config:[{}] supplied for plugin [{}].".format(k, rule_config, rule))

        # Verify if delivery_config contains valid keys
        if delivery_config != {}:
            for k, v in delivery_config.items():
                if k not in delivery_plugin_config:
                    raise ValueError(
                        "Invalid key:[{}] in delivery_config:[{}] supplied for plugin [{}].".format(k, delivery_config, channel))

        # First create templates for notification and rule, channel plugins
        post_url = 'http://{}:{}/notification/{}'.format(_address, _port, urllib.parse.quote(name))
        await _hit_post_url(post_url)  # Create Notification template
        post_url = 'http://{}:{}/notification/{}/rule/{}'.format(_address, _port, urllib.parse.quote(name),
                                                                         urllib.parse.quote(rule))
        await _hit_post_url(post_url)  # Create Notification rule template
        post_url = 'http://{}:{}/notification/{}/delivery/{}'.format(_address, _port, urllib.parse.quote(name),
                                                                             urllib.parse.quote(channel))
        await _hit_post_url(post_url)  # Create Notification delivery template

        # Create configurations
        storage = connect.get_storage_async()
        config_mgr = ConfigurationManager(storage)
        notification_config = {
            "rule": rule,
            "channel": channel,
            "notification_type": notification_type,
            "enable":is_enabled,
        }
        await _update_configurations(config_mgr, name, notification_config, rule_config, delivery_config)

        audit = AuditLogger(storage)
        await audit.information('NTFAD', {"name": name})
    except ValueError as ex:
        raise web.HTTPBadRequest(reason=str(ex))
    except Exception as e:
        raise web.HTTPInternalServerError(reason=str(e))
    else:
        return web.json_response({'result': "Notification {} created successfully".format(name)})
Exemple #3
0
async def add_service(request):
    """
    Create a new service to run a specific plugin

    :Example:
             curl -X POST http://localhost:8081/foglamp/service -d '{"name": "DHT 11", "plugin": "dht11", "type": "south", "enabled": true}'
             curl -sX POST http://localhost:8081/foglamp/service -d '{"name": "Sine", "plugin": "sinusoid", "type": "south", "enabled": true, "config": {"dataPointsPerSec": {"value": "10"}}}' | jq
             curl -X POST http://localhost:8081/foglamp/service -d '{"name": "NotificationServer", "type": "notification", "enabled": true}' | jq
    """

    try:
        data = await request.json()
        if not isinstance(data, dict):
            raise ValueError('Data payload must be a valid JSON')

        name = data.get('name', None)
        plugin = data.get('plugin', None)
        service_type = data.get('type', None)
        enabled = data.get('enabled', None)
        config = data.get('config', None)

        if name is None:
            raise web.HTTPBadRequest(
                reason='Missing name property in payload.')
        if utils.check_reserved(name) is False:
            raise web.HTTPBadRequest(
                reason='Invalid name property in payload.')
        if utils.check_foglamp_reserved(name) is False:
            raise web.HTTPBadRequest(
                reason=
                "'{}' is reserved for FogLAMP and can not be used as service name!"
                .format(name))
        if service_type is None:
            raise web.HTTPBadRequest(
                reason='Missing type property in payload.')

        service_type = str(service_type).lower()
        if service_type == 'north':
            raise web.HTTPNotAcceptable(
                reason='north type is not supported for the time being.')
        if service_type not in ['south', 'notification']:
            raise web.HTTPBadRequest(
                reason='Only south and notification type are supported.')
        if plugin is None and service_type == 'south':
            raise web.HTTPBadRequest(
                reason='Missing plugin property for type south in payload.')
        if plugin and utils.check_reserved(plugin) is False:
            raise web.HTTPBadRequest(
                reason='Invalid plugin property in payload.')

        if enabled is not None:
            if enabled not in ['true', 'false', True, False]:
                raise web.HTTPBadRequest(
                    reason='Only "true", "false", true, false'
                    ' are allowed for value of enabled.')
        is_enabled = True if (
            (type(enabled) is str and enabled.lower() in ['true']) or
            ((type(enabled) is bool and enabled is True))) else False

        # Check if a valid plugin has been provided
        plugin_module_path, plugin_config, process_name, script = "", {}, "", ""
        if service_type == 'south':
            # "plugin_module_path" is fixed by design. It is MANDATORY to keep the plugin in the exactly similar named
            # folder, within the plugin_module_path.
            # if multiple plugin with same name are found, then python plugin import will be tried first
            plugin_module_path = "foglamp.plugins.south"
            try:
                plugin_info = load_python_plugin(plugin_module_path, plugin,
                                                 service_type)
                plugin_config = plugin_info['config']
                if not plugin_config:
                    _logger.exception("Plugin %s import problem from path %s",
                                      plugin, plugin_module_path)
                    raise web.HTTPNotFound(
                        reason='Plugin "{}" import problem from path "{}".'.
                        format(plugin, plugin_module_path))
                process_name = 'south_c'
                script = '["services/south_c"]'
            except ImportError as ex:
                # Checking for C-type plugins
                plugin_config = load_c_plugin(plugin, service_type)
                if not plugin_config:
                    _logger.exception(
                        "Plugin %s import problem from path %s. %s", plugin,
                        plugin_module_path, str(ex))
                    raise web.HTTPNotFound(
                        reason='Plugin "{}" import problem from path "{}".'.
                        format(plugin, plugin_module_path))

                process_name = 'south_c'
                script = '["services/south_c"]'
            except TypeError as ex:
                _logger.exception(str(ex))
                raise web.HTTPBadRequest(reason=str(ex))
            except Exception as ex:
                _logger.exception("Failed to fetch plugin configuration. %s",
                                  str(ex))
                raise web.HTTPInternalServerError(
                    reason='Failed to fetch plugin configuration')
        elif service_type == 'notification':
            process_name = 'notification_c'
            script = '["services/notification_c"]'

        storage = connect.get_storage_async()
        config_mgr = ConfigurationManager(storage)

        # Check  whether category name already exists
        category_info = await config_mgr.get_category_all_items(
            category_name=name)
        if category_info is not None:
            raise web.HTTPBadRequest(
                reason="The '{}' category already exists".format(name))

        # Check that the schedule name is not already registered
        count = await check_schedules(storage, name)
        if count != 0:
            raise web.HTTPBadRequest(
                reason='A service with this name already exists.')

        # Check that the process name is not already registered
        count = await check_scheduled_processes(storage, process_name)
        if count == 0:
            # Now first create the scheduled process entry for the new service
            payload = PayloadBuilder().INSERT(name=process_name,
                                              script=script).payload()
            try:
                res = await storage.insert_into_tbl("scheduled_processes",
                                                    payload)
            except StorageServerError as ex:
                _logger.exception("Failed to create scheduled process. %s",
                                  ex.error)
                raise web.HTTPInternalServerError(
                    reason='Failed to create service.')
            except Exception as ex:
                _logger.exception("Failed to create scheduled process. %s",
                                  str(ex))
                raise web.HTTPInternalServerError(
                    reason='Failed to create service.')

        # check that notification service is not already registered, right now notification service LIMIT to 1
        if service_type == 'notification':
            res = await check_notification_schedule(storage)
            for ps in res['rows']:
                if 'notification_c' in ps['process_name']:
                    raise web.HTTPBadRequest(
                        reason='A Notification service schedule already exists.'
                    )
        elif service_type == 'south':
            try:
                # Create a configuration category from the configuration defined in the plugin
                category_desc = plugin_config['plugin']['description']
                await config_mgr.create_category(
                    category_name=name,
                    category_description=category_desc,
                    category_value=plugin_config,
                    keep_original_items=True)
                # Create the parent category for all South services
                await config_mgr.create_category("South", {},
                                                 "South microservices", True)
                await config_mgr.create_child_category("South", [name])

                # If config is in POST data, then update the value for each config item
                if config is not None:
                    if not isinstance(config, dict):
                        raise ValueError('Config must be a JSON object')
                    for k, v in config.items():
                        await config_mgr.set_category_item_value_entry(
                            name, k, v['value'])

            except Exception as ex:
                await config_mgr.delete_category_and_children_recursively(name)
                _logger.exception("Failed to create plugin configuration. %s",
                                  str(ex))
                raise web.HTTPInternalServerError(
                    reason='Failed to create plugin configuration.')

        # If all successful then lastly add a schedule to run the new service at startup
        try:
            schedule = StartUpSchedule()
            schedule.name = name
            schedule.process_name = process_name
            schedule.repeat = datetime.timedelta(0)
            schedule.exclusive = True
            #  if "enabled" is supplied, it gets activated in save_schedule() via is_enabled flag
            schedule.enabled = False

            # Save schedule
            await server.Server.scheduler.save_schedule(schedule, is_enabled)
            schedule = await server.Server.scheduler.get_schedule_by_name(name)
        except StorageServerError as ex:
            await config_mgr.delete_category_and_children_recursively(name)
            _logger.exception("Failed to create schedule. %s", ex.error)
            raise web.HTTPInternalServerError(
                reason='Failed to create service.')
        except Exception as ex:
            await config_mgr.delete_category_and_children_recursively(name)
            _logger.exception("Failed to create service. %s", str(ex))
            raise web.HTTPInternalServerError(
                reason='Failed to create service.')

    except ValueError as e:
        raise web.HTTPBadRequest(reason=str(e))
    else:
        return web.json_response({
            'name': name,
            'id': str(schedule.schedule_id)
        })
Exemple #4
0
async def add_task(request):
    """ Create a new task to run a specific plugin

    :Example:
     curl -X POST http://localhost:8081/foglamp/scheduled/task -d
     '{
        "name": "North Readings to PI",
        "plugin": "pi_server",
        "type": "north",
        "schedule_type": 3,
        "schedule_day": 0,
        "schedule_time": 0,
        "schedule_repeat": 30,
        "schedule_enabled": true
     }'

     curl -sX POST http://localhost:8081/foglamp/scheduled/task -d
     '{"name": "PI-2",
     "plugin": "pi_server",
     "type": "north",
     "schedule_type": 3,
     "schedule_day": 0,
     "schedule_time": 0,
     "schedule_repeat": 30,
     "schedule_enabled": true,
     "config": {
        "producerToken": {"value": "uid=180905062754237&sig=kx5l+"},
        "URL": {"value": "https://10.2.5.22:5460/ingress/messages"}}}'
    """

    try:
        data = await request.json()
        if not isinstance(data, dict):
            raise ValueError('Data payload must be a valid JSON')

        name = data.get('name', None)
        plugin = data.get('plugin', None)
        task_type = data.get('type', None)

        schedule_type = data.get('schedule_type', None)
        schedule_day = data.get('schedule_day', None)
        schedule_time = data.get('schedule_time', None)
        schedule_repeat = data.get('schedule_repeat', None)
        enabled = data.get('schedule_enabled', None)
        config = data.get('config', None)

        if name is None:
            raise web.HTTPBadRequest(
                reason='Missing name property in payload.')
        if plugin is None:
            raise web.HTTPBadRequest(
                reason='Missing plugin property in payload.')
        if task_type is None:
            raise web.HTTPBadRequest(
                reason='Missing type property in payload.')
        if utils.check_reserved(name) is False:
            raise web.HTTPBadRequest(
                reason='Invalid name property in payload.')
        if utils.check_reserved(plugin) is False:
            raise web.HTTPBadRequest(
                reason='Invalid plugin property in payload.')
        if task_type not in ['north']:
            raise web.HTTPBadRequest(reason='Only north type is supported.')

        if schedule_type is None:
            raise web.HTTPBadRequest(reason='schedule_type is mandatory')
        if not isinstance(schedule_type, int) and not schedule_type.isdigit():
            raise web.HTTPBadRequest(
                reason='Error in schedule_type: {}'.format(schedule_type))
        if int(schedule_type) not in list(Schedule.Type):
            raise web.HTTPBadRequest(
                reason='schedule_type error: {}'.format(schedule_type))
        if int(schedule_type) == Schedule.Type.STARTUP:
            raise web.HTTPBadRequest(
                reason='schedule_type cannot be STARTUP: {}'.format(
                    schedule_type))

        schedule_type = int(schedule_type)

        if schedule_day is not None:
            if isinstance(schedule_day, float) or (
                    isinstance(schedule_day, str) and
                (schedule_day.strip() != "" and not schedule_day.isdigit())):
                raise web.HTTPBadRequest(
                    reason='Error in schedule_day: {}'.format(schedule_day))
        else:
            schedule_day = int(
                schedule_day) if schedule_day is not None else None

        if schedule_time is not None and (not isinstance(schedule_time, int)
                                          and not schedule_time.isdigit()):
            raise web.HTTPBadRequest(
                reason='Error in schedule_time: {}'.format(schedule_time))
        else:
            schedule_time = int(
                schedule_time) if schedule_time is not None else None

        if schedule_repeat is not None and (not isinstance(
                schedule_repeat, int) and not schedule_repeat.isdigit()):
            raise web.HTTPBadRequest(
                reason='Error in schedule_repeat: {}'.format(schedule_repeat))
        else:
            schedule_repeat = int(
                schedule_repeat) if schedule_repeat is not None else None

        if schedule_type == Schedule.Type.TIMED:
            if not schedule_time:
                raise web.HTTPBadRequest(
                    reason=
                    'schedule_time cannot be empty/None for TIMED schedule.')
            if schedule_day is not None and (schedule_day < 1
                                             or schedule_day > 7):
                raise web.HTTPBadRequest(
                    reason=
                    'schedule_day {} must either be None or must be an integer, 1(Monday) '
                    'to 7(Sunday).'.format(schedule_day))
            if schedule_time < 0 or schedule_time > 86399:
                raise web.HTTPBadRequest(
                    reason=
                    'schedule_time {} must be an integer and in range 0-86399.'
                    .format(schedule_time))

        if schedule_type == Schedule.Type.INTERVAL:
            if schedule_repeat is None:
                raise web.HTTPBadRequest(
                    reason=
                    'schedule_repeat {} is required for INTERVAL schedule_type.'
                    .format(schedule_repeat))
            elif not isinstance(schedule_repeat, int):
                raise web.HTTPBadRequest(
                    reason='schedule_repeat {} must be an integer.'.format(
                        schedule_repeat))

        if enabled is not None:
            if enabled not in ['true', 'false', True, False]:
                raise web.HTTPBadRequest(
                    reason=
                    'Only "true", "false", true, false are allowed for value of enabled.'
                )
        is_enabled = True if (
            (type(enabled) is str and enabled.lower() in ['true']) or
            ((type(enabled) is bool and enabled is True))) else False

        # Check if a valid plugin has been provided
        try:
            # "plugin_module_path" is fixed by design. It is MANDATORY to keep the plugin in the exactly similar named
            # folder, within the plugin_module_path.
            # if multiple plugin with same name are found, then python plugin import will be tried first
            plugin_module_path = "foglamp.plugins.{}".format(task_type)
            import_file_name = "{path}.{dir}.{file}".format(
                path=plugin_module_path, dir=plugin, file=plugin)
            _plugin = __import__(import_file_name, fromlist=[''])

            script = '["tasks/north"]'
            # Fetch configuration from the configuration defined in the plugin
            plugin_info = _plugin.plugin_info()
            if plugin_info['type'] != task_type:
                msg = "Plugin of {} type is not supported".format(
                    plugin_info['type'])
                _logger.exception(msg)
                return web.HTTPBadRequest(reason=msg)
            plugin_config = plugin_info['config']
            process_name = 'north'
        except ImportError as ex:
            # Checking for C-type plugins
            script = '["tasks/north_c"]'
            plugin_info = apiutils.get_plugin_info(plugin, dir=task_type)
            if plugin_info['type'] != task_type:
                msg = "Plugin of {} type is not supported".format(
                    plugin_info['type'])
                _logger.exception(msg)
                return web.HTTPBadRequest(reason=msg)
            plugin_config = plugin_info['config']
            process_name = 'north_c'
            if not plugin_config:
                _logger.exception("Plugin %s import problem from path %s. %s",
                                  plugin, plugin_module_path, str(ex))
                raise web.HTTPNotFound(
                    reason='Plugin "{}" import problem from path "{}"'.format(
                        plugin, plugin_module_path))
        except Exception as ex:
            _logger.exception("Failed to fetch plugin configuration. %s",
                              str(ex))
            raise web.HTTPInternalServerError(
                reason='Failed to fetch plugin configuration.')

        storage = connect.get_storage_async()
        config_mgr = ConfigurationManager(storage)

        # Check whether category name already exists
        category_info = await config_mgr.get_category_all_items(
            category_name=name)
        if category_info is not None:
            raise web.HTTPBadRequest(
                reason="The '{}' category already exists".format(name))

        # Check that the schedule name is not already registered
        count = await check_schedules(storage, name)
        if count != 0:
            raise web.HTTPBadRequest(
                reason='A north instance with this name already exists')

        # Check that the process name is not already registered
        count = await check_scheduled_processes(storage, process_name)
        if count == 0:  # Create the scheduled process entry for the new task
            payload = PayloadBuilder().INSERT(name=process_name,
                                              script=script).payload()
            try:
                res = await storage.insert_into_tbl("scheduled_processes",
                                                    payload)
            except StorageServerError as ex:
                _logger.exception("Failed to create scheduled process. %s",
                                  ex.error)
                raise web.HTTPInternalServerError(
                    reason='Failed to create north instance.')
            except Exception as ex:
                _logger.exception("Failed to create scheduled process. %s", ex)
                raise web.HTTPInternalServerError(
                    reason='Failed to create north instance.')

        # If successful then create a configuration entry from plugin configuration
        try:
            # Create a configuration category from the configuration defined in the plugin
            category_desc = plugin_config['plugin']['description']
            await config_mgr.create_category(
                category_name=name,
                category_description=category_desc,
                category_value=plugin_config,
                keep_original_items=True)
            # Create the parent category for all North tasks
            await config_mgr.create_category("North", {}, 'North tasks', True)
            await config_mgr.create_child_category("North", [name])

            # If config is in POST data, then update the value for each config item
            if config is not None:
                if not isinstance(config, dict):
                    raise ValueError('Config must be a JSON object')
                for k, v in config.items():
                    await config_mgr.set_category_item_value_entry(
                        name, k, v['value'])
        except Exception as ex:
            await config_mgr.delete_category_and_children_recursively(name)
            _logger.exception("Failed to create plugin configuration. %s",
                              str(ex))
            raise web.HTTPInternalServerError(
                reason='Failed to create plugin configuration.')

        # If all successful then lastly add a schedule to run the new task at startup
        try:
            schedule = TimedSchedule() if schedule_type == Schedule.Type.TIMED else \
                       IntervalSchedule() if schedule_type == Schedule.Type.INTERVAL else \
                       ManualSchedule()
            schedule.name = name
            schedule.process_name = process_name
            schedule.day = schedule_day
            m, s = divmod(schedule_time if schedule_time is not None else 0,
                          60)
            h, m = divmod(m, 60)
            schedule.time = datetime.time().replace(hour=h, minute=m, second=s)
            schedule.repeat = datetime.timedelta(
                seconds=schedule_repeat if schedule_repeat is not None else 0)
            schedule.exclusive = True
            schedule.enabled = False  # if "enabled" is supplied, it gets activated in save_schedule() via is_enabled flag

            # Save schedule
            await server.Server.scheduler.save_schedule(schedule, is_enabled)
            schedule = await server.Server.scheduler.get_schedule_by_name(name)
        except StorageServerError as ex:
            await config_mgr.delete_category_and_children_recursively(name)
            _logger.exception("Failed to create schedule. %s", ex.error)
            raise web.HTTPInternalServerError(
                reason='Failed to create north instance.')
        except Exception as ex:
            await config_mgr.delete_category_and_children_recursively(name)
            _logger.exception("Failed to create schedule. %s", str(ex))
            raise web.HTTPInternalServerError(
                reason='Failed to create north instance.')

    except ValueError as e:
        raise web.HTTPBadRequest(reason=str(e))
    else:
        return web.json_response({
            'name': name,
            'id': str(schedule.schedule_id)
        })
 def test_check_reserved(self, test_string, expected):
     actual = common_utils.check_reserved(test_string)
     assert expected == actual