示例#1
0
async def read_multipart(reader: MultipartReader) -> Tuple[dict, bytearray]:
    part1 = await reader.next()
    if part1 is None or part1.headers[hdrs.CONTENT_TYPE] != 'application/json':
        raise web.HTTPNotAcceptable(reason='expected metadata')
    metadata = await part1.json()
    part2 = await reader.next()
    if part2 is None or part2.headers[hdrs.CONTENT_TYPE] != 'image/jpeg':
        raise web.HTTPNotAcceptable(reason='expected image data')
    jpeg = await part2.read(decode=False)
    return (metadata, jpeg)
示例#2
0
async def handler(request):
    body = await request.json()
    state_str = body.get('state')
    state = request.app.openkart.State.__members__.get(state_str)
    if state is None:
        raise web.HTTPNotAcceptable(text=f'unknown state {state_str}')

    try:
        await request.app.openkart.set_state(state)
    except AttributeError:
        raise web.HTTPNotAcceptable(text='server not configured for this')

    return await get_state_handler(request)
async def game(request):

    if request.method == 'GET':
        async with request.app['db'].acquire() as conn:
            # GET all games and display gamename and status
            cursor = await conn.execute(db.game.select())
            records = await cursor.fetchall()
            games = str([(i[0], i[1]) for i in records])
            print('records ', records)
            return web.Response(text='Games are: ' + games)

    elif request.method == 'POST':
        try:
            # inserts a new game into the game table
            # throws IntegrityError if game already exists with this name
            data = await request.post()
            game_name = data['name']

            async with request.app['db'].acquire() as conn:
                await conn.execute(db.game.insert().values(name=game_name,
                                                           status='NEW'))

        except (KeyError, TypeError, ValueError) as e:
            raise web.HTTPBadRequest(
                text='You have not specified a game name') from e
        except (IntegrityError):
            raise web.HTTPNotAcceptable(text="a game called " + game_name +
                                        " already exists.")

        return web.Response(text='New Game: ' + game_name +
                            ' has been created')

    return web.HTTPBadRequest(text='bad request type')
示例#4
0
async def purge_all(request):
    try:
        json_body = await request.json()
    except JSONDecodeError:
        raise web.HTTPNotAcceptable(reason="Invalid json")

    return await base.purge_all(json_body)
示例#5
0
async def post_index(request):
    if request.headers['Content-Type'] != 'application/json':
        raise web.HTTPNotAcceptable()

    data = await parser.parse(SizeChart, request)

    return web.json_response({})
示例#6
0
async def put_chart(request):
    if request.headers['Content-Type'] != 'application/json':
        raise web.HTTPNotAcceptable()
    chart_id = request.match_info['id']
    data = await parser.parse(SizeChart, request)

    return web.json_response({})
示例#7
0
def best_content_type(request: web.Request) -> str:
    # language=rst
    """The best matching Content-Type.

    Todo:
        Generalize. (Now, we only negotiate hal+json and friends.)

    Raises:
        aiohttp.web.HTTPNotAcceptable: if none of the available content types
            are acceptable by the client.  See :ref:`aiohttp web exceptions
            <aiohttp-web-exceptions>`.

    """
    if 'ACCEPT' not in request.headers:
        return "application/hal+json; charset=UTF-8"
    accept = ','.join(request.headers.getall('ACCEPT', ['*/*']))
    mime_types = [part.split(';', 2)[0].strip() for part in accept.split(',')]
    if not _HAL_JSON_COMPATIBLE.isdisjoint(mime_types):
        return "application/hal+json; charset=UTF-8"
    elif "application/json" in mime_types:
        return "application/json; charset=UTF-8"
    else:
        body = ",".join(_AVAILABLE_CONTENT_TYPES).encode('ascii')
        raise web.HTTPNotAcceptable(
            body=body, content_type='text/plain; charset="US-ASCII"')
