summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>2015-10-28 12:20:37 +1300
committerAndrew Bartlett <abartlet@samba.org>2015-12-24 04:09:29 +0100
commitcbb93977cd0e427ba83c2f3dff31668901ad4699 (patch)
tree80562fc2ed357e1230228e8c43c64c97613b3340
parent8e6f2d923c9fc4249d162c8731fbbf5b44e87936 (diff)
downloadsamba-cbb93977cd0e427ba83c2f3dff31668901ad4699.tar.gz
samba-tool: add sites subnet subcommands
This allows you to add, remove, or shift subnets. Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Signed-off-by: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Garming Sam <garming@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
-rw-r--r--python/samba/netcmd/sites.py121
-rw-r--r--python/samba/subnets.py186
-rw-r--r--python/samba/tests/samba_tool/sites.py55
-rwxr-xr-xsource4/dsdb/tests/python/sites.py76
4 files changed, 436 insertions, 2 deletions
diff --git a/python/samba/netcmd/sites.py b/python/samba/netcmd/sites.py
index 53091a251cf..f0c792d90ee 100644
--- a/python/samba/netcmd/sites.py
+++ b/python/samba/netcmd/sites.py
@@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-from samba import sites
+from samba import sites, subnets
from samba.samdb import SamDB
import samba.getopt as options
from samba.auth import system_session
@@ -102,10 +102,127 @@ class cmd_sites_delete(Command):
self.outf.write("Site %s removed!\n" % sitename)
+class cmd_sites_subnet_create(Command):
+ """Create a new subnet."""
+ synopsis = "%prog <subnet> <site-of-subnet> [options]"
+ takes_args = ["subnetname", "site_of_subnet"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server",
+ type=str, metavar="URL", dest="H"),
+ ]
+
+ def run(self, subnetname, site_of_subnet, H=None, sambaopts=None,
+ credopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ samdb.transaction_start()
+ try:
+ subnets.create_subnet(samdb, samdb.get_config_basedn(), subnetname,
+ site_of_subnet)
+ samdb.transaction_commit()
+ except subnets.SubnetException, e:
+ samdb.transaction_cancel()
+ raise CommandError("Error while creating subnet %s: %s" %
+ (subnetname, e))
+
+ self.outf.write("Subnet %s created !\n" % subnetname)
+
+
+class cmd_sites_subnet_delete(Command):
+ """Delete an existing subnet."""
+
+ synopsis = "%prog <subnet> [options]"
+
+ takes_args = ["subnetname"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server",
+ type=str, metavar="URL", dest="H"),
+ ]
+
+ def run(self, subnetname, H=None, sambaopts=None, credopts=None,
+ versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ samdb.transaction_start()
+ try:
+ subnets.delete_subnet(samdb, samdb.get_config_basedn(), subnetname)
+ samdb.transaction_commit()
+ except subnets.SubnetException, e:
+ samdb.transaction_cancel()
+ raise CommandError("Error while removing subnet %s, error: %s" %
+ (subnetname, e))
+
+ self.outf.write("Subnet %s removed!\n" % subnetname)
+
+
+class cmd_sites_subnet_set_site(Command):
+ """Assign a subnet to a site."""
+ synopsis = "%prog <subnet> <site-of-subnet> [options]"
+ takes_args = ["subnetname", "site_of_subnet"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server",
+ type=str, metavar="URL", dest="H"),
+ ]
+
+ def run(self, subnetname, site_of_subnet, H=None, sambaopts=None,
+ credopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ samdb.transaction_start()
+ try:
+ subnets.set_subnet_site(samdb, samdb.get_config_basedn(),
+ subnetname, site_of_subnet)
+ samdb.transaction_commit()
+ except subnets.SubnetException, e:
+ samdb.transaction_cancel()
+ raise CommandError("Error assigning subnet %s to site %s: %s" %
+ (subnetname, site_of_subnet, e))
+
+ print >> self.outf, ("Subnet %s shifted to site %s" %
+ (subnet_name, site_of_subnet))
+
+
+class cmd_sites_subnet(SuperCommand):
+ """Subnet management subcommands."""
+ subcommands = {
+ "create": cmd_sites_subnet_create(),
+ "remove": cmd_sites_subnet_delete(),
+ "set-site": cmd_sites_subnet_set_site(),
+ }
class cmd_sites(SuperCommand):
"""Sites management."""
-
subcommands = {}
subcommands["create"] = cmd_sites_create()
subcommands["remove"] = cmd_sites_delete()
+ subcommands["subnet"] = cmd_sites_subnet()
diff --git a/python/samba/subnets.py b/python/samba/subnets.py
new file mode 100644
index 00000000000..e859f06e46d
--- /dev/null
+++ b/python/samba/subnets.py
@@ -0,0 +1,186 @@
+# Add/remove subnets to sites.
+#
+# Copyright (C) Catalyst.Net Ltd 2015
+# Copyright Matthieu Patou <mat@matws.net> 2011
+#
+# Catalyst.Net's contribution was written by Douglas Bagnall
+# <douglas.bagnall@catalyst.net.nz>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import ldb
+from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, LdbError
+from sites import SiteNotFoundException
+
+class SubnetException(Exception):
+ """Base element for Subnet errors"""
+ pass
+
+
+class SubnetNotFound(SubnetException):
+ """The subnet requested does not exist."""
+ pass
+
+
+class SubnetAlreadyExists(SubnetException):
+ """The subnet being added already exists."""
+ pass
+
+
+class SubnetInvalid(SubnetException):
+ """The subnet CIDR is invalid."""
+ pass
+
+
+class SiteNotFound(SubnetException):
+ """The site to be used for the subnet does not exist."""
+ pass
+
+
+def create_subnet(samdb, configDn, subnet_name, site_name):
+ """Create a subnet and associate it with a site.
+
+ :param samdb: A samdb connection
+ :param configDn: The DN of the configuration partition
+ :param subnet_name: name of the subnet to create (a CIDR range)
+ :return: None
+ :raise SubnetAlreadyExists: if the subnet to be created already exists.
+ :raise SiteNotFound: if the site does not exist.
+ """
+ ret = samdb.search(base=configDn, scope=ldb.SCOPE_SUBTREE,
+ expression='(&(objectclass=Site)(cn=%s))' %
+ ldb.binary_encode(site_name))
+ if len(ret) != 1:
+ raise SiteNotFound('A site with the name %s does not exist' %
+ site_name)
+ dn_site = ret[0].dn
+
+ if not isinstance(subnet_name, str):
+ raise SubnetInvalid("%s is not a valid subnet (not a string)" % subnet_name)
+
+ dnsubnet = ldb.Dn(samdb, "CN=Subnets,CN=Sites")
+ if dnsubnet.add_base(configDn) == False:
+ raise SubnetException("dnsubnet.add_base() failed")
+ if dnsubnet.add_child("CN=X") == False:
+ raise SubnetException("dnsubnet.add_child() failed")
+ dnsubnet.set_component(0, "CN", subnet_name)
+
+ try:
+ m = ldb.Message()
+ m.dn = dnsubnet
+ m["objectclass"] = ldb.MessageElement("subnet", FLAG_MOD_ADD,
+ "objectclass")
+ m["siteObject"] = ldb.MessageElement(str(dn_site), FLAG_MOD_ADD,
+ "siteObject")
+ samdb.add(m)
+ except ldb.LdbError as (enum, estr):
+ if enum == ldb.ERR_INVALID_DN_SYNTAX:
+ raise SubnetInvalid("%s is not a valid subnet: %s" % (subnet_name, estr))
+ elif enum == ldb.ERR_ENTRY_ALREADY_EXISTS:
+ # Subnet collisions are checked by exact match only, not
+ # overlapping range. This won't stop you creating
+ # 10.1.1.0/24 when there is already 10.1.0.0/16, or
+ # prevent you from having numerous IPv6 subnets that refer
+ # to the same range (e.g 5::0/16, 5::/16, 5:0:0::/16).
+ raise SubnetAlreadyExists('A subnet with the CIDR %s already exists'
+ % subnet_name)
+ else:
+ raise
+
+
+def delete_subnet(samdb, configDn, subnet_name):
+ """Delete a subnet.
+
+ :param samdb: A samdb connection
+ :param configDn: The DN of the configuration partition
+ :param subnet_name: Name of the subnet to delete
+ :return: None
+ :raise SubnetNotFound: if the subnet to be deleted does not exist.
+ """
+ dnsubnet = ldb.Dn(samdb, "CN=Subnets,CN=Sites")
+ if dnsubnet.add_base(configDn) == False:
+ raise SubnetException("dnsubnet.add_base() failed")
+ if dnsubnet.add_child("CN=X") == False:
+ raise SubnetException("dnsubnet.add_child() failed")
+ dnsubnet.set_component(0, "CN", subnet_name)
+
+ try:
+ ret = samdb.search(base=dnsubnet, scope=ldb.SCOPE_BASE,
+ expression="objectClass=subnet")
+ if len(ret) != 1:
+ raise SubnetNotFound('Subnet %s does not exist' % subnet_name)
+ except LdbError as (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ raise SubnetNotFound('Subnet %s does not exist' % subnet_name)
+
+ samdb.delete(dnsubnet)
+
+
+def set_subnet_site(samdb, configDn, subnet_name, site_name):
+ """Assign a subnet to a site.
+
+ This dissociates the subnet from its previous site.
+
+ :param samdb: A samdb connection
+ :param configDn: The DN of the configuration partition
+ :param subnet_name: Name of the subnet
+ :param site_name: Name of the site
+ :return: None
+ :raise SubnetNotFound: if the subnet does not exist.
+ :raise SiteNotFound: if the site does not exist.
+ """
+
+ dnsubnet = ldb.Dn(samdb, "CN=Subnets,CN=Sites")
+ if dnsubnet.add_base(configDn) == False:
+ raise SubnetException("dnsubnet.add_base() failed")
+ if dnsubnet.add_child("CN=X") == False:
+ raise SubnetException("dnsubnet.add_child() failed")
+ dnsubnet.set_component(0, "CN", subnet_name)
+
+ try:
+ ret = samdb.search(base=dnsubnet, scope=ldb.SCOPE_BASE,
+ expression="objectClass=subnet")
+ if len(ret) != 1:
+ raise SubnetNotFound('Subnet %s does not exist' % subnet_name)
+ except LdbError as (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ raise SubnetNotFound('Subnet %s does not exist' % subnet_name)
+
+ dnsite = ldb.Dn(samdb, "CN=Sites")
+ if dnsite.add_base(configDn) == False:
+ raise SubnetException("dnsites.add_base() failed")
+ if dnsite.add_child("CN=X") == False:
+ raise SubnetException("dnsites.add_child() failed")
+ dnsite.set_component(0, "CN", site_name)
+
+ dnservers = ldb.Dn(samdb, "CN=Servers")
+ dnservers.add_base(dnsite)
+
+ try:
+ ret = samdb.search(base=dnsite, scope=ldb.SCOPE_BASE,
+ expression="objectClass=site")
+ if len(ret) != 1:
+ raise SiteNotFoundException('Site %s does not exist' % site_name)
+ except LdbError as (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ raise SiteNotFoundException('Site %s does not exist' % site_name)
+
+ siteDn = str(ret[0].dn)
+
+ m = ldb.Message()
+ m.dn = dnsubnet
+ m["siteObject"] = ldb.MessageElement(siteDn, FLAG_MOD_REPLACE,
+ "siteObject")
+ samdb.modify(m)
diff --git a/python/samba/tests/samba_tool/sites.py b/python/samba/tests/samba_tool/sites.py
index 212df92cacd..81cc66d73b0 100644
--- a/python/samba/tests/samba_tool/sites.py
+++ b/python/samba/tests/samba_tool/sites.py
@@ -55,3 +55,58 @@ class SitesCmdTestCase(BaseSitesCmdTestCase):
# now delete it
self.samdb.delete(dnsite, ["tree_delete:0"])
+
+
+class SitesSubnetCmdTestCase(BaseSitesCmdTestCase):
+ def setUp(self):
+ super(SitesSubnetCmdTestCase, self).setUp()
+ self.sitename = "testsite"
+ self.sitename2 = "testsite2"
+ self.samdb.transaction_start()
+ sites.create_site(self.samdb, self.config_dn, self.sitename)
+ sites.create_site(self.samdb, self.config_dn, self.sitename2)
+ self.samdb.transaction_commit()
+
+ def tearDown(self):
+ self.samdb.transaction_start()
+ sites.delete_site(self.samdb, self.config_dn, self.sitename)
+ sites.delete_site(self.samdb, self.config_dn, self.sitename2)
+ self.samdb.transaction_commit()
+ super(SitesSubnetCmdTestCase, self).tearDown()
+
+ def test_site_subnet_create(self):
+ cidrs = (("10.9.8.0/24", self.sitename),
+ ("50.60.0.0/16", self.sitename2),
+ ("50.61.0.0/16", self.sitename2), # second subnet on the site
+ ("50.0.0.0/8", self.sitename), # overlapping subnet, other site
+ ("50.62.1.2/32", self.sitename), # single IP
+ ("aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1100/120",
+ self.sitename2),
+ )
+
+ for cidr, sitename in cidrs:
+ result, out, err = self.runsubcmd("sites", "subnet", "create",
+ cidr, sitename,
+ "-H", self.dburl,
+ self.creds_string)
+ self.assertCmdSuccess(result)
+
+ ret = self.samdb.search(base=self.config_dn,
+ scope=ldb.SCOPE_SUBTREE,
+ expression=('(&(objectclass=subnet)(cn=%s))'
+ % cidr))
+ self.assertIsNotNone(ret)
+ self.assertEqual(len(ret), 1)
+
+ dnsubnets = ldb.Dn(self.samdb,
+ "CN=Subnets,CN=Sites,%s" % self.config_dn)
+
+ for cidr, sitename in cidrs:
+ dnsubnet = ldb.Dn(self.samdb, ("Cn=%s,CN=Subnets,CN=Sites,%s" %
+ (cidr, self.config_dn)))
+
+ ret = self.samdb.search(base=dnsubnets, scope=ldb.SCOPE_ONELEVEL,
+ expression='(dn=%s)' % dnsubnet)
+ self.assertIsNotNone(ret)
+ self.assertEqual(len(ret), 1)
+ self.samdb.delete(dnsubnet, ["tree_delete:0"])
diff --git a/source4/dsdb/tests/python/sites.py b/source4/dsdb/tests/python/sites.py
index f42e7bf7e3e..6242a9dbda0 100755
--- a/source4/dsdb/tests/python/sites.py
+++ b/source4/dsdb/tests/python/sites.py
@@ -27,10 +27,12 @@ from samba.tests.subunitrun import TestProgram, SubunitOptions
import samba.getopt as options
from samba import sites
+from samba import subnets
from samba.auth import system_session
from samba.samdb import SamDB
import samba.tests
from samba.dcerpc import security
+from ldb import SCOPE_SUBTREE
parser = optparse.OptionParser(__file__ + " [options] <host>")
sambaopts = options.SambaOptions(parser)
@@ -107,5 +109,79 @@ class SimpleSitesTests(SitesBaseTests):
"Default-First-Site-Name")
+# tests for subnets
+class SimpleSubnetTests(SitesBaseTests):
+
+ def setUp(self):
+ super(SimpleSubnetTests, self).setUp()
+ self.basedn = self.ldb.get_config_basedn()
+ self.sitename = "testsite"
+ self.sitename2 = "testsite2"
+ self.ldb.transaction_start()
+ sites.create_site(self.ldb, self.basedn, self.sitename)
+ sites.create_site(self.ldb, self.basedn, self.sitename2)
+ self.ldb.transaction_commit()
+
+ def tearDown(self):
+ self.ldb.transaction_start()
+ sites.delete_site(self.ldb, self.basedn, self.sitename)
+ sites.delete_site(self.ldb, self.basedn, self.sitename2)
+ self.ldb.transaction_commit()
+ super(SimpleSubnetTests, self).tearDown()
+
+ def test_create_delete(self):
+ """Create a subnet and delete it again."""
+ basedn = self.ldb.get_config_basedn()
+ cidr = "10.11.12.0/24"
+
+ subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
+
+ self.assertRaises(subnets.SubnetAlreadyExists,
+ subnets.create_subnet, self.ldb, basedn, cidr,
+ self.sitename)
+
+ subnets.delete_subnet(self.ldb, basedn, cidr)
+
+ ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
+ expression='(&(objectclass=subnet)(cn=%s))' % cidr)
+
+ self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
+
+ def test_create_shift_delete(self):
+ """Create a subnet, shift it to another site, then delete it."""
+ basedn = self.ldb.get_config_basedn()
+ cidr = "10.11.12.0/24"
+
+ subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
+
+ subnets.set_subnet_site(self.ldb, basedn, cidr, self.sitename2)
+
+ ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
+ expression='(&(objectclass=subnet)(cn=%s))' % cidr)
+
+ sites = ret[0]['siteObject']
+ self.assertEqual(len(sites), 1)
+ self.assertEqual(sites[0],
+ 'CN=testsite2,CN=Sites,%s' % self.ldb.get_config_basedn())
+
+ self.assertRaises(subnets.SubnetAlreadyExists,
+ subnets.create_subnet, self.ldb, basedn, cidr,
+ self.sitename)
+
+ subnets.delete_subnet(self.ldb, basedn, cidr)
+
+ ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
+ expression='(&(objectclass=subnet)(cn=%s))' % cidr)
+
+ self.assertEqual(len(ret), 0, 'Failed to delete subnet %s' % cidr)
+
+ def test_delete_subnet_that_does_not_exist(self):
+ """Ensure we can't delete a site that isn't there."""
+ basedn = self.ldb.get_config_basedn()
+ cidr = "10.15.0.0/16"
+
+ self.assertRaises(subnets.SubnetNotFound,
+ subnets.delete_subnet, self.ldb, basedn, cidr)
+
TestProgram(module=__name__, opts=subunitopts)