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
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, )
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
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))
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()
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)
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