On 06/03/2015 02:38 PM, Martin Babinsky wrote:
On 06/03/2015 01:34 PM, Petr Vobornik wrote:
On 06/03/2015 10:59 AM, Martin Babinsky wrote:
On 06/03/2015 10:52 AM, Martin Babinsky wrote:
On 05/26/2015 03:31 PM, Petr Vobornik wrote:
On 05/26/2015 12:19 PM, Petr Vobornik wrote:
this patch is based on top of my patch #856 and tbabej'
s 325-9.

Obsoletes Ludwig's 0006.

ipalib part of topology management

Design:
- http://www.freeipa.org/page/V4/Manage_replication_topology

https://fedorahosted.org/freeipa/ticket/4302



New version attached:
- domainlevel_show usage changed to domainlevel_get
- updated VERSION
- added more attrs to default_attributes



Hi Petr,

the commands themselves seem to work just fine. I had encountered some
quirks in the underlying topology plugin, but I will address them in a
different thread in order to keep the discussion relevant to the
reviewed patch.

I have some minor coomments below:

1.)
  IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=121
-# Last change: pvoborni - added server-find and server-show
+IPA_API_VERSION_MINOR=122
+# Last change: pvoborni - added topology management commands

Several people were touching API in the meantime so please double-check
that you have correct VERSION and regenerate API.txt

Patch rebased.


2.)

