Exemple #1
0
    def test_node_list_without_zone_does_not_filter(self):
        nodes = [factory.make_Node(zone=factory.make_Zone()) for _ in range(3)]

        query = RequestFixture({}, '')
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertSequenceEqual([node.system_id for node in nodes],
                                 extract_system_ids_from_nodes(node_list))
Exemple #2
0
    def test_node_list_with_agent_name_filters_with_empty_string(self):
        factory.make_Node(agent_name=factory.make_name("agent-name"))
        node = factory.make_Node(agent_name="")

        query = RequestFixture({"agent_name": ""}, "agent_name")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertSequenceEqual([node.system_id],
                                 extract_system_ids_from_nodes(node_list))
Exemple #3
0
    def test_node_list_with_nonexistent_id_returns_empty_list(self):
        # Trying to list a nonexistent node id returns a list containing
        # no nodes -- even if other (non-matching) nodes exist.
        existing_id = factory.make_Node().system_id
        nonexistent_id = existing_id + factory.make_string()
        query = RequestFixture({"id": [nonexistent_id]}, "id")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertItemsEqual([], extract_system_ids_from_nodes(node_list))
Exemple #4
0
    def test_node_list_with_id_returns_matching_nodes(self):
        # The "list" operation takes optional "id" parameters.  Only
        # nodes with matching ids will be returned.
        ids = [factory.make_Node().system_id for _ in range(3)]
        matching_id = ids[0]
        query = RequestFixture({"id": [matching_id]}, "id")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertItemsEqual([matching_id],
                              extract_system_ids_from_nodes(node_list))
Exemple #5
0
    def test_node_list_with_pool_filters_by_pool(self):
        pool1 = factory.make_ResourcePool()
        pool2 = factory.make_ResourcePool()
        node1 = factory.make_Node(pool=pool1)
        factory.make_Node(pool=pool2)

        query = RequestFixture({"pool": pool1.name}, "pool", self.user)
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertSequenceEqual([node1.system_id],
                                 extract_system_ids_from_nodes(node_list))
Exemple #6
0
    def test_node_list_with_some_matching_ids_returns_matching_nodes(self):
        # If some nodes match the requested ids and some don't, only the
        # matching ones are returned.
        existing_id = factory.make_Node().system_id
        nonexistent_id = existing_id + factory.make_string()

        query = RequestFixture({"id": [existing_id, nonexistent_id]}, "id")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertItemsEqual([existing_id],
                              extract_system_ids_from_nodes(node_list))
Exemple #7
0
    def test_node_list_with_zone_filters_by_zone(self):
        non_listed_node = factory.make_Node(zone=factory.make_Zone(
            name="twilight"))
        ignore_unused(non_listed_node)
        zone = factory.make_Zone()
        node = factory.make_Node(zone=zone)

        query = RequestFixture({"zone": zone.name}, "zone")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertSequenceEqual([node.system_id],
                                 extract_system_ids_from_nodes(node_list))
Exemple #8
0
    def test_node_list_with_agent_name_filters_by_agent_name(self):
        non_listed_node = factory.make_Node(
            agent_name=factory.make_name("agent_name"))
        ignore_unused(non_listed_node)
        agent_name = factory.make_name("agent-name")
        node = factory.make_Node(agent_name=agent_name)

        query = RequestFixture({"agent_name": agent_name}, "agent_name")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertSequenceEqual([node.system_id],
                                 extract_system_ids_from_nodes(node_list))
Exemple #9
0
    def test_node_list_with_hostname_returns_matching_nodes(self):
        # The list operation takes optional "hostname" parameters. Only nodes
        # with matching hostnames will be returned.
        nodes = [factory.make_Node() for _ in range(3)]
        matching_hostname = nodes[0].hostname
        matching_system_id = nodes[0].system_id

        query = RequestFixture({"hostname": [matching_hostname]}, "hostname")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertItemsEqual([matching_system_id],
                              extract_system_ids_from_nodes(node_list))