示例#8
0
    def parse_accept(self, request, content_types):
        """Parse the incoming Accept: headers for content types matching the
        available handlers

        Currently this method does not care about the quality Accept parameter
        (e.g. 'text/plain;q=0.5')

        :param request: the incoming HTTP request
        :type request: aiohttp.Request
        :param content_types: list of content types that can be produced
        :type content_types: list(string)
        :returns: The first matching item, or None if the incoming request has
            no Accept headers.
        :rtype: string
        :raises aiohttp.web.HTTPNotAcceptable: if incoming Accept headers can
            not be matched against any of the available produced types.
        """

        client_acceptables = [
            accept.split(';')[0] for accept in ','.join(
                request.headers.getall('ACCEPT', [])).split(',')
        ]

        self.log.info('acceptables: %r', client_acceptables)
        if len(client_acceptables) == 0 or (len(client_acceptables) == 1 and
                                            len(client_acceptables[0]) == 0):
            return None
        for accept in client_acceptables:
            for content_type in content_types:
                if fnmatch.fnmatch(content_type, accept):
                    return content_type
        raise web.HTTPNotAcceptable()
示例#9
0
def abort(code):
    if code == 400: return web.HTTPBadRequest()
    elif code == 401: return web.HTTPUnauthorized()
    elif code == 402: return web.HTTPPaymentRequired()
    elif code == 403: return web.HTTPForbidden()
    elif code == 404: return web.HTTPNotFound()
    elif code == 405: return web.HTTPMethodNotAllowed()
    elif code == 406: return web.HTTPNotAcceptable()
    elif code == 407: return web.HTTPProxyAuthenticationRequired()
    elif code == 408: return web.HTTPRequestTimeout()
    elif code == 409: return web.HTTPConflict()
    elif code == 410: return web.HTTPGone()
    elif code == 411: return web.HTTPLengthRequired()
    elif code == 412: return web.HTTPPreconditionFailed()
    elif code == 413: return web.HTTPRequestEntityTooLarge()
    elif code == 414: return web.HTTPRequestURITooLong()
    elif code == 415: return web.HTTPUnsupportedMediaType()
    elif code == 416: return web.HTTPRequestRangeNotSatisfiable()
    elif code == 417: return web.HTTPExpectationFailed()
    elif code == 421: return web.HTTPMisdirectedRequest()
    elif code == 422: return web.HTTPUnprocessableEntity()
    elif code == 424: return web.HTTPFailedDependency()
    elif code == 426: return web.HTTPUpgradeRequired()
    elif code == 428: return web.HTTPPreconditionRequired()
    elif code == 429: return web.HTTPTooManyRequests()
    elif code == 431: return web.HTTPRequestHeaderFieldsTooLarge()
    elif code == 451: return web.HTTPUnavailableForLegalReasons()
    else: return web.HTTPBadRequest()
示例#10
0
    async def wrapper(request):
        # First, check that client requests supported output format.
        is_text_output = False
        accepts = ",".join(request.headers.getall("Accept", []))
        # Text is rendered only if explicitly specified.
        if "text/plain" in accepts:
            is_text_output = True
        elif "*/*" not in accepts and "application/json" not in accepts:
            # Client is requesting an unknown format.
            raise web.HTTPNotAcceptable()

        # Execute the decorated view.
        view_result = await func(request)

        # Render the response.
        results = [view_result] if isinstance(view_result,
                                              dict) else view_result
        all_success = all(c["success"] for c in results)
        status_code = 200 if all_success else 503

        if is_text_output:
            # Multiple checks can be rendered as text to be easier
            # to read (eg. in Pingdom "Root cause" UI).
            max_project_length = max([len(c["project"]) for c in results])
            max_name_length = max([len(c["name"]) for c in results])
            text = "\n".join([(check["project"].ljust(max_project_length + 2) +
                               check["name"].ljust(max_name_length + 2) +
                               repr(check["success"])) for check in results])
            # Let's add some details about each failing check at the bottom.
            fields = (
                "url",
                "description",
                "documentation",
                "parameters",
                "data",
                "troubleshooting",
            )
            for check in [c for c in results if not c["success"]]:
                text += "\n" * 2 + "\n{project}  {name}\n".format(**check)

                check = {
                    **check,
                    "parameters": repr(check["parameters"]),
                    "data": json.dumps(check["data"], indent=2),
                }
                text += "\n".join(
                    chain(*[(
                        "  " + field.capitalize() + ":",
                        textwrap.indent(check[field], "    "),
                    ) for field in fields]))

            return web.Response(text=text, status=status_code)

        # Default rendering is JSON.
        return web.json_response(view_result, status=status_code)