+        Str(
+            'nsds5replicatedattributelist?',
+            cli_name='replattrs',
+            label='Attributes to replicate',
+            doc=_('Attributes that are not replicated to a consumer
server '
+                  'during a fractional update. E.g.,
`(objectclass=*) '
+                  '$ EXCLUDE accountlockout memberof'),
+        ),
+        Str(
+            'nsds5replicatedattributelisttotal?',
+            cli_name='replattrstotal',
+            label=_('Attributes for total update'),
+            doc=_('Attributes that are not replicated to a consumer
server '
+                  'during a total update. E.g. (objectclass=*) $
EXCLUDE '
+                  'accountlockout'),

The descriptions of these two options confused me greatly, are these
attributes supposed to be replicated or not, or is there some more
complex logic behind them that I failed to grasp? I am cc'ing
Ludwig, he
can probably explain them to us and then we can decide whether we may
alter the descriptions to be less confusing.

3.)

+    takes_params = (
+        Str(
+            'cn',
+            cli_name='name',
+            primary_key=True,
+            label=_('Suffix name'),
+        ),
+        Str(
+            'iparepltopoconfroot',
+            maxlength=255,
+            cli_name='suffix',
+            label=_('Suffix to be managed'),
+            normalizer=lambda value: value.lower(),
+        ),
+    )

This also confused me at first, I suggest to change the label of
'iparepltopoconfroot' to something like 'LDAP suffix to be managed' or
'LDAP subtree to be managed'.

Changed to 'LDAP suffix to be managed'


4.)

There is currently no way to rename existing topology
segments/suffixes.
In the case of hosts with funky FQDN's (pointing at you, ABC lab), the
segment cn's created during replica installs are mearly impossible to
remember and it would be nice to rename them to something more
manageable. However, this is not related to core functionality and can
be a subject of a separate patch once this gets pushed.

That's all from my side.


I also forgot to ask what is the expected policy when deleting a
non-empty topology suffix. If this is not supported and you have to
first remove all segments and then the suffix itself, the
'topologysuffix-del' command should issue an error pointing the user to
correct procedure.


Do we have a use case for creation or deletion of topology suffix?
That's a good question.

Anyway, I have noticed couple more things:

1.) it seems that there some of unused imports in topology.py. Please
investigate whether all of them are really needed.

Fixed


2.)

+from ipalib.plugins.baseldap import *
+from ipalib.plugins import baseldap

I do not like that starred import at all. Either import the particular
classes you use (like e.g. in basuser.py), or just leave the second
import statetement and use the appropriate namespace
(baseldap.LDAPObject etc.).

Fixed


3.) there are couple of pep8 complaints, please try to fix them unless
it impairs readability:

./ipalib/constants.py:121:80: E501 line too long (81 > 79 characters)
./ipalib/plugins/topology.py:72:80: E501 line too long (88 > 79 characters)
./ipalib/plugins/topology.py:73:26: E131 continuation line unaligned for
hanging indent
./ipalib/plugins/topology.py:73:80: E501 line too long (93 > 79 characters)
./ipalib/plugins/topology.py:103:80: E501 line too long (80 > 79
characters)
./ipalib/plugins/topology.py:111:80: E501 line too long (80 > 79
characters)
./ipalib/plugins/topology.py:207:80: E501 line too long (80 > 79
characters)
./ipalib/plugins/topology.py:232:80: E501 line too long (80 > 79
characters)

won't fix

./ipalib/plugins/topology.py:269:80: E501 line too long (84 > 79
characters)
./ipalib/plugins/topology.py:278:80: E501 line too long (89 > 79
characters)

fixed

./ipalib/plugins/topology.py:363:80: E501 line too long (80 > 79
characters)
./ipalib/plugins/topology.py:375:80: E501 line too long (80 > 79
characters)

won't fix

--
Petr Vobornik
From b7af37921b388757b3b0ece6cbdf9f150b0beaf5 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 22 May 2015 09:50:09 +0200
Subject: [PATCH] topology: ipa management commands

ipalib part of topology management

Design:
- http://www.freeipa.org/page/V4/Manage_replication_topology

https://fedorahosted.org/freeipa/ticket/4302
---
 API.txt                    | 155 ++++++++++++++++++
 VERSION                    |   4 +-
 ipalib/constants.py        |   1 +
 ipalib/plugins/topology.py | 385 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 543 insertions(+), 2 deletions(-)
 create mode 100644 ipalib/plugins/topology.py

diff --git a/API.txt b/API.txt
index 6520f2c428342cdd30b0db830ed4ddbc89e4302a..0e42fadc66c129e53c3860fb7eeec69c1f148147 100644
--- a/API.txt
+++ b/API.txt
@@ -4494,6 +4494,161 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: topologysegment_add
+args: 2,13,3
+arg: Str('topologysuffixcn', cli_name='topologysuffix', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, primary_key=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: StrEnum('iparepltoposegmentdirection', attribute=True, cli_name='direction', default=u'both', multivalue=False, required=True, values=(u'both', u'left-right', u'right-left', u'none'))
+option: Str('iparepltoposegmentleftnode', attribute=True, cli_name='leftnode', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$', required=True)
+option: Str('iparepltoposegmentrightnode', attribute=True, cli_name='rightnode', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$', required=True)
+option: StrEnum('nsds5replicaenabled', attribute=True, cli_name='enabled', multivalue=False, required=False, values=(u'on', u'off'))
+option: Str('nsds5replicastripattrs', attribute=True, cli_name='stripattrs', multivalue=False, required=False)
+option: Str('nsds5replicatedattributelist', attribute=True, cli_name='replattrs', multivalue=False, required=False)
+option: Str('nsds5replicatedattributelisttotal', attribute=True, cli_name='replattrstotal', multivalue=False, required=False)
+option: Int('nsds5replicatimeout', attribute=True, cli_name='timeout', minvalue=0, multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: topologysegment_del
+args: 2,2,3
+arg: Str('topologysuffixcn', cli_name='topologysuffix', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=True, primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: topologysegment_find
+args: 2,15,4
+arg: Str('topologysuffixcn', cli_name='topologysuffix', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='name', maxlength=255, multivalue=False, primary_key=True, query=True, required=False)
+option: StrEnum('iparepltoposegmentdirection', attribute=True, autofill=False, cli_name='direction', default=u'both', multivalue=False, query=True, required=False, values=(u'both', u'left-right', u'right-left', u'none'))
+option: Str('iparepltoposegmentleftnode', attribute=True, autofill=False, cli_name='leftnode', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$', query=True, required=False)
+option: Str('iparepltoposegmentrightnode', attribute=True, autofill=False, cli_name='rightnode', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$', query=True, required=False)
+option: StrEnum('nsds5replicaenabled', attribute=True, autofill=False, cli_name='enabled', multivalue=False, query=True, required=False, values=(u'on', u'off'))
+option: Str('nsds5replicastripattrs', attribute=True, autofill=False, cli_name='stripattrs', multivalue=False, query=True, required=False)
+option: Str('nsds5replicatedattributelist', attribute=True, autofill=False, cli_name='replattrs', multivalue=False, query=True, required=False)
+option: Str('nsds5replicatedattributelisttotal', attribute=True, autofill=False, cli_name='replattrstotal', multivalue=False, query=True, required=False)
+option: Int('nsds5replicatimeout', attribute=True, autofill=False, cli_name='timeout', minvalue=0, multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: topologysegment_mod
+args: 2,15,3
+arg: Str('topologysuffixcn', cli_name='topologysuffix', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: StrEnum('iparepltoposegmentdirection', attribute=True, autofill=False, cli_name='direction', default=u'both', multivalue=False, required=False, values=(u'both', u'left-right', u'right-left', u'none'))
+option: Str('iparepltoposegmentleftnode', attribute=True, autofill=False, cli_name='leftnode', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$', required=False)
+option: Str('iparepltoposegmentrightnode', attribute=True, autofill=False, cli_name='rightnode', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$', required=False)
+option: StrEnum('nsds5replicaenabled', attribute=True, autofill=False, cli_name='enabled', multivalue=False, required=False, values=(u'on', u'off'))
+option: Str('nsds5replicastripattrs', attribute=True, autofill=False, cli_name='stripattrs', multivalue=False, required=False)
+option: Str('nsds5replicatedattributelist', attribute=True, autofill=False, cli_name='replattrs', multivalue=False, required=False)
+option: Str('nsds5replicatedattributelisttotal', attribute=True, autofill=False, cli_name='replattrstotal', multivalue=False, required=False)
+option: Int('nsds5replicatimeout', attribute=True, autofill=False, cli_name='timeout', minvalue=0, multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: topologysegment_refresh
+args: 2,4,3
+arg: Str('topologysuffixcn', cli_name='topologysuffix', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('left?', autofill=True, default=False)
+option: Flag('right?', autofill=True, default=False)
+option: Flag('stop?', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'bool'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: topologysegment_show
+args: 2,4,3
+arg: Str('topologysuffixcn', cli_name='topologysuffix', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: topologysuffix_add
+args: 1,6,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('iparepltopoconfroot', attribute=True, cli_name='suffix', maxlength=255, multivalue=False, required=True)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: topologysuffix_del
+args: 1,2,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=True, primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: topologysuffix_find
+args: 1,8,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='name', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('iparepltopoconfroot', attribute=True, autofill=False, cli_name='suffix', maxlength=255, multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: topologysuffix_mod
+args: 1,8,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('iparepltopoconfroot', attribute=True, autofill=False, cli_name='suffix', maxlength=255, multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: topologysuffix_show
+args: 1,4,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
 command: trust_add
 args: 1,13,3
 arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, required=True)
diff --git a/VERSION b/VERSION
index 2ad3827923bc0f404513300edc8498ed6717c571..6f6e363eb028027f789aff84256f58488d0a7964 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=123
-# Last change: rcritten - added service constraint delegation plugin
+IPA_API_VERSION_MINOR=124
+# Last change: pvoborni - added topology management commands
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 95dec54a51f38ae63eba667daacf35dcd7500cf3..75db702d1eed7b0a59f29c1d3b3ca4a47d15b090 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -118,6 +118,7 @@ DEFAULT_CONFIG = (
     ('container_radiusproxy', DN(('cn', 'radiusproxy'))),
     ('container_views', DN(('cn', 'views'), ('cn', 'accounts'))),
     ('container_masters', DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'))),
+    ('container_topology', DN(('cn', 'topology'), ('cn', 'ipa'), ('cn', 'etc'))),
 
     # Ports, hosts, and URIs:
     ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
diff --git a/ipalib/plugins/topology.py b/ipalib/plugins/topology.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba99133b4b105369b317df7eb1b8623c7f483ce8
--- /dev/null
+++ b/ipalib/plugins/topology.py
@@ -0,0 +1,385 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+from ipalib import api, errors
+from ipalib import Int, Str, Bool, StrEnum, Flag
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import (
+    LDAPObject, LDAPSearch, LDAPCreate, LDAPDelete, LDAPUpdate, LDAPQuery,
+    LDAPRetrieve)
+from ipalib import _, ngettext
+from ipalib import output
+from ipapython.dn import DN
+
+
+__doc__ = _("""
+Topology
+
+Management of a replication topology.
+
+Requires minimum domain level 1.
+""")
+
+register = Registry()
+
+MINIMUM_DOMAIN_LEVEL = 1
+
+
+def validate_domain_level(api):
+    current = int(api.Command.domainlevel_get()['result'])
+    if current < MINIMUM_DOMAIN_LEVEL:
+        raise errors.InvalidDomainLevelError(
+            _('Topology management requires minimum domain level {0} '
+              .format(MINIMUM_DOMAIN_LEVEL))
+        )
+
+
+@register()
+class topologysegment(LDAPObject):
+    """
+    Topology segment.
+    """
+    parent_object = 'topologysuffix'
+    container_dn = api.env.container_topology
+    object_name = _('segment')
+    object_name_plural = _('segments')
+    object_class = ['iparepltoposegment']
+    default_attributes = [
+        'cn',
+        'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode',
+        'ipaReplTopoSegmentLeftNode', 'nsds5replicastripattrs',
+        'nsds5replicatedattributelist', 'nsds5replicatedattributelisttotal',
+        'nsds5replicatimeout', 'nsds5replicaenabled'
+    ]
+    search_display_attributes = [
+        'cn', 'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode',
+        'ipaReplTopoSegmentLeftNode'
+    ]
+
+    label = _('Topology Segments')
+    label_singular = _('Topology Segment')
+
+    takes_params = (
+        Str(
+            'cn',
+            maxlength=255,
+            cli_name='name',
+            primary_key=True,
+            label=_('Segment name'),
+            default_from=lambda iparepltoposegmentleftnode, iparepltoposegmentrightnode:
+                         '%s-%s' % (iparepltoposegmentleftnode, iparepltoposegmentrightnode),
+            normalizer=lambda value: value.lower(),
+            doc=_('Arbitrary string identifying the segment'),
+        ),
+        Str(
+            'iparepltoposegmentleftnode',
+            pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$',
+            pattern_errmsg='may only include letters, numbers, -, . and $',
+            maxlength=255,
+            cli_name='leftnode',
+            label=_('Left node'),
+            normalizer=lambda value: value.lower(),
+            doc=_('Left replication node - an IPA server'),
+        ),
+        Str(
+            'iparepltoposegmentrightnode',
+            pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$',
+            pattern_errmsg='may only include letters, numbers, -, . and $',
+            maxlength=255,
+            cli_name='rightnode',
+            label=_('Right node'),
+            normalizer=lambda value: value.lower(),
+            doc=_('Right replication node - an IPA server'),
+        ),
+        StrEnum(
+            'iparepltoposegmentdirection',
+            cli_name='direction',
+            label=_('Connectivity'),
+            values=(u'both', u'left-right', u'right-left', u'none'),
+            default=u'both',
+            doc=_('Direction of replication between left and right replication '
+                  'node'),
+        ),
+        Str(
+            'nsds5replicastripattrs?',
+            cli_name='stripattrs',
+            label=_('Attributes to strip'),
+            normalizer=lambda value: value.lower(),
+            doc=_('A space separated list of attributes which are removed from '
+                  'replication updates.')
+        ),
+        Str(
+            'nsds5replicatedattributelist?',
+            cli_name='replattrs',
+            label='Attributes to replicate',
+            doc=_('Attributes that are not replicated to a consumer server '
+                  'during a fractional update. E.g., `(objectclass=*) '
+                  '$ EXCLUDE accountlockout memberof'),
+        ),
+        Str(
+            'nsds5replicatedattributelisttotal?',
+            cli_name='replattrstotal',
+            label=_('Attributes for total update'),
+            doc=_('Attributes that are not replicated to a consumer server '
+                  'during a total update. E.g. (objectclass=*) $ EXCLUDE '
+                  'accountlockout'),
+        ),
+        Int(
+            'nsds5replicatimeout?',
+            cli_name='timeout',
+            label=_('Session timeout'),
+            minvalue=0,
+            doc=_('Number of seconds outbound LDAP operations waits for a '
+                  'response from the remote replica before timing out and '
+                  'failing'),
+        ),
+        StrEnum(
+            'nsds5replicaenabled?',
+            cli_name='enabled',
+            label=_('Replication agreement enabled'),
+            doc=_('Whether a replication agreement is active, meaning whether '
+                  'replication is occurring per that agreement'),
+            values=(u'on', u'off'),
+        ),
+    )
+
+    def validate_nodes(self, ldap, dn, entry_attrs):
+        leftnode = entry_attrs.get('iparepltoposegmentleftnode')
+        rightnode = entry_attrs.get('iparepltoposegmentrightnode')
+
+        if not leftnode and not rightnode:
+            return  # nothing to check
+
+        # check if nodes are IPA servers
+        masters = self.api.Command.server_find('', sizelimit=0)['result']
+        m_hostnames = [master['cn'][0].lower() for master in masters]
+
+        if leftnode and leftnode not in m_hostnames:
+            raise errors.ValidationError(
+                name='leftnode',
+                error=_('left node is not a topology node: %(leftnode)s') %
+                     dict(leftnode=leftnode)
+            )
+
+        if rightnode and rightnode not in m_hostnames:
+            raise errors.ValidationError(
+                name='rightnode',
+                error=_('right node is not a topology node: %(rightnode)s') %
+                     dict(rightnode=rightnode)
+            )
+
+        # prevent creation of reflexive relation
+        key = 'leftnode'
+        if not leftnode or not rightnode:  # get missing end
+            _entry_attrs = ldap.get_entry(dn, ['*'])
+            if not leftnode:
+                key = 'rightnode'
+                leftnode = _entry_attrs['iparepltoposegmentleftnode'][0]
+            else:
+                rightnode = _entry_attrs['iparepltoposegmentrightnode'][0]
+
+        if leftnode == rightnode:
+            raise errors.ValidationError(
+                name=key,
+                error=_('left node and right node must not be the same')
+            )
+
+
+@register()
+class topologysegment_find(LDAPSearch):
+    __doc__ = _('Search for topology segments.')
+
+    msg_summary = ngettext(
+        '%(count)d segment matched',
+        '%(count)d segments matched', 0
+    )
+
+
+@register()
+class topologysegment_add(LDAPCreate):
+    __doc__ = _('Add a new segment.')
+
+    msg_summary = _('Added segment "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        validate_domain_level(self.api)
+        self.obj.validate_nodes(ldap, dn, entry_attrs)
+        return dn
+
+
+@register()
+class topologysegment_del(LDAPDelete):
+    __doc__ = _('Delete a segment.')
+
+    msg_summary = _('Deleted segment "%(value)s"')
+
+    def pre_callback(self, ldap, dn, *keys, **options):
+        assert isinstance(dn, DN)
+        validate_domain_level(self.api)
+        return dn
+
+
+@register()
+class topologysegment_mod(LDAPUpdate):
+    __doc__ = _('Modify a segment.')
+
+    msg_summary = _('Modified segment "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        validate_domain_level(self.api)
+        self.obj.validate_nodes(ldap, dn, entry_attrs)
+        return dn
+
+
+@register()
+class topologysegment_refresh(LDAPQuery):
+    __doc__ = _('Request a replication refresh of specified node.')
+
+    has_output = output.standard_value
+    msg_summary = _('%(value)s')
+
+    takes_options = (
+        Flag(
+            'left?',
+            doc=_('Initialize left node'),
+            default=False,
+        ),
+        Flag(
+            'right?',
+            doc=_('Initialize right node'),
+            default=False,
+        ),
+        Flag(
+            'stop?',
+            doc=_('Stop already started refresh of chosen node(s)'),
+            default=False,
+        ),
+    )
+
+    def execute(self, *keys, **options):
+        dn = self.obj.get_dn(*keys, **options)
+        validate_domain_level(self.api)
+
+        entry = self.obj.backend.get_entry(
+            dn, [
+                'nsds5beginreplicarefresh;left',
+                'nsds5beginreplicarefresh;right'
+            ])
+
+        left = options.get('left')
+        right = options.get('right')
+        stop = options.get('stop')
+        action = u'start'
+        msg = _('Replication refresh for segment: "%(pkey)s" requested.')
+        if stop:
+            action = u'stop'
+            msg = _('Stopping of replication refresh for segment: "'
+                    '%(pkey)s" requested.')
+
+        if not left and not right:
+            raise errors.OptionError(
+                _('at least one node has to be specified')
+            )
+
+        if left:
+            entry['nsds5beginreplicarefresh;left'] = [action]
+        if right:
+            entry['nsds5beginreplicarefresh;right'] = [action]
+
+        self.obj.backend.update_entry(entry)
+
+        msg = msg % {'pkey': keys[-1]}
+        return dict(
+            result=True,
+            value=msg,
+        )
+
+
+@register()
+class topologysegment_show(LDAPRetrieve):
+    __doc__ = _('Display a segment.')
+
+
+@register()
+class topologysuffix(LDAPObject):
+    """
+    Suffix managed by the topology plugin.
+    """
+    container_dn = api.env.container_topology
+    object_name = _('suffix')
+    object_name_plural = _('suffices')
+    object_class = ['iparepltopoconf']
+    default_attributes = ['cn', 'ipaReplTopoConfRoot']
+    search_display_attributes = ['cn', 'ipaReplTopoConfRoot']
+    label = _('Topology suffices')
+    label_singular = _('Topology suffix')
+
+    takes_params = (
+        Str(
+            'cn',
+            cli_name='name',
+            primary_key=True,
+            label=_('Suffix name'),
+        ),
+        Str(
+            'iparepltopoconfroot',
+            maxlength=255,
+            cli_name='suffix',
+            label=_('LDAP suffix to be managed'),
+            normalizer=lambda value: value.lower(),
+        ),
+    )
+
+
+@register()
+class topologysuffix_find(LDAPSearch):
+    __doc__ = _('Search for topology suffices.')
+
+    msg_summary = ngettext(
+        '%(count)d topology suffix matched',
+        '%(count)d topology suffices matched', 0
+    )
+
+
+@register()
+class topologysuffix_del(LDAPDelete):
+    __doc__ = _('Delete a topology suffix.')
+
+    msg_summary = _('Deleted topology suffix "%(value)s"')
+
+    def pre_callback(self, ldap, dn, *keys, **options):
+        assert isinstance(dn, DN)
+        validate_domain_level(self.api)
+        return dn
+
+
+@register()
+class topologysuffix_add(LDAPCreate):
+    __doc__ = _('Add a new topology suffix to be managed.')
+
+    msg_summary = _('Added topology suffix "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        validate_domain_level(self.api)
+        return dn
+
+
+@register()
+class topologysuffix_mod(LDAPUpdate):
+    __doc__ = _('Modify a topology suffix.')
+
+    msg_summary = _('Modified topology suffix "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        validate_domain_level(self.api)
+        return dn
+
+
+@register()
+class topologysuffix_show(LDAPRetrieve):
+    __doc__ = _('Show managed suffix.')
-- 
2.1.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to