Example #1
0
 def dehydrate_subnet(self, subnet, for_list=False):
     data = {
         "id": subnet.id,
         "updated": dehydrate_datetime(subnet.updated),
         "created": dehydrate_datetime(subnet.created),
         "name": subnet.name,
         "description": subnet.description,
         "dns_servers": (
             " ".join(sorted(subnet.dns_servers))
             if subnet.dns_servers is not None
             else ""
         ),
         "vlan": subnet.vlan_id,
         "space": subnet.vlan.space_id,
         "rdns_mode": subnet.rdns_mode,
         "allow_dns": subnet.allow_dns,
         "allow_proxy": subnet.allow_proxy,
         "cidr": subnet.cidr,
         "gateway_ip": subnet.gateway_ip,
         "active_discovery": subnet.active_discovery,
         "managed": subnet.managed,
         "disabled_boot_architectures": subnet.disabled_boot_architectures,
     }
     full_range = subnet.get_iprange_usage()
     metadata = IPRangeStatistics(full_range)
     data["statistics"] = metadata.render_json(
         include_ranges=True, include_suggestions=True
     )
     data["version"] = IPNetwork(subnet.cidr).version
     if not for_list:
         data["ip_addresses"] = subnet.render_json_for_related_ips(
             with_username=True, with_summary=True
         )
     return data
Example #2
0
    def statistics(self, request, id):
        """@description-title Get subnet statistics
        @description Returns statistics for the specified subnet, including:

        - **num_available**: the number of available IP addresses
        - **largest_available**: the largest number of contiguous free IP
          addresses
        - **num_unavailable**: the number of unavailable IP addresses
        - **total_addresses**: the sum of the available plus unavailable
          addresses
        - **usage**: the (floating point) usage percentage of this subnet
        - **usage_string**: the (formatted unicode) usage percentage of this
          subnet
        - **ranges**: the specific IP ranges present in ths subnet (if
          specified)

        Note: to supply additional optional parameters for this request, add
        them to the request URI: e.g.
        ``/subnets/1/?op=statistics&include_suggestions=1``

        @param (int) "{id}" [required=true] A subnet ID.

        @param (int) "include_ranges" [required=false] If '1', includes
        detailed information about the usage of this range. '1' == True, '0' ==
        False.

        @param (int) "include_suggestions" [required=false] If '1', includes
        the suggested gateway and dynamic range for this subnet, if it were to
        be configured. '1' == True, '0' == False.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the statistics.
        @success-example "success-json" [exkey=subnets-statistics]
        placeholder text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested subnet is not found.
        @error-example "not-found"
            Not Found
        """
        subnet = Subnet.objects.get_subnet_or_404(
            id, request.user, NodePermission.view
        )
        include_ranges = get_optional_param(
            request.GET, "include_ranges", default=False, validator=StringBool
        )
        include_suggestions = get_optional_param(
            request.GET,
            "include_suggestions",
            default=False,
            validator=StringBool,
        )
        full_iprange = subnet.get_iprange_usage()
        statistics = IPRangeStatistics(full_iprange)
        return statistics.render_json(
            include_ranges=include_ranges,
            include_suggestions=include_suggestions,
        )
Example #3
0
 def dehydrate(self, subnet, data, for_list=False):
     full_range = subnet.get_iprange_usage()
     metadata = IPRangeStatistics(full_range)
     data['statistics'] = metadata.render_json(include_ranges=True,
                                               include_suggestions=True)
     data['version'] = IPNetwork(subnet.cidr).version
     data['space'] = subnet.vlan.space_id
     if not for_list:
         data["ip_addresses"] = subnet.render_json_for_related_ips(
             with_username=True, with_summary=True)
     return data
Example #4
0
 def test__default_does_not_include_ranges(self):
     subnet = factory.make_Subnet()
     factory.make_StaticIPAddress(alloc_type=IPADDRESS_TYPE.USER_RESERVED,
                                  subnet=subnet)
     response = self.client.get(get_subnet_uri(subnet), {
         'op': 'statistics',
     })
     self.assertEqual(http.client.OK, response.status_code,
                      explain_unexpected_response(http.client.OK, response))
     result = json.loads(response.content.decode(settings.DEFAULT_CHARSET))
     full_iprange = subnet.get_iprange_usage()
     statistics = IPRangeStatistics(full_iprange)
     expected_result = statistics.render_json(include_ranges=False)
     self.assertThat(result, Equals(expected_result))