Exemple #10
0
    def test_node_list_without_pool_does_not_filter(self):
        nodes = [
            factory.make_Node(pool=factory.make_ResourcePool())
            for _ in range(3)
        ]

        query = RequestFixture({}, "", self.user)
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertSequenceEqual(
            [node.system_id for node in nodes],
            extract_system_ids_from_nodes(node_list),
        )
Exemple #11
0
    def test_node_list_without_agent_name_does_not_filter(self):
        nodes = [
            factory.make_Node(agent_name=factory.make_name("agent-name"))
            for _ in range(3)
        ]

        query = RequestFixture({}, "")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertSequenceEqual(
            [node.system_id for node in nodes],
            extract_system_ids_from_nodes(node_list),
        )
Exemple #12
0
    def test_node_list_with_macs_returns_matching_nodes(self):
        # The "list" operation takes optional "mac_address" parameters. Only
        # nodes with matching MAC addresses will be returned.
        interfaces = [
            factory.make_Interface(INTERFACE_TYPE.PHYSICAL) for _ in range(3)
        ]
        matching_mac = str(interfaces[0].mac_address)
        matching_system_id = interfaces[0].node.system_id

        query = RequestFixture({"mac_address": [matching_mac]}, "mac_address")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        self.assertItemsEqual([matching_system_id],
                              extract_system_ids_from_nodes(node_list))
Exemple #13
0
    def test_node_lists_list_devices(self):
        machines = [
            factory.make_Node(agent_name=factory.make_name('agent-name'))
            for _ in range(3)
        ]
        # Create devices.
        devices = [factory.make_Device() for _ in range(3)]

        query = RequestFixture({}, '')
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        system_ids = extract_system_ids_from_nodes(node_list)
        self.assertEqual([node.system_id for node in machines + devices],
                         system_ids, "Node listing doesn't contain devices.")
Exemple #14
0
    def test_node_list_with_ids_orders_by_id(self):
        # Even when ids are passed to "list," nodes are returned in id
        # order, not necessarily in the order of the id arguments.
        all_nodes = [factory.make_Node() for _ in range(3)]
        system_ids = [node.system_id for node in all_nodes]
        random.shuffle(system_ids)

        query = RequestFixture({"id": list(system_ids)}, "id")
        node_list = nodes_module.filtered_nodes_list_from_request(query)

        sorted_system_ids = [
            node.system_id
            for node in sorted(all_nodes, key=lambda node: node.id)
        ]
        self.assertSequenceEqual(sorted_system_ids,
                                 extract_system_ids_from_nodes(node_list))