示例#11
0
def render_mako(request, data):
    handler = request.match_info.handler
    template_name = handler.__dict__.get('template', None)
    if not template_name:
        raise web.HTTPNotAcceptable(text='text/html not supported.')
    if not isinstance(data, Mapping):
        raise web.HTTPInternalServerError(
            text="context should be mapping, not {}".format(type(data)))
    template = request.app['mako_lookup'].get_template(template_name)
    text = template.render_unicode(**data)
    return web.Response(text=text, content_type=request['selected_media_type'])
示例#12
0
def _validate_and_extract_from(ws_msg):
    try:
        json_data = json.loads(ws_msg)
    except json.JSONDecodeError:
        raise web.HTTPNotAcceptable(reason="Invalid json")

    v = Validator(schemas.WEB_SOCKET_MSG)
    if not v.validate(json_data):
        raise web.HTTPBadRequest(reason=v.errors)

    return json_data['command'], json_data['body']
def _best_content_type(request: web.Request,
                       available_content_types: T.List[str]) -> str:
    # language=rst
    """The best matching content type.

    Returns:
        The best content type to use for the HTTP response, given a certain
        ``request`` and a list of ``available_content_types``.

    Parameters:
        request (aiohttp.web.Request): the current request
        available_content_types: an ordered list of available content types,
            ordered by quality, best quality first.

    Raises:
        web.HTTPNotAcceptable: if none of the available content types are
            acceptable by the client. See :ref:`aiohttp web exceptions
            <aiohttp-web-exceptions>`.

    Example::

        AVAILABLE = [
            'foo/bar',
            'foo/baz; charset="utf-8"'
        ]
        def handler(request):
            bct = best_content_type(request, AVAILABLE)

    """
    if hdrs.ACCEPT not in request.headers:
        return available_content_types[0]
    accept = ','.join(request.headers.getall(hdrs.ACCEPT))
    acceptable_content_types = dict()
    for acceptable in accept.split(','):
        try:
            main, sub = acceptable.split(';', 2)[0].strip().split('/', 2)
        except ValueError:
            raise web.HTTPBadRequest(text="Malformed Accept: header") from None
        if main not in acceptable_content_types:
            acceptable_content_types[main] = set()
        acceptable_content_types[main].add(sub)
    # Try to find a matching 'main/sub' or 'main/*':
    for available in available_content_types:
        main, sub = available.split(';', 2)[0].strip().split('/', 2)
        if main in acceptable_content_types:
            subs = acceptable_content_types[main]
            if sub in subs or '*' in subs:
                return available
    if '*' in acceptable_content_types and '*' in acceptable_content_types['*']:
        return available_content_types[0]
    # Darn, none of our content types are acceptable to the client:
    body = ",".join(available_content_types).encode('ascii')
    raise web.HTTPNotAcceptable(body=body,
                                content_type='text/plain; charset="US-ASCII"')
示例#14
0
    async def handle_request(self, request: web.Request) -> web.Response:
        request_time = int(time() * 1000)
        try:
            # Check if the Content-Type and Accept headers both are presented
            # and contain 'application/json'

            if request.headers["Content-Type"] != "application/json":
                raise web.HTTPUnsupportedMediaType(
                    reason="Invalid Content-Type")
            if request.headers["Accept"] != "application/json":
                raise web.HTTPNotAcceptable(reason="Invalid Accept header")
        except KeyError as exp:
            reason = "{} header is required".format(exc_message(exp))
            raise web.HTTPNotAcceptable(reason=reason)

        try:
            request_data = await request.json()
        except json.JSONDecodeError:
            exp = JsonRpcError(Errors.PARSE_ERROR)
            self.log_request(request, request_time, "invalid", exp)
            return exp.http_response()

        try:
            if isinstance(request_data, list):
                response_data = await self.process_batch_rpc(request_data)
                self.log_request(request, request_time)
                return web.json_response(response_data)
            response_data = await self.process_single_rpc(request_data)
            self.log_request(request, request_time, request_data["method"])
            return web.json_response(response_data)
        except JsonRpcError as exp:
            exp.set_context(request_data)
            self.log_request(request, request_time,
                             request_data.get("method", "unknown/invalid"),
                             exp)
            return exp.http_response()
