Browse Source

[2380] supported canceling creation of zone due to intermediate failure.

JINMEI Tatuya 12 years ago
parent
commit
974e63e707

+ 41 - 3
src/bin/loadzone/loadzone.py.in

@@ -32,6 +32,12 @@ class BadArgument(Exception):
     '''
     pass
 
+class LoadFailure(Exception):
+    '''An exception indicating failure in loading operation.
+
+    '''
+    pass
+
 def set_cmd_options(parser):
     '''Helper function to set command-line options.
 
@@ -66,7 +72,31 @@ class LoadZoneRunner:
             raise BadArgument("Invalid zone name '" + args[0] + "': " +
                               str(ex))
 
+    def __cancel_create(self):
+        '''sqlite3-only hack: delete the zone just created on load failure.
+
+        This should eventually be done via generic datasrc API, but right now
+        we don't have that interface.  Leaving the zone in this situation
+        is too bad, so we handle it with a workaround.
+
+        '''
+        if self._datasrc_type is not 'sqlite3':
+            return
+
+        import sqlite3          # we need the module only here
+        import json
+
+        # If we are here, the following should basically succeed; since
+        # this is considered a temporary workaround we don't bother to catch
+        # and recover rare failure cases.
+        dbfile = json.loads(self._datasrc_config)['database_file']
+        with sqlite3.connect(dbfile) as conn:
+            cur = conn.cursor()
+            cur.execute("DELETE FROM zones WHERE name = ?",
+                        [self._zone_name.to_text()])
+
     def _do_load(self):
+        created = False
         try:
             datasrc_client = DataSourceClient(self._datasrc_type,
                                               self._datasrc_config)
@@ -79,15 +109,23 @@ class LoadZoneRunner:
             loader.load()
             return
         except Exception as ex:
-            logger.error(LOADZONE_LOAD_ERROR, self._zone_name,
-                         self._zone_class, ex)
-            raise ex
+            # release any remaining lock held in the client/loader
+            loader, datasrc_client = None, None
+            if created:
+                self.__cancel_create()
+                logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
+                             self._zone_class)
+            raise LoadFailure(str(ex))
 
     def run(self):
         try:
             self._parse_args()
+            self._do_load()
         except BadArgument as ex:
             logger.error(LOADZONE_ARGUMENT_ERROR, ex)
+        except LoadFailure as ex:
+            logger.error(LOADZONE_LOAD_ERROR, self._zone_name,
+                         self._zone_class, ex)
         return 0
 
 if '__main__' == __name__:

+ 2 - 0
src/bin/loadzone/loadzone_messages.mes

@@ -21,3 +21,5 @@
 % LOADZONE_ZONE_CREATED Zone %1/%2 does not exist in the data source, newly created
 
 % LOADZONE_LOAD_ERROR Failed to load zone %1/%2: %3
+
+% LOADZONE_CANCEL_CREATE_ZONE Creation of new zone %1/%2 was canceled

+ 13 - 3
src/bin/loadzone/tests/loadzone_test.py

@@ -127,7 +127,7 @@ class TestLoadZoneRunner(unittest.TestCase):
         self.__common_load_setup()
         self.__runner._datasrc_config = "invalid config"
         self.__check_zone_soa(ORIG_SOA_TXT)
-        self.assertRaises(isc.datasrc.Error, self.__runner._do_load)
+        self.assertRaises(LoadFailure, self.__runner._do_load)
         self.__check_zone_soa(ORIG_SOA_TXT) # no change to the zone
 
     def test_load_fail_badzone(self):
@@ -136,7 +136,7 @@ class TestLoadZoneRunner(unittest.TestCase):
         self.__runner._zone_file = \
             LOCAL_TESTDATA_PATH + '/broken-example.org.zone'
         self.__check_zone_soa(ORIG_SOA_TXT)
-        self.assertRaises(isc.datasrc.MasterFileError, self.__runner._do_load)
+        self.assertRaises(LoadFailure, self.__runner._do_load)
         self.__check_zone_soa(ORIG_SOA_TXT)
 
     def test_load_fail_noloader(self):
@@ -145,9 +145,19 @@ class TestLoadZoneRunner(unittest.TestCase):
         self.__runner._datasrc_type = 'memory'
         self.__runner._datasrc_config = '{"type": "memory"}'
         self.__check_zone_soa(ORIG_SOA_TXT)
-        self.assertRaises(isc.datasrc.NotImplemented, self.__runner._do_load)
+        self.assertRaises(LoadFailure, self.__runner._do_load)
         self.__check_zone_soa(ORIG_SOA_TXT)
 
+    def test_load_fail_create_cancel(self):
+        '''Load attempt fails and new creation of zone is canceled'''
+        self.__common_load_setup()
+        self.__runner._zone_name = Name('example.com')
+        self.__runner._zone_file = 'no-such-file'
+        self.__check_zone_soa(None, zone_name=Name('example.com'))
+        self.assertRaises(LoadFailure, self.__runner._do_load)
+        # _do_load() should have once created the zone but then canceled it.
+        self.__check_zone_soa(None, zone_name=Name('example.com'))
+
 if __name__== "__main__":
     isc.log.resetUnitTestRootLogger()
     unittest.main()