Example #5
0
    def update_allocation_notification(self):
        # Workaround for edge cases in Django. (See bug #1702527.)
        if self.id is None:
            return
        ident = "ip_exhaustion__subnet_%d" % self.id
        # Circular imports.
        from maasserver.models import Config, Notification

        threshold = Config.objects.get_config(
            "subnet_ip_exhaustion_threshold_count"
        )
        notification = Notification.objects.filter(ident=ident).first()
        delete_notification = False
        if threshold > 0:
            full_iprange = self.get_iprange_usage()
            statistics = IPRangeStatistics(full_iprange)
            # Check if there are less available IPs in the subnet than the
            # warning threshold.
            meets_warning_threshold = statistics.num_available <= threshold
            # Check if the warning threshold is appropriate relative to the
            # size of the subnet. It's pointless to warn about address
            # exhaustion on a /30, for example: the admin already knows it's
            # small, so we would just be annoying them.
            subnet_is_reasonably_large_relative_to_threshold = (
                threshold * 3 <= statistics.total_addresses
            )
            if (
                meets_warning_threshold
                and subnet_is_reasonably_large_relative_to_threshold
            ):
                notification_text = (
                    "IP address exhaustion imminent on subnet: %s. "
                    "There are %d free addresses out of %d "
                    "(%s used)."
                ) % (
                    self.label,
                    statistics.num_available,
                    statistics.total_addresses,
                    statistics.usage_percentage_string,
                )
                if notification is None:
                    Notification.objects.create_warning_for_admins(
                        notification_text, ident=ident
                    )
                else:
                    # Note: This will update the notification, but will not
                    # bring it back for those who have dismissed it. Maybe we
                    # should consider creating a new notification if the
                    # situation is now more severe, such as raise it to an
                    # error if it's half remaining threshold.
                    notification.message = notification_text
                    notification.save()
            else:
                delete_notification = True
        else:
            delete_notification = True
        if notification is not None and delete_notification:
            notification.delete()
Example #6
0
    def statistics(self, request, id):
        """\
        Returns statistics for the specified subnet, including:

        num_available: the number of available IP addresses
        largest_available: the largest number of contiguous free IP addresses
        num_unavailable: the number of unavailable IP addresses
        total_addresses: the sum of the available plus unavailable addresses
        usage: the (floating point) usage percentage of this subnet
        usage_string: the (formatted unicode) usage percentage of this subnet
        ranges: the specific IP ranges present in ths subnet (if specified)

        Optional parameters
        -------------------

        include_ranges
           If True, includes detailed information
           about the usage of this range.

        include_suggestions
          If True, includes the suggested gateway and dynamic range for this
          subnet, if it were to be configured.

        Returns 404 if the subnet is not found.
        """
        subnet = Subnet.objects.get_subnet_or_404(id, request.user,
                                                  NodePermission.view)
        include_ranges = get_optional_param(request.GET,
                                            'include_ranges',
                                            default=False,
                                            validator=StringBool)
        include_suggestions = get_optional_param(request.GET,
                                                 'include_suggestions',
                                                 default=False,
                                                 validator=StringBool)
        full_iprange = subnet.get_iprange_usage()
        statistics = IPRangeStatistics(full_iprange)
        return statistics.render_json(include_ranges=include_ranges,
                                      include_suggestions=include_suggestions)
Example #7
0
def get_subnets_utilisation_stats():
    """Return a dict mapping subnet CIDRs to their utilisation details."""
    ips_count = _get_subnets_ipaddress_count()

    stats = {}
    for subnet in Subnet.objects.all():
        full_range = subnet.get_iprange_usage()
        range_stats = IPRangeStatistics(subnet.get_iprange_usage())
        static = 0
        reserved_available = 0
        reserved_used = 0
        dynamic_available = 0
        dynamic_used = 0
        for rng in full_range.ranges:
            if IPRANGE_TYPE.DYNAMIC in rng.purpose:
                dynamic_available += rng.num_addresses
            elif IPRANGE_TYPE.RESERVED in rng.purpose:
                reserved_available += rng.num_addresses
            elif "assigned-ip" in rng.purpose:
                static += rng.num_addresses
        # allocated IPs
        subnet_ips = ips_count[subnet.id]
        reserved_used += subnet_ips[IPADDRESS_TYPE.USER_RESERVED]
        reserved_available -= reserved_used
        dynamic_used += (
            subnet_ips[IPADDRESS_TYPE.AUTO]
            + subnet_ips[IPADDRESS_TYPE.DHCP]
            + subnet_ips[IPADDRESS_TYPE.DISCOVERED]
        )
        dynamic_available -= dynamic_used
        stats[subnet.cidr] = {
            "available": range_stats.num_available,
            "unavailable": range_stats.num_unavailable,
            "dynamic_available": dynamic_available,
            "dynamic_used": dynamic_used,
            "static": static,
            "reserved_available": reserved_available,
            "reserved_used": reserved_used,
        }
    return stats