示例#15
0
文件: http.py 项目: brianmay/robotica
        async def middleware(request: web.Request) -> web.Response:
            """ Middleware handler. """
            if request.method == "GET":
                request.data = request.query_string
            else:
                content_type = request.content_type
                if content_type == "application/json":
                    try:
                        request.data = await request.json()
                    except JSONDecodeError:
                        logger.error("Invalid JSON received")
                        raise web.HTTPBadRequest
                else:
                    logger.error("Unsupported content type '%s'.",
                                 request.content_type)
                    return web.HTTPNotAcceptable()

            for accept in request.headers.getall('ACCEPT', []):
                if accept == "application/json":
                    data_out = await handler(request)
                    return web.json_response(data_out)

            logger.error("Unsupported ACCEPT header '%s'.", accept)
            return web.HTTPNotAcceptable()
示例#16
0
async def delete_user(request):
    """ Delete a user from users table

    :Example:
        curl -H "authorization: <token>" -X DELETE  http://localhost:8081/fledge/admin/{user_id}/delete
    """
    if request.is_auth_optional:
        _logger.warning(FORBIDDEN_MSG)
        raise web.HTTPForbidden

    # TODO: we should not prevent this, when we have at-least 1 admin (super) user
    try:
        user_id = int(request.match_info.get('user_id'))
    except ValueError as ex:
        _logger.warning(str(ex))
        raise web.HTTPBadRequest(reason=str(ex))

    if user_id == 1:
        msg = "Super admin user can not be deleted"
        _logger.warning(msg)
        raise web.HTTPNotAcceptable(reason=msg)

    # Requester should not be able to delete her/himself
    if user_id == request.user["id"]:
        msg = "You can not delete your own account"
        _logger.warning(msg)
        raise web.HTTPBadRequest(reason=msg)

    try:
        result = await User.Objects.delete(user_id)
        if not result['rows_affected']:
            raise User.DoesNotExist

    except ValueError as ex:
        _logger.warning(str(ex))
        raise web.HTTPBadRequest(reason=str(ex))
    except User.DoesNotExist:
        msg = "User with id:<{}> does not exist".format(int(user_id))
        _logger.warning(msg)
        raise web.HTTPNotFound(reason=msg)
    except Exception as exc:
        _logger.exception(str(exc))
        raise web.HTTPInternalServerError(reason=str(exc))

    _logger.info("User with id:<{}> has been deleted successfully.".format(
        int(user_id)))

    return web.json_response({'message': "User has been deleted successfully"})
示例#17
0
    async def delete(self, request):
        """
        Удаление пользователя
        :param request:
        :return:
        """
        db = request.app['db']
        auth = request.auth
        if request.auth.get('group') != 1:
            return web.HTTPUnauthorized()
        if not auth.get('client'):
            return web.HTTPUnauthorized()

        id = request.match_info.get('id', 0)

        user = await one(
            db,
            User.get_by([User.id == id, User.client_id == auth.get('client')]))

        del_user_result = bool(await delete(
            db,
            User.remove([User.id == id,
                         User.client_id == auth.get('client')])))
        del_params_result = bool(await delete(
            db, Param.remove([Param.user_id == user.get('id')])))

        if not del_user_result:
            raise web.HTTPNotAcceptable(
                reason='Can`t delete user. Try again later ',
                body='Can`t delete user. Try again later')

        # статистика
        stat = {
            'user_id': auth.get('id'),
            'code': '17',
            'msg': 'User deleted [' + str(id) + ']'
        }
        await insert(db, Statistic.add(stat))

        # разлогиневаем удаленного
        cache = request.app.get('cache')
        await cache.kill_cache('uid_token_' + str(id))

        return web.json_response(user,
                                 dumps=self.__encode_helper,
                                 headers={'X-Total-Count': '1'})
