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)})
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)})
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) })
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