[PATCH] samba-tool sites/subnets administration

Roel van Meer roel at 1afa.com
Mon Jun 8 01:27:30 MDT 2015


Hi list,

attached are four patches that extend the functionality of samba-tool with  
some commands for administration of sites and subnets.  After these patches,  
we have the following subcommands in the "samba-tool sites" command:

Available subcommands:
  create        - Create a new site.
  createsubnet  - Create a new subnet.
  list          - List all sites.
  listsubnets   - List all subnets.
  moveserver    - Move a server from one site to another.
  remove        - Delete an existing site.
  removesubnet  - Delete an existing subnet.

I've chosen to put the subnet subcommands under the sites command.  Another  
option would be to create a new supercommand "subnets".  I can redo the  
patches if that is preferred.

Things that are still missing are:
- There is not yet any input validation on the subnets.  Do we already
  have python code for network address validation somewhere?
- DNS entries aren't created and/or modified when changing sites.

Review and feedback would be appreciated.

Thanks,

Roel
-------------- next part --------------
>From 839565ada8c189e6045eac83b814fc835cd6b2f5 Mon Sep 17 00:00:00 2001
From: Roel van Meer <roel at 1afa.com>
Date: Thu, 4 Jun 2015 11:18:48 +0200
Subject: [PATCH 1/4] samba-tool sites: Add "list" command

This adds one command to samba-tool:

  samba-tool sites list

The command lists the configured sites in the domain.
---
 python/samba/netcmd/sites.py |   32 ++++++++++++++++++++++++++++++++
 1 files changed, 32 insertions(+), 0 deletions(-)

diff --git a/python/samba/netcmd/sites.py b/python/samba/netcmd/sites.py
index 09df55e..76c6f49 100644
--- a/python/samba/netcmd/sites.py
+++ b/python/samba/netcmd/sites.py
@@ -17,6 +17,7 @@
 #
 
 import os
+import ldb
 from samba import sites
 from samba.samdb import SamDB
 import samba.getopt as options
@@ -95,6 +96,36 @@ class cmd_sites_delete(Command):
 
         self.outf.write("Site %s removed!\n" % sitename)
 