示例#18
0
    def IDputClasslist(self, data, request):
        """Accept classlist upload.

        Only "manager" can perform this action.

        The classlist should be provided as a ordered list of (str, str)
        pairs where each pair is (student ID, student name).

        Side effects on the server test spec file:
          * If numberToName and/or numberToProduce are -1, values are
            set based on this classlist (spec is permanently altered)/
          * If numberToName < 0 but numberToProduce is too small for the
            result, respond with HTTPNotAcceptable.

        Returns:
            aiohttp.web_response.Response: Success or failure.  Can be:
                200: success
                400: authentication problem.
                HTTPBadRequest: not manager, or malformed request.
                HTTPConflict: we already have a classlist.
                    TODO: would be nice to be able to "try again".
                HTTPNotAcceptable: classlist too short (see above).
        """
        if not data["user"] == "manager":
            raise web.HTTPBadRequest(reason="Not manager")
        if os.path.isfile(Path(specdir) / "classlist.csv"):
            raise web.HTTPConflict(reason="we already have a classlist")
        classlist = data["classlist"]
        # TODO should we make copy until sure it passes verification?
        spec = self.server.testSpec
        if spec.numberToName < 0 or spec.numberToProduce < 0:
            if spec.number_to_name < 0:
                spec.set_number_papers_to_name(len(classlist))
            if spec.number_to_produce < 0:
                spec.set_number_papers_add_spares(len(classlist))
            try:
                spec.verifySpec(verbose="log")
            except ValueError as e:
                raise web.HTTPNotAcceptable(reason=str(e))
            spec.saveVerifiedSpec()
        with open(Path(specdir) / "classlist.csv", "w") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(["id", "studentName"])
            writer.writerows(classlist)
        return web.Response()
示例#19
0
    async def __call__(self, request):
        authorization = request.headers.get('Authorization')
        # TODO: can authorization be '' or is None test?
        if not authorization:
            authorization = request.query.get('access_token')
        await check_authorization(request, self.acl, authorization)

        try:
            accepted = next(x[0] for x in request.headers.getall('ACCEPT', [])
                            if x[0].startswith(self.HEADER_PREFIX))
            accepted_version = int(accepted[len(self.HEADER_PREFIX):])
        except (StopIteration, ValueError):
            accepted_version = self.MIN_API_VERSION

        try:
            func = next(func for version, func in self.funcs
                        if version <= accepted_version)
        except StopIteration:
            raise web.HTTPNotAcceptable()

        return await func(request)
示例#20
0
async def Event(request: web.Request) -> EventType:
    user_agent = request.headers.get('User-Agent', None)
    if not user_agent.startswith('GitHub-Hookshot/'):
        raise web.HTTPNotAcceptable(reason='User agent looks incorrect')
    content_type = request.headers.get('Content-Type', None)
    if not content_type == 'application/json':
        raise web.HTTPUnsupportedMediaType(reason='Not a JSON payload')
    payload = await request.json()
    event_type = request.headers.get('X-GitHub-Event', None)
    key = 'issue' if event_type == 'issues' else event_type
    return EventType(
        app=request.app,
        key=key,
        type=event_type,
        guid=request.headers.get('X-GitHub-Delivery', None),
        signature=request.headers.get('X-Hub-Signature', None),
        user_agent=user_agent,
        content_type=content_type,
        payload=payload,
        action=payload.get('action', None),
    )
示例#21
0
 def error(self,
           request: web.Request = None,
           response: dict = {},
           exception: Exception = None,
           state: int = 400,
           headers: dict = {},
           **kwargs) -> web.Response:
     # TODO: process the exception object
     response_obj = {"status": "Failed"}
     if not request:
         request = self.request
     if exception:
         response_obj["reason"] = str(exception)
     args = {**kwargs}
     if isinstance(response, dict):
         response_obj = {**response_obj, **response}
         args["content_type"] = "application/json"
         args["text"] = json.dumps(response_obj)
     else:
         args["body"] = response
     # defining the error
     if state == 400:  # bad request
         obj = web.HTTPBadRequest(**args)
     elif state == 401:  # unauthorized
         obj = web.HTTPUnauthorized(**args)
     elif state == 403:  # forbidden
         obj = web.HTTPForbidden(**args)
     elif state == 404:  # not found
         obj = web.HTTPNotFound(**args)
     elif state == 406:
         obj = web.HTTPNotAcceptable(**args)
     elif state == 412:
         obj = web.HTTPPreconditionFailed(**args)
     elif state == 428:
         obj = web.HTTPPreconditionRequired(**args)
     else:
         obj = web.HTTPBadRequest(**args)
     for header, value in headers.items():
         obj.headers[header] = value
     return obj