Exemple #15
0
    def query(self, request):
        """@description-title List node events
        @description List node events, optionally filtered by various criteria
        via URL query parameters.

        @param (string) "hostname" [required=false] An optional hostname. Only
        events relating to the node with the matching hostname will be
        returned. This can be specified multiple times to get events relating
        to more than one node.

        @param (string) "mac_address" [required=false] An optional list of MAC
        addresses.  Only nodes with matching MAC addresses will be returned.

        @param (string) "id" [required=false] An optional list of system ids.
        Only nodes with matching system ids will be returned.

        @param (string) "zone" [required=false] An optional name for a physical
        zone. Only nodes in the zone will be returned.

        @param (string) "agent_name" [required=false] An optional agent name.
        Only nodes with matching agent names will be returned.

        @param (string) "level" [required=false] Desired minimum log level of
        returned events. Returns this level of events and greater. Choose from:
        %(log_levels)s.  The default is INFO.

        @param (string) "limit" [required=false] Optional number of events to
        return. Default 100.  Maximum: 1000.

        @param (string) "before" [required=false] Optional event id.  Defines
        where to start returning older events.

        @param (string) "after" [required=false] Optional event id.  Defines
        where to start returning newer events.

        @param (string) "owner" [required=false] If specified, filters the list
        to show only events owned by the specified username.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing a list of
        events objects.
        @success-example "success-json" [exkey=events-query] placeholder text
        """
        # Extract & validate optional parameters from the request.
        after = get_optional_param(request.GET, 'after', None, Int)
        before = get_optional_param(request.GET, 'before', None, Int)
        level = get_optional_param(request.GET, 'level', 'INFO')
        limit = get_optional_param(
            request.GET, "limit", DEFAULT_EVENT_LOG_LIMIT, Int)
        owner = get_optional_param(request.GET, 'owner', default=None)

        # Limit what we'll return to avoid being swamped.
        if limit > MAX_EVENT_LOG_COUNT:
            raise MAASAPIBadRequest((
                "Requested number of events %d is greater than"
                " limit: %d") % (limit, MAX_EVENT_LOG_COUNT))
        else:
            # The limit should never be less than 1.
            limit = 1 if limit < 1 else limit

        # Filter first by optional node ID, hostname, MAC, etc.
        nodes = filtered_nodes_list_from_request(request)
        # Event lists aren't supported on devices.
        nodes = nodes.exclude(node_type=NODE_TYPE.DEVICE)

        # Check first for AUDIT level.
        if level == LOGGING_LEVELS[AUDIT]:
            events = Event.objects.filter(type__level=AUDIT)
        elif level in LOGGING_LEVELS_BY_NAME:
            events = Event.objects.filter(node__in=nodes)
            # Eliminate logs below the requested level.
            events = events.exclude(
                type__level__lt=LOGGING_LEVELS_BY_NAME[level])
        elif level is not None:
            raise MAASAPIBadRequest(
                "Unrecognised log level: %s" % level)

        events = (
            events.all()
            .select_related('type')
            .select_related('node'))

        # Filter events for owner.
        if owner is not None:
            events = events.filter(username=owner)

        # Future feature:
        # This is where we would filter for events 'since last node deployment'
        # using a query param like since_last_deployed=true, but we aren't
        # right now because we don't currently record a timestamp of the last
        # deployment, and we don't have an event subtype for node status
        # changes to filter for the deploying status event.

        if after is None and before is None:
            # Get `limit` events, newest first.
            events = events.order_by('-id')
            events = events[:limit]
        elif after is None:
            # Get `limit` events, newest first, all before `before`.
            events = events.filter(id__lt=before)
            events = events.order_by('-id')
            events = events[:limit]
        elif before is None:
            # Get `limit` events, OLDEST first, all after `after`, then
            # reverse the results.
            events = events.filter(id__gt=after)
            events = events.order_by('id')
            events = reversed(events[:limit])
        else:
            raise MAASAPIBadRequest(
                "There is undetermined behaviour when both "
                "`after` and `before` are specified.")

        # We need to load all of these events at some point, so save them
        # into a list now so that len() is cheap.
        events = list(events)

        # Helper for building prev_uri and next_uri.
        def make_uri(params, base=reverse('events_handler')):
            query = urllib.parse.urlencode(params, doseq=True)
            url = urllib.parse.urlparse(base)._replace(query=query)
            return url.geturl()

        # Figure out a URI to obtain a set of newer events.
        next_uri_params = get_overridden_query_dict(
            request.GET, {"before": []}, self.all_params)
        if len(events) == 0:
            if before is None:
                # There are no newer events NOW, but there may be later.
                next_uri = make_uri(next_uri_params)
            else:
                # Without limiting to `before`, we might find some more events.
                next_uri_params["after"] = before - 1
                next_uri = make_uri(next_uri_params)
        else:
            # The first event is the newest.
            next_uri_params["after"] = str(events[0].id)
            next_uri = make_uri(next_uri_params)

        # Figure out a URI to obtain a set of older events.
        prev_uri_params = get_overridden_query_dict(
            request.GET, {"after": []}, self.all_params)
        if len(events) == 0:
            if after is None:
                # There are no older events and never will be.
                prev_uri = None
            else:
                # Without limiting to `after`, we might find some more events.
                prev_uri_params["before"] = after + 1
                prev_uri = make_uri(prev_uri_params)
        else:
            # The last event is the oldest.
            prev_uri_params["before"] = str(events[-1].id)
            prev_uri = make_uri(prev_uri_params)

        return {
            "count": len(events),
            "events": [event_to_dict(event) for event in events],
            "next_uri": next_uri,
            "prev_uri": prev_uri,
        }