+class cmd_sites_list(Command):
+    """List all sites."""
+
+    synopsis = "%prog [options]"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "versionopts": options.VersionOptions,
+        "credopts": options.CredentialsOptions,
+    }
+
+    def run(self, sambaopts=None, credopts=None, versionopts=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+        url =  lp.private_path("sam.ldb")
+
+        if not os.path.exists(url):
+            raise CommandError("secret database not found at %s " % url)
+        samdb = SamDB(url=url, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        res = samdb.search(samdb.get_config_basedn(), scope=ldb.SCOPE_SUBTREE,
+                           expression="(objectClass=Site)",
+                           attrs=["cn"])
+        if (len(res) == 0):
+            return
+
+        for msg in res:
+            self.outf.write("%s\n" % msg.get("cn", idx=0))
+
 
 
 class cmd_sites(SuperCommand):
@@ -103,3 +134,4 @@ class cmd_sites(SuperCommand):
     subcommands = {}
     subcommands["create"] = cmd_sites_create()
     subcommands["remove"] = cmd_sites_delete()
+    subcommands["list"] = cmd_sites_list()
-- 
1.7.1

-------------- next part --------------
>From 9f7dcb8a428ea6baf8ecce11bd7ca34219ffd6df Mon Sep 17 00:00:00 2001
From: Roel van Meer <roel at 1afa.com>
Date: Thu, 4 Jun 2015 11:47:09 +0200
Subject: [PATCH 2/4] samba-tool sites: add -H option

This adds the -H option to the sites subcommands.
Note that the explicit check for existence of the database has been
removed, since none of the other modules have it.
---
 python/samba/netcmd/sites.py |   43 +++++++++++++++++++++++++----------------
 1 files changed, 26 insertions(+), 17 deletions(-)

diff --git a/python/samba/netcmd/sites.py b/python/samba/netcmd/sites.py
index 76c6f49..3ca3595 100644
--- a/python/samba/netcmd/sites.py
+++ b/python/samba/netcmd/sites.py
@@ -25,7 +25,8 @@ from samba.auth import system_session
 from samba.netcmd import (
     Command,
     CommandError,
-    SuperCommand
+    SuperCommand,
+    Option
     )
 
 
@@ -36,20 +37,23 @@ class cmd_sites_create(Command):
 
     takes_args = ["sitename"]
 
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        ]
+
     takes_optiongroups = {
         "sambaopts": options.SambaOptions,
         "versionopts": options.VersionOptions,
         "credopts": options.CredentialsOptions,
     }
 
-    def run(self, sitename, sambaopts=None, credopts=None, versionopts=None):
+    def run(self, sitename, sambaopts=None, credopts=None, versionopts=None,
+            H=None):
         lp = sambaopts.get_loadparm()
         creds = credopts.get_credentials(lp, fallback_machine=True)
-        url =  lp.private_path("sam.ldb")
 
-        if not os.path.exists(url):
-            raise CommandError("secret database not found at %s " % url)
-        samdb = SamDB(url=url, session_info=system_session(),
+        samdb = SamDB(url=H, session_info=system_session(),
                       credentials=creds, lp=lp)
 
         samdb.transaction_start()
@@ -69,21 +73,24 @@ class cmd_sites_delete(Command):
 
     takes_args = ["sitename"]
 
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        ]
+
     takes_optiongroups = {
         "sambaopts": options.SambaOptions,
         "versionopts": options.VersionOptions,
         "credopts": options.CredentialsOptions,
     }
 
-    def run(self, sitename, sambaopts=None, credopts=None, versionopts=None):
+    def run(self, sitename, sambaopts=None, credopts=None, versionopts=None,
+            H=None):
         lp = sambaopts.get_loadparm()
         creds = credopts.get_credentials(lp, fallback_machine=True)
-        url =  lp.private_path("sam.ldb")
 
-        if not os.path.exists(url):
-            raise CommandError("secret database not found at %s " % url)
-        samdb = SamDB(url=url, session_info=system_session(),
-            credentials=creds, lp=lp)
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
 
         samdb.transaction_start()
         try:
@@ -101,20 +108,22 @@ class cmd_sites_list(Command):
 
     synopsis = "%prog [options]"
 
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        ]
+
     takes_optiongroups = {
         "sambaopts": options.SambaOptions,
         "versionopts": options.VersionOptions,
         "credopts": options.CredentialsOptions,
     }
 
-    def run(self, sambaopts=None, credopts=None, versionopts=None):
+    def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
         lp = sambaopts.get_loadparm()
         creds = credopts.get_credentials(lp, fallback_machine=True)
-        url =  lp.private_path("sam.ldb")
 
