diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 360ec4ad..ea1a7fcd 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -262,7 +262,12 @@ def enumerate_nodegroup_collection(collectionpath, configmanager): def enumerate_node_collection(collectionpath, configmanager): if collectionpath == ['nodes']: # it is just '/node/', need to list nodes - return iterate_collections(configmanager.list_nodes()) + allnodes = list(configmanager.list_nodes()) + try: + allnodes.sort(key=noderange.humanify_nodename) + except TypeError: + allnodes.sort() + return iterate_collections(allnodes) nodeorrange = collectionpath[1] if collectionpath[0] == 'nodes' and not configmanager.is_node(nodeorrange): raise exc.NotFoundException("Invalid element requested") @@ -368,8 +373,8 @@ def handle_node_request(configmanager, inputdata, operation, if isnoderange: try: nodes = noderange.NodeRange(nodeorrange, configmanager).nodes - except Exception: - raise exc.NotFoundException("Invalid Noderange") + except Exception as e: + raise exc.NotFoundException("Invalid Noderange: " + str(e)) else: nodes = (nodeorrange,) except IndexError: # doesn't actually have a long enough path @@ -381,9 +386,19 @@ def handle_node_request(configmanager, inputdata, operation, if operation == "create": inputdata = msg.InputAttributes(pathcomponents, inputdata) create_node(inputdata.attribs, configmanager) - return iterate_collections(configmanager.list_nodes()) + allnodes = list(configmanager.list_nodes()) + try: + allnodes.sort(key=noderange.humanify_nodename) + except TypeError: + allnodes.sort() + return iterate_collections(allnodes) if isnoderange and len(pathcomponents) == 3 and pathcomponents[2] == 'nodes': # this means that it's a list of relevant nodes + nodes = list(nodes) + try: + nodes.sort(key=noderange.humanify_nodename) + except TypeError: + nodes.sort() return iterate_collections(nodes) if len(pathcomponents) == 2: iscollection = True diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 7e103c27..b1c70b21 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -393,9 +393,9 @@ def resourcehandler_backend(env, start_response): pagecontent += datum start_response('200 OK', headers) yield pagecontent - except exc.NotFoundException: + except exc.NotFoundException as ne: start_response('404 Not found', headers) - yield "404 - Request path not recognized" + yield "404 - Request path not recognized - " + str(ne) except exc.InvalidArgumentException as e: start_response('400 Bad Request - ' + str(e), headers) yield '400 - Bad Request - ' + str(e) diff --git a/confluent_server/confluent/noderange.py b/confluent_server/confluent/noderange.py index bb58574b..c533cfd4 100644 --- a/confluent_server/confluent/noderange.py +++ b/confluent_server/confluent/noderange.py @@ -22,19 +22,30 @@ import itertools import pyparsing as pp +import re # construct custom grammar with pyparsing _nodeword = pp.Word(pp.alphanums + '~^$/=-:.*+!') _nodebracket = pp.QuotedString(quoteChar='[', endQuoteChar=']', unquoteResults=False) _nodeatom = pp.Group(pp.OneOrMore(_nodeword | _nodebracket)) -_paginationstart = pp.Word('<', pp.nums) -_paginationend = pp.Word('>', pp.nums) +_paginationstart = pp.Group(pp.Word('<', pp.nums)) +_paginationend = pp.Group(pp.Word('>', pp.nums)) _grammar = _nodeatom | ',-' | ',' | '@' | _paginationstart | _paginationend _parser = pp.nestedExpr(content=_grammar) _numextractor = pp.OneOrMore(pp.Word(pp.alphas + '-') | pp.Word(pp.nums)) +numregex = re.compile('([0-9]+)') +def humanify_nodename(nodename): + """Analyzes nodename in a human way to enable natural sort + + :param nodename: The node name to analyze + :returns: A structure that can be consumed by 'sorted' + """ + return [int(text) if text.isdigit() else text.lower() + for text in re.split(numregex, nodename)] + # TODO: pagination operators pp.nums for begin and end respective class NodeRange(object): @@ -48,15 +59,27 @@ class NodeRange(object): self.beginpage = None self.endpage = None self.cfm = config - elements = _parser.parseString("(" + noderange + ")").asList()[0] - self._noderange = self._evaluate(elements) + try: + elements = _parser.parseString("(" + noderange + ")").asList()[0] + except pp.ParseException as pe: + raise Exception("Invalid syntax") + if noderange[0] in ('<', '>'): + # pagination across all nodes + self._evaluate(elements) + self._noderange = set(self.cfm.list_nodes()) + else: + self._noderange = self._evaluate(elements) @property def nodes(self): if self.beginpage is None and self.endpage is None: return self._noderange sortedlist = list(self._noderange) - sortedlist.sort() + try: + sortedlist.sort(key=humanify_nodename) + except TypeError: + # The natural sort attempt failed, fallback to ascii sort + sortedlist.sort() if self.beginpage is not None: sortedlist = sortedlist[self.beginpage:] if self.endpage is not None: @@ -135,8 +158,6 @@ class NodeRange(object): def expand_entity(self, entname): if self.cfm is None or self.cfm.is_node(entname): return set([entname]) - if self.cfm.is_node(entname): - return set([entname]) if self.cfm.is_nodegroup(entname): grpcfg = self.cfm.get_nodegroup_attributes(entname) nodes = grpcfg['nodes'] @@ -144,6 +165,7 @@ class NodeRange(object): nodes |= NodeRange( grpcfg['noderange']['value'], self.cfm).nodes return nodes + raise Exception('Unknown node ' + entname) def _expandstring(self, element, filternodes=None): prefix = '' diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index ae9a9713..ead744aa 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -214,9 +214,9 @@ def process_request(connection, request, cfm, authdata, authname, skipauth): configmanager.ConfigManager.shutdown() else: hdlr = pluginapi.handle_path(path, operation, cfm, params) - except exc.NotFoundException: + except exc.NotFoundException as e: tlvdata.send(connection, {"errorcode": 404, - "error": "Target not found"}) + "error": "Target not found - " + str(e)}) tlvdata.send(connection, {"_requestdone": 1}) except exc.InvalidArgumentException as e: tlvdata.send(connection, {"errorcode": 400,