freeipa/ipaserver/plugins/location.py
Rob Crittenden f589a8952c Fix div-by-zero when svc weight is 0 for all masters in location
The relative service weight output tries to show the relative
chance that any given master in a locaiton will be picked. This
didn't account for all masters having a weight of 0 which would
result in a divide-by-zero error.

Implement the following rules:
1. If all masters have weight == 0 then all are equally
   weighted.
2. If any masters have weight == 0 then they have an
   extremely small chance of being chosen, percentage is
   0.1.
3. Otherwise it's percentage change is based on the sum of
   the weights of non-zero masters.

https://pagure.io/freeipa/issue/8135

Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
2020-02-26 13:42:10 -05:00

266 lines
7.8 KiB
Python

#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from __future__ import (
absolute_import,
division,
)
from ipalib import (
_,
ngettext,
api,
Str,
DNSNameParam,
output,
messages
)
from ipalib.errors import DependentEntry
from ipalib.plugable import Registry
from ipaserver.dns_data_management import IPASystemRecords
from ipaserver.plugins.baseldap import (
LDAPCreate,
LDAPSearch,
LDAPRetrieve,
LDAPDelete,
LDAPObject,
LDAPUpdate,
)
from ipapython.dn import DN
from ipapython.dnsutil import DNSName
__doc__ = _("""
IPA locations
""") + _("""
Manipulate DNS locations
""") + _("""
EXAMPLES:
""") + _("""
Find all locations:
ipa location-find
""") + _("""
Show specific location:
ipa location-show location
""") + _("""
Add location:
ipa location-add location --description 'My location'
""") + _("""
Delete location:
ipa location-del location
""")
register = Registry()
@register()
class location(LDAPObject):
"""
IPA locations
"""
container_dn = api.env.container_locations
object_name = _('location')
object_name_plural = _('locations')
object_class = ['top', 'ipaLocationObject']
search_attributes = ['idnsName']
default_attributes = [
'idnsname', 'description'
]
label = _('IPA Locations')
label_singular = _('IPA Location')
permission_filter_objectclasses = ['ipaLocationObject']
managed_permissions = {
'System: Read IPA Locations': {
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'objectclass', 'idnsname', 'description',
},
'default_privileges': {'DNS Administrators'},
},
'System: Add IPA Locations': {
'ipapermright': {'add'},
'default_privileges': {'DNS Administrators'},
},
'System: Remove IPA Locations': {
'ipapermright': {'delete'},
'default_privileges': {'DNS Administrators'},
},
'System: Modify IPA Locations': {
'ipapermright': {'write'},
'ipapermdefaultattr': {
'description',
},
'default_privileges': {'DNS Administrators'},
},
}
takes_params = (
DNSNameParam(
'idnsname',
cli_name='name',
primary_key=True,
label=_('Location name'),
doc=_('IPA location name'),
# dns name must be relative, we will put it into middle of
# location domain name for location records
only_relative=True,
),
Str(
'description?',
label=_('Description'),
doc=_('IPA Location description'),
),
Str(
'servers_server*',
label=_('Servers'),
doc=_('Servers that belongs to the IPA location'),
flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
),
Str(
'dns_server*',
label=_('Advertised by servers'),
doc=_('List of servers which advertise the given location'),
flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
),
)
def get_dn(self, *keys, **options):
loc = keys[0]
assert isinstance(loc, DNSName)
loc_a = loc.ToASCII()
return super(location, self).get_dn(loc_a, **options)
@register()
class location_add(LDAPCreate):
__doc__ = _('Add a new IPA location.')
msg_summary = _('Added IPA location "%(value)s"')
@register()
class location_del(LDAPDelete):
__doc__ = _('Delete an IPA location.')
msg_summary = _('Deleted IPA location "%(value)s"')
def pre_callback(self, ldap, dn, *keys, **options):
assert isinstance(dn, DN)
if not options.get('force'):
servers = self.api.Command.server_find(
in_location=keys[-1])['result']
location_member = servers[0]['cn'][0] if servers else None
if location_member:
raise DependentEntry(
label=_('IPA Server'),
key=keys[-1],
dependent=location_member
)
system_records =IPASystemRecords(self.api)
_success, failed = system_records.remove_location_records(keys[-1])
if failed:
self.add_message(messages.AutomaticDNSRecordsUpdateFailed())
return dn
@register()
class location_mod(LDAPUpdate):
__doc__ = _('Modify information about an IPA location.')
msg_summary = _('Modified IPA location "%(value)s"')
@register()
class location_find(LDAPSearch):
__doc__ = _('Search for IPA locations.')
msg_summary = ngettext(
'%(count)d IPA location matched',
'%(count)d IPA locations matched', 0
)
@register()
class location_show(LDAPRetrieve):
__doc__ = _('Display information about an IPA location.')
has_output = LDAPRetrieve.has_output + (
output.Output(
'servers',
type=dict,
doc=_('Servers in location'),
flags={'no_display'}, # we use customized print to CLI
),
)
def execute(self, *keys, **options):
result = super(location_show, self).execute(*keys, **options)
servers_additional_info = {}
if not options.get('raw'):
servers_name = []
dns_servers = []
weight_sum = 0
servers = self.api.Command.server_find(
in_location=keys[0], no_members=False)['result']
for server in servers:
s_name = server['cn'][0]
servers_name.append(s_name)
weight = int(server.get('ipaserviceweight', [100])[0])
weight_sum += weight
servers_additional_info[s_name] = {
'cn': server['cn'],
'ipaserviceweight': server.get(
'ipaserviceweight', [u'100']),
}
s_roles = server.get('enabled_role_servrole', ())
if s_roles:
servers_additional_info[s_name][
'enabled_role_servrole'] = s_roles
if 'DNS server' in s_roles:
dns_servers.append(s_name)
# 1. If all masters have weight == 0 then all are equally
# weighted.
# 2. If any masters have weight == 0 then they have an
# extremely small chance of being chosen, percentage is
# 0.1.
# 3. Otherwise it's percentage change is based on the sum of
# the weights of non-zero masters.
dividend = 100.0
if weight_sum != 0:
for server in servers_additional_info.values():
if int(server['ipaserviceweight'][0]) == 0:
dividend = dividend - 0.1
for server in servers_additional_info.values():
if weight_sum == 0:
val = 100 / len(servers)
elif int(server['ipaserviceweight'][0]) == 0:
val = 0.1
else:
val = (
int(server['ipaserviceweight'][0]) *
dividend / weight_sum
)
server['service_relative_weight'] = [
u'{:.1f}%'.format(val)
]
if servers_name:
result['result']['servers_server'] = servers_name
if dns_servers:
result['result']['dns_server'] = dns_servers
if not dns_servers and servers_additional_info:
self.add_message(messages.LocationWithoutDNSServer(
location=keys[0]
))
result['servers'] = servers_additional_info
return result