-        if not os.path.exists(url):
-            raise CommandError("secret database not found at %s " % url)
-        samdb = SamDB(url=url, session_info=system_session(),
+        samdb = SamDB(url=H, session_info=system_session(),
                       credentials=creds, lp=lp)
 
         res = samdb.search(samdb.get_config_basedn(), scope=ldb.SCOPE_SUBTREE,
-- 
1.7.1

-------------- next part --------------
>From a20c19e08afb131851736b6d37e28cb0b9ac8959 Mon Sep 17 00:00:00 2001
From: Roel van Meer <roel at 1afa.com>
Date: Thu, 4 Jun 2015 15:57:00 +0200
Subject: [PATCH 3/4] samba-tool sites: Add "moveserver" command

This adds a command that can be used to move a server from one
site to another:

  samba-tool sites moveserver <server> <current site> <new site>
---
 python/samba/netcmd/sites.py |   39 ++++++++++++++++++
 python/samba/sites.py        |   92 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 128 insertions(+), 3 deletions(-)

diff --git a/python/samba/netcmd/sites.py b/python/samba/netcmd/sites.py
index 3ca3595..82b3ba6 100644
--- a/python/samba/netcmd/sites.py
+++ b/python/samba/netcmd/sites.py
@@ -135,6 +135,44 @@ class cmd_sites_list(Command):
         for msg in res:
             self.outf.write("%s\n" % msg.get("cn", idx=0))
 
+class cmd_sites_moveserver(Command):
+    """Move a server from one site to another."""
+
+    synopsis = "%prog <server> <current site> <new site> [options]"
+
+    takes_args = ["server", "cursitename", "newsitename"]
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        ]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "versionopts": options.VersionOptions,
+        "credopts": options.CredentialsOptions,
+    }
+
+    def run(self, server, cursitename, newsitename, sambaopts=None,
+            credopts=None, versionopts=None, H=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        samdb.transaction_start()
+        try:
+            ok = sites.move_server(samdb, samdb.get_config_basedn(), server,
+                                   cursitename, newsitename)
+            samdb.transaction_commit()
+        except Exception, e:
+            samdb.transaction_cancel()
+            raise CommandError(
+                "Error while moving server %s, error: %s" % (server, str(e)))
+
+        self.outf.write("Server %s moved successfully\n" % server)
+
 
 
 class cmd_sites(SuperCommand):
@@ -144,3 +182,4 @@ class cmd_sites(SuperCommand):
     subcommands["create"] = cmd_sites_create()
     subcommands["remove"] = cmd_sites_delete()
     subcommands["list"] = cmd_sites_list()
+    subcommands["moveserver"] = cmd_sites_moveserver()
diff --git a/python/samba/sites.py b/python/samba/sites.py
index 76c57dd..6bcbdb5 100644
--- a/python/samba/sites.py
+++ b/python/samba/sites.py
@@ -15,7 +15,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-"""Manipulating sites."""
+"""Manipulating sites and subnets."""
 
 import ldb
 from ldb import FLAG_MOD_ADD
@@ -32,7 +32,7 @@ class SiteException(Exception):
 
 
 class SiteNotFoundException(SiteException):
-    """Raised when the site is not found and it's expected to exists."""
+    """Raised when the site is not found and it's expected to exist."""
 
     def __init__(self, value):
         self.value = value
@@ -41,7 +41,7 @@ class SiteNotFoundException(SiteException):
         return "SiteNotFoundException: " + self.value
 
 class SiteAlreadyExistsException(SiteException):
-    """Raised when the site is not found and it's expected not to exists."""
+    """Raised when the site is found and it's expected not to exist."""
 
     def __init__(self, value):
         self.value = value
@@ -58,6 +58,45 @@ class SiteServerNotEmptyException(SiteException):
     def __str__(self):
         return "SiteServerNotEmpty: " + self.value
 
+class ServerNotFoundException(SiteException):
+    """Raised when a server is not found and it's expected to exist."""
+
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return "ServerNotFoundException: " + self.value
+
+
+class SubnetException(Exception):
+    """Base element for Subnet errors"""
+
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return "SubnetException: " + self.value
+
+
+class SubnetNotFoundException(SubnetException):
+    """Raised when the site is not found and it's expected to exist."""
+
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return "SubnetNotFoundException: " + self.value
+
+class SubnetAlreadyExistsException(SubnetException):
+    """Raised when the subnet is found and it's expected not to exist."""
+
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return "SubnetAlreadyExists: " + self.value
+
+
 def create_site(samdb, configDn, siteName):
     """
     Create a site
@@ -123,3 +162,50 @@ def delete_site(samdb, configDn, siteName):
     samdb.delete(dnsite, ["tree_delete:0"])
 
     return True
+
+def move_server(samdb, configDn, server, cursiteName, newsiteName):
+    """
+    Move a server from one site to another
+
+    :param samdb: A samdb connection
+    :param configDn: The DN of the configuration partition
+    :param server: The name of the server to be moved
+    :param cursiteName: Name of the site the server is currently in
+    :param newsiteName: Name of the site the server should be moved to
+    :return: True upon success
+    :raise SiteNotFoundException: if either of the sites does not exist
+    :raise ServerNotFoundException: if the server is not found in the
+        specified site
+    """
+
+    dnsites = ldb.Dn(samdb, "CN=Sites,%s" % (str(configDn)))
+    dncursite = ldb.Dn(samdb, "CN=%s,CN=Sites,%s" %
+                              (cursiteName, str(configDn)))
+    dnnewsite = ldb.Dn(samdb, "CN=%s,CN=Sites,%s" %
+                              (newsiteName, str(configDn)))
+    dnservers = ldb.Dn(samdb, "CN=Servers,CN=%s,CN=Sites,%s" %
+                              (cursiteName, str(configDn)))
+    dncurserver = ldb.Dn(samdb, "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" %
+                                (server.upper(), cursiteName, str(configDn)))
+    dnnewserver = ldb.Dn(samdb, "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" %
+                                (server.upper(), newsiteName, str(configDn)))
+
+    ret = samdb.search(base=dnsites, scope=ldb.SCOPE_ONELEVEL,
+                       expression='(dn=%s)' % str(dncursite))
+    if len(ret) != 1:
+        raise SiteNotFoundException('Site %s does not exist' % cursiteName)
+
+    ret = samdb.search(base=dnsites, scope=ldb.SCOPE_ONELEVEL,
+                       expression='(dn=%s)' % str(dnnewsite))
+    if len(ret) != 1:
+        raise SiteNotFoundException('Site %s does not exist' % newsiteName)
+
+    ret = samdb.search(base=dnservers, scope=ldb.SCOPE_ONELEVEL,
+                       expression='(dn=%s)' % str(dncurserver))
+    if len(ret) != 1:
+        raise ServerNotFoundException(
+            'Server %s not found in site %s' % (server, cursiteName))
+
+    samdb.rename(dncurserver, dnnewserver)
+
+    return True
-- 
1.7.1

-------------- next part --------------
>From 370fe2cf87d1146629e3ec1e03944f9a2f25f374 Mon Sep 17 00:00:00 2001
From: Roel van Meer <roel at 1afa.com>
Date: Thu, 4 Jun 2015 16:07:03 +0200
Subject: [PATCH 4/4] samba-tool sites: Add commands for subnet administration

This adds three commands:

  samba-tool sites listsubnets
  samba-tool sites createsubnet <subnet>
  samba-tool sites removesubnet <subnet>
---
 python/samba/netcmd/sites.py |  110 ++++++++++++++++++++++++++++++++++++++++++
 python/samba/sites.py        |   60 +++++++++++++++++++++++
 2 files changed, 170 insertions(+), 0 deletions(-)

diff --git a/python/samba/netcmd/sites.py b/python/samba/netcmd/sites.py
index 82b3ba6..55dc7ea 100644
--- a/python/samba/netcmd/sites.py
+++ b/python/samba/netcmd/sites.py
@@ -173,6 +173,113 @@ class cmd_sites_moveserver(Command):
 
         self.outf.write("Server %s moved successfully\n" % server)
 
+class cmd_sites_createsubnet(Command):
+    """Create a new subnet."""
+
+    synopsis = "%prog <subnet> <site> [options]"
+
+    takes_args = ["subnet", "sitename"]
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        ]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "versionopts": options.VersionOptions,
+        "credopts": options.CredentialsOptions,
+    }
+
+    def run(self, subnet, sitename, sambaopts=None, credopts=None,
+            versionopts=None, H=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        samdb.transaction_start()
+        try:
+            ok = sites.create_subnet(samdb, samdb.get_config_basedn(),
+                                     subnet, sitename)
+            samdb.transaction_commit()
+        except Exception, e:
+            samdb.transaction_cancel()
+            raise CommandError(
+                "Error while creating subnet %s, error: %s" % (subnet, str(e)))
+
+        self.outf.write("Subnet %s created successfully\n" % subnet)
+
+class cmd_sites_deletesubnet(Command):
+    """Delete an existing subnet."""
+
+    synopsis = "%prog <subnet> [options]"
+
+    takes_args = ["subnet"]
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        ]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "versionopts": options.VersionOptions,
+        "credopts": options.CredentialsOptions,
+    }
+
+    def run(self, subnet, sambaopts=None, credopts=None, versionopts=None,
+            H=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        samdb.transaction_start()
+        try:
+            ok = sites.delete_subnet(samdb, samdb.get_config_basedn(), subnet)
+            samdb.transaction_commit()
+        except Exception, e:
+            samdb.transaction_cancel()
+            raise CommandError(
+                "Error while removing subnet %s, error: %s" % (subnet, str(e)))
+
+        self.outf.write("Deleted subnet %s\n" % subnet)
+
+class cmd_sites_listsubnets(Command):
+    """List all subnets."""
+
+    synopsis = "%prog [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        ]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "versionopts": options.VersionOptions,
+        "credopts": options.CredentialsOptions,
+    }
+
+    def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        res = samdb.search(samdb.get_config_basedn(), scope=ldb.SCOPE_SUBTREE,
+                           expression="(objectClass=Subnet)",
+                           attrs=["cn"])
+        if (len(res) == 0):
+            return
+
+        for msg in res:
+            self.outf.write("%s\n" % msg.get("cn", idx=0))
+
 
 
 class cmd_sites(SuperCommand):
@@ -183,3 +290,6 @@ class cmd_sites(SuperCommand):
     subcommands["remove"] = cmd_sites_delete()
     subcommands["list"] = cmd_sites_list()
     subcommands["moveserver"] = cmd_sites_moveserver()
+    subcommands["listsubnets"] = cmd_sites_listsubnets()
+    subcommands["createsubnet"] = cmd_sites_createsubnet()
+    subcommands["removesubnet"] = cmd_sites_deletesubnet()
diff --git a/python/samba/sites.py b/python/samba/sites.py
index 6bcbdb5..8d6d1c9 100644
--- a/python/samba/sites.py
+++ b/python/samba/sites.py
@@ -209,3 +209,63 @@ def move_server(samdb, configDn, server, cursiteName, newsiteName):
     samdb.rename(dncurserver, dnnewserver)
 
     return True
+
+def create_subnet(samdb, configDn, subnet, siteName):
+    """
+    Create a subnet
+
+    :param samdb: A samdb connection
+    :param configDn: The DN of the configuration partition
+    :param subnet: The subnet to create
+    :param siteName: Name of the site to associate this subnet with
+    :return: True upon success
+    :raise SubnetAlreadyExists: if the subnet to be created already exists.
+    """
+
+    ret = samdb.search(base=configDn, scope=ldb.SCOPE_SUBTREE,
+                       expression='(&(objectclass=Subnet)(cn=%s))' % subnet)
+    if len(ret) != 0:
+        raise SubnetAlreadyExistsException(
+            'A subnet with the prefix %s already exists' % subnet)
+
+    dnsites = ldb.Dn(samdb, "CN=Sites,%s" % (str(configDn)))
+    dnsite = ldb.Dn(samdb, "CN=%s,CN=Sites,%s" % (siteName, str(configDn)))
+
+    ret = samdb.search(base=dnsites, scope=ldb.SCOPE_ONELEVEL,
+                       expression='(dn=%s)' % str(dnsite))
+    if len(ret) != 1:
+        raise SiteNotFoundException('Site %s does not exist' % siteName)
+
+    m = ldb.Message()
+    m.dn = ldb.Dn(samdb, "CN=%s,CN=Subnets,CN=Sites,%s" %
+                         (subnet, str(configDn)))
+    m["objectclass"] = ldb.MessageElement("subnet", FLAG_MOD_ADD, "objectclass")
+    m["siteObject"] = ("CN=%s,CN=Sites,%s" % (siteName, str(configDn)))
+
+    samdb.add(m)
+
+    return True
+
+def delete_subnet(samdb, configDn, subnet):
+    """
+    Delete a subnet
+
+    :param samdb: A samdb connection
+    :param configDn: The DN of the configuration partition
+    :param subnet: The subnet to delete
+    :return: True upon success
+    :raise SubnetNotFoundException: if the site to be deleted does not exist.
+    """
+
+    dnsubnets = ldb.Dn(samdb, "CN=Subnets,CN=Sites,%s" % (str(configDn)))
+    dnsubnet = ldb.Dn(samdb, "CN=%s,CN=Subnets,CN=Sites,%s" %
+                             (subnet, str(configDn)))
+
+    ret = samdb.search(base=dnsubnets, scope=ldb.SCOPE_ONELEVEL,
+                       expression='(dn=%s)' % str(dnsubnet))
+    if len(ret) != 1:
+        raise SubnetNotFoundException('Subnet %s does not exist' % subnet)
+
+    samdb.delete(dnsubnet, ["tree_delete:0"])
+
+    return True
-- 
1.7.1



More information about the samba-technical mailing list