示例#22
0
    async def post(self) -> Union[web.json_response, web.HTTPException]:
        """
        Take user credentials and check if user exist.
        If exist - return his profile in JSON. Else -> HTTPException
        Returns: User profile or Exception

        """
        request_data = await self.request.json()

        result = await self.find_user(request_data)

        if not result:
            raise web.HTTPNotFound(text="User not found")

        profile = Profile(**clean_data(result))

        if profile.check_password(request_data['password']):
            await profile.fetch_contacts(self.request.app['db'])

            return web.json_response(profile.to_dict(), status=200)

        raise web.HTTPNotAcceptable(text="Wrong password")
示例#23
0
 def serialize(self, data):
     raise web.HTTPNotAcceptable(
         body='')  # TODO respond with all acceptable content-types
示例#24
0
文件: api.py 项目: williamrv19/xenon
    async def is_ready(self, request):
        if self.bot.is_ready():
            raise web.HTTPOk()

        raise web.HTTPNotAcceptable()
示例#25
0
文件: api.py 项目: williamrv19/xenon
    async def is_connected(self, request):
        if self.connected:
            raise web.HTTPOk()

        raise web.HTTPNotAcceptable()
示例#26
0
    async def liveness_probe(self, request):
        if self.bot.is_ready():
            raise web.HTTPOk()

        raise web.HTTPNotAcceptable()
示例#27
0
 def handler_not_acceptable(self):
     raise web.HTTPNotAcceptable(text=json.dumps(NOT_ACCEPTABLE_MSG),
                                 content_type="application/json")
示例#28
0
 def raiseIfNotAllowed(self):
     if not self.shouldHandle:
         raise web.HTTPNotAcceptable(body='')
示例#29
0
async def reset(request):
    """ reset user (only role and password)
        :Example:
            curl -H "authorization: <token>" -X PUT -d '{"role_id": "1"}' http://localhost:8081/fledge/admin/{user_id}/reset
            curl -H "authorization: <token>" -X PUT -d '{"password": "******"}' http://localhost:8081/fledge/admin/{user_id}/reset
            curl -H "authorization: <token>" -X PUT -d '{"role_id": 1, "password": "******"}' http://localhost:8081/fledge/admin/{user_id}/reset
    """
    if request.is_auth_optional:
        _logger.warning(FORBIDDEN_MSG)
        raise web.HTTPForbidden

    user_id = request.match_info.get('user_id')
    if int(user_id) == 1:
        msg = "Restricted for Super Admin user"
        _logger.warning(msg)
        raise web.HTTPNotAcceptable(reason=msg)

    data = await request.json()
    password = data.get('password')
    role_id = data.get('role_id')

    if not role_id and not password:
        msg = "Nothing to update the user"
        _logger.warning(msg)
        raise web.HTTPBadRequest(reason=msg)

    if role_id and not (await is_valid_role(role_id)):
        msg = "Invalid or bad role id"
        _logger.warning(msg)
        return web.HTTPBadRequest(reason=msg)

    if password and not isinstance(password, str):
        _logger.warning(PASSWORD_ERROR_MSG)
        raise web.HTTPBadRequest(reason=PASSWORD_ERROR_MSG)
    if password and not re.match(PASSWORD_REGEX_PATTERN, password):
        _logger.warning(PASSWORD_ERROR_MSG)
        raise web.HTTPBadRequest(reason=PASSWORD_ERROR_MSG)

    user_data = {}
    if 'role_id' in data:
        user_data.update({'role_id': data['role_id']})
    if 'password' in data:
        user_data.update({'password': data['password']})

    try:
        await User.Objects.update(user_id, user_data)
    except ValueError as ex:
        _logger.warning(str(ex))
        raise web.HTTPBadRequest(reason=str(ex))
    except User.DoesNotExist:
        msg = "User with id:<{}> does not exist".format(int(user_id))
        _logger.warning(msg)
        raise web.HTTPNotFound(reason=msg)
    except User.PasswordAlreadyUsed:
        msg = "The new password should be different from previous 3 used"
        _logger.warning(msg)
        raise web.HTTPBadRequest(reason=msg)
    except Exception as exc:
        _logger.exception(str(exc))
        raise web.HTTPInternalServerError(reason=str(exc))

    _logger.info("User with id:<{}> has been updated successfully".format(
        int(user_id)))

    return web.json_response({
        'message':
        'User with id:<{}> has been updated successfully'.format(user_id)
    })
示例#30
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)
        })