From 820eea56fb5a2cde0784eadedf6bf5710061a534 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@kr.ethz.ch>
Date: Thu, 6 Oct 2011 09:27:16 +0200
Subject: [PATCH 1/4] install finish

Signed-off-by: Nico Schottelius <nico@kr.ethz.ch>
---
 lib/cdist/config_install.py | 2 +-
 lib/cdist/type.py           | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/lib/cdist/config_install.py b/lib/cdist/config_install.py
index 9a84c2cf..39ee79c6 100644
--- a/lib/cdist/config_install.py
+++ b/lib/cdist/config_install.py
@@ -68,7 +68,7 @@ class ConfigInstall:
      log.info("Running global explorers")
      explorers = self.path.list_global_explorers()
      if(len(explorers) == 0):
-         raise CdistError("No explorers found in", self.path.global_explorer_dir)
+         raise CdistError("No explorers found in ", self.path.global_explorer_dir)
 
      self.path.transfer_global_explorers()
      for explorer in explorers:
diff --git a/lib/cdist/type.py b/lib/cdist/type.py
index e1c5f589..e6c35ad1 100644
--- a/lib/cdist/type.py
+++ b/lib/cdist/type.py
@@ -46,6 +46,10 @@ class Type(object):
         """Check whether a type is used for installation (if not: for configuration)"""
         return os.path.isfile(os.path.join(self.path, "install"))
 
+    def explorer_dir(self):
+        """Return remote directory that holds the explorers of a type"""
+        return os.path.join(self.remote_path, "explorer")
+
     def remote_explorer_dir(self):
         """Return remote directory that holds the explorers of a type"""
         return os.path.join(self.remote_path, "explorer")

From 5b70ff5694bfa18ad745987d9aee7c32a131eeb3 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@kr.ethz.ch>
Date: Thu, 6 Oct 2011 12:07:50 +0200
Subject: [PATCH 2/4] whiteboard to file

Signed-off-by: Nico Schottelius <nico@kr.ethz.ch>
---
 doc/dev/logs/2011-10-06 | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 doc/dev/logs/2011-10-06

diff --git a/doc/dev/logs/2011-10-06 b/doc/dev/logs/2011-10-06
new file mode 100644
index 00000000..14edab51
--- /dev/null
+++ b/doc/dev/logs/2011-10-06
@@ -0,0 +1,30 @@
+GlobalExplorer
+   list_explorers()
+   list_explorers_names()
+
+   base_dir
+   __init__(name)
+   out_dir
+   env
+   name = id
+   path
+   return_code
+   return_value
+
+--------------------------------------------------------------------------------
+Exec:
+
+normal:
+
+scp /from/where $USER@$HOST:REMOTE_BASE/cdist-internal
+ssh $USER@$HOST MY_CMD_THAT_NEEDS_TO_RUN_IN_BIN_SH (including ENV)
+
+sudo:
+
+scp $USER@$HOST:REMOTE_BASE/cdist-internal
+ssh $USER@$HOST sudo MY_CMD_THAT_NEEDS_TO_RUN_IN_BIN_SH (including ENV)
+
+chroot:
+
+[sudo] cp file /chroot/THE_HOST_BASE/REMOTE_BASE/cdist-internal
+[sudo] chroot /chroot MY_CMD_THAT_NEEDS_TO_RUN_IN_BIN_SH (including ENV)

From 2ec2ab26ce95b967a82577409e42fdf745adba41 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@kr.ethz.ch>
Date: Thu, 6 Oct 2011 12:34:34 +0200
Subject: [PATCH 3/4] update path with changes from yesterday (system crash)

Signed-off-by: Nico Schottelius <nico@kr.ethz.ch>
---
 lib/cdist/path.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/cdist/path.py b/lib/cdist/path.py
index 5a2b64d2..2fcf3aea 100644
--- a/lib/cdist/path.py
+++ b/lib/cdist/path.py
@@ -117,7 +117,7 @@ class Path:
             shutil.rmtree(self.cache_dir)
         shutil.move(self.temp_dir, self.cache_dir)
 
-    
+
     def __init_out_dirs(self):
         """Initialise output directory structure"""
         os.mkdir(self.out_dir)

From 1c8a14339759aece4029e8ca8452b22bca3a9753 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@kr.ethz.ch>
Date: Thu, 6 Oct 2011 16:52:56 +0200
Subject: [PATCH 4/4] commit previously missed change

Signed-off-by: Nico Schottelius <nico@kr.ethz.ch>
---
 lib/cdist/config_install.py | 429 +++++++++++++++++-------------------
 1 file changed, 202 insertions(+), 227 deletions(-)

diff --git a/lib/cdist/config_install.py b/lib/cdist/config_install.py
index 39ee79c6..4829061b 100644
--- a/lib/cdist/config_install.py
+++ b/lib/cdist/config_install.py
@@ -31,276 +31,251 @@ import cdist.path
 
 log = logging.getLogger(__name__)
 
-CODE_HEADER                  = "#!/bin/sh -e\n"
+CODE_HEADER = "#!/bin/sh -e\n"
 
 class ConfigInstall:
- """Class to hold install and config methods"""
+    """Cdist main class to hold arbitrary data"""
 
- def __init__(self, target_host, 
-                 initial_manifest=False,
-                 remote_user="root",
-                 home=None,
-                 exec_path=sys.argv[0],
-                 debug=False):
+    def __init__(self, target_host, 
+                    initial_manifest=False,
+                    home=None,
+                    exec_path=sys.argv[0],
+                    debug=False):
 
-     self.target_host    = target_host
-     self.debug          = debug
-     self.remote_user    = remote_user
-     self.exec_path      = exec_path
+        self.target_host    = target_host
+        os.environ['target_host'] = target_host
 
-     # FIXME: broken - construct elsewhere!
-     self.remote_prefix = ["ssh", self.remote_user + "@" + self.target_host]
+        self.debug          = debug
+        self.exec_path      = exec_path
 
-     self.path = cdist.path.Path(self.target_host, 
-                     initial_manifest=initial_manifest,
-                     remote_user=self.remote_user,
-                     remote_prefix=self.remote_prefix,
-                     base_dir=home,
-                     debug=debug)
-     
-     self.objects_prepared = []
+        self.path = cdist.path.Path(self.target_host, 
+                        initial_manifest=initial_manifest,
+                        base_dir=home,
+                        debug=debug)
+        
+        self.objects_prepared = []
 
- def cleanup(self):
-     self.path.cleanup()
+    def cleanup(self):
+        self.path.cleanup()
 
- def run_global_explorers(self):
-     """Run global explorers"""
-     log.info("Running global explorers")
-     explorers = self.path.list_global_explorers()
-     if(len(explorers) == 0):
-         raise CdistError("No explorers found in ", self.path.global_explorer_dir)
+    def run_global_explores(self):
+        """Run global explorers"""
+        log.info("Running global explorers")
+        explorers = self.path.list_global_explorers()
+        if(len(explorers) == 0):
+            raise CdistError("No explorers found in", self.path.global_explorer_dir)
 
-     self.path.transfer_global_explorers()
-     for explorer in explorers:
-         output = self.path.global_explorer_output_path(explorer)
-         output_fd = open(output, mode='w')
-         cmd = []
-         cmd.append("__explorer=" + cdist.path.REMOTE_GLOBAL_EXPLORER_DIR)
-         cmd.append(self.path.remote_global_explorer_path(explorer))
+        self.path.transfer_global_explorers()
+        for explorer in explorers:
+            output = self.path.global_explorer_output_path(explorer)
+            output_fd = open(output, mode='w')
+            cmd = []
+            cmd.append("__explorer=" + cdist.path.REMOTE_GLOBAL_EXPLORER_DIR)
+            cmd.append(self.path.remote_global_explorer_path(explorer))
 
-         cdist.exec.run_or_fail(cmd, stdout=output_fd, remote_prefix=self.remote_prefix)
-         output_fd.close()
+            cdist.exec.run_or_fail(cmd, stdout=output_fd, remote_prefix=True)
+            output_fd.close()
 
-# FIXME: where to call this from?
- def run_type_explorer(self, cdist_object):
-     """Run type specific explorers for objects"""
+    def run_type_explorer(self, cdist_object):
+        """Run type specific explorers for objects"""
 
-     type = self.path.get_type_from_object(cdist_object)
-     self.path.transfer_type_explorers(type)
+        type = self.path.get_type_from_object(cdist_object)
+        self.path.transfer_type_explorers(type)
 
-     cmd = []
-     cmd.append("__explorer="        + cdist.path.REMOTE_GLOBAL_EXPLORER_DIR)
-     cmd.append("__type_explorer="   + self.path.remote_type_explorer_dir(type))
-     cmd.append("__object="          + self.path.remote_object_dir(cdist_object))
-     cmd.append("__object_id="       + self.path.get_object_id_from_object(cdist_object))
-     cmd.append("__object_fq="       + cdist_object)
+        cmd = []
+        cmd.append("__explorer="        + cdist.path.REMOTE_GLOBAL_EXPLORER_DIR)
+        cmd.append("__type_explorer="   + self.path.remote_type_explorer_dir(type))
+        cmd.append("__object="          + self.path.remote_object_dir(cdist_object))
+        cmd.append("__object_id="       + self.path.get_object_id_from_object(cdist_object))
+        cmd.append("__object_fq="       + cdist_object)
 
-     # Need to transfer at least the parameters for objects to be useful
-     self.path.transfer_object_parameter(cdist_object)
+        # Need to transfer at least the parameters for objects to be useful
+        self.path.transfer_object_parameter(cdist_object)
 
-     # FIXME: Broken due to refactoring into type.py
-     explorers = self.path.list_type_explorers(type)
-     for explorer in explorers:
-         remote_cmd = cmd + [os.path.join(self.path.remote_type_explorer_dir(type), explorer)]
-         output = os.path.join(self.path.type_explorer_output_dir(cdist_object), explorer)
-         output_fd = open(output, mode='w')
-         log.debug("%s exploring %s using %s storing to %s", 
-                         cdist_object, explorer, remote_cmd, output)
-                     
-         cdist.exec.run_or_fail(remote_cmd, stdout=output_fd, remote_prefix=self.remote_prefix)
-         output_fd.close()
+        explorers = self.path.list_type_explorers(type)
+        for explorer in explorers:
+            remote_cmd = cmd + [os.path.join(self.path.remote_type_explorer_dir(type), explorer)]
+            output = os.path.join(self.path.type_explorer_output_dir(cdist_object), explorer)
+            output_fd = open(output, mode='w')
+            log.debug("%s exploring %s using %s storing to %s", 
+                            cdist_object, explorer, remote_cmd, output)
+                        
+            cdist.exec.run_or_fail(remote_cmd, stdout=output_fd, remote_prefix=True)
+            output_fd.close()
 
- def link_emulator(self):
-     """Link emulator to types"""
-     cdist.emulator.link(self.exec_path,
-         self.path.bin_dir, self.path.list_types())
+    def link_emulator(self):
+        """Link emulator to types"""
+        cdist.emulator.link(self.exec_path,
+            self.path.bin_dir, self.path.list_types())
 
- def init_deploy(self):
-     """Ensure the base directories are cleaned up"""
-     log.debug("Creating clean directory structure")
+    def init_deploy(self):
+        """Ensure the base directories are cleaned up"""
+        log.debug("Creating clean directory structure")
 
-     self.path.remove_remote_dir(cdist.path.REMOTE_BASE_DIR)
-     self.path.remote_mkdir(cdist.path.REMOTE_BASE_DIR)
-     self.link_emulator()
+        self.path.remove_remote_dir(cdist.path.REMOTE_BASE_DIR)
+        self.path.remote_mkdir(cdist.path.REMOTE_BASE_DIR)
+        self.link_emulator()
 
- def run_initial_manifest(self):
-     """Run the initial manifest"""
-     log.info("Running initial manifest %s", self.path.initial_manifest)
-     env = {  "__manifest" : self.path.manifest_dir }
-     self.run_manifest(self.path.initial_manifest, extra_env=env)
+    def run_initial_manifest(self):
+        """Run the initial manifest"""
+        log.info("Running initial manifest %s", self.path.initial_manifest)
+        env = {  "__manifest" : self.path.manifest_dir }
+        self.run_manifest(self.path.initial_manifest, extra_env=env)
 
- def run_type_manifest(self, cdist_object):
-     """Run manifest for a specific object"""
-     type = self.path.get_type_from_object(cdist_object)
-     manifest = self.path.type_dir(type, "manifest")
-     
-     log.debug("%s: Running %s", cdist_object, manifest)
-     if os.path.exists(manifest):
-         env = { "__object" :    self.path.object_dir(cdist_object), 
-                 "__object_id":  self.path.get_object_id_from_object(cdist_object),
-                 "__object_fq":  cdist_object,
-                 "__type":       self.path.type_dir(type)
-                 }
-         self.run_manifest(manifest, extra_env=env)
+    def run_type_manifest(self, cdist_object):
+        """Run manifest for a specific object"""
+        type = self.path.get_type_from_object(cdist_object)
+        manifest = self.path.type_dir(type, "manifest")
+        
+        log.debug("%s: Running %s", cdist_object, manifest)
+        if os.path.exists(manifest):
+            env = { "__object" :    self.path.object_dir(cdist_object), 
+                    "__object_id":  self.path.get_object_id_from_object(cdist_object),
+                    "__object_fq":  cdist_object,
+                    "__type":       self.path.type_dir(type)
+                    }
+            self.run_manifest(manifest, extra_env=env)
 
- def run_manifest(self, manifest, extra_env=None):
-     """Run a manifest"""
-     log.debug("Running manifest %s, env=%s", manifest, extra_env)
-     env = os.environ.copy()
-     env['PATH'] = self.path.bin_dir + ":" + env['PATH']
+    def run_manifest(self, manifest, extra_env=None):
+        """Run a manifest"""
+        log.debug("Running manifest %s, env=%s", manifest, extra_env)
+        env = os.environ.copy()
+        env['PATH'] = self.path.bin_dir + ":" + env['PATH']
 
-     # Information required in every manifest
-     env['__target_host']            = self.target_host
-     env['__global']                 = self.path.out_dir
-     
-     # Submit debug flag to manifest, can be used by emulator and types
-     if self.debug:
-         env['__debug']                  = "yes"
+        # Information required in every manifest
+        env['__target_host']            = self.target_host
+        env['__global']                 = self.path.out_dir
+        
+        # Submit debug flag to manifest, can be used by emulator and types
+        if self.debug:
+            env['__debug']                  = "yes"
 
-     # Required for recording source
-     env['__cdist_manifest']         = manifest
+        # Required for recording source
+        env['__cdist_manifest']         = manifest
 
-     # Required to find types
-     env['__cdist_type_base_dir']    = self.path.type_base_dir
+        # Required to find types
+        env['__cdist_type_base_dir']    = self.path.type_base_dir
 
-     # Other environment stuff
-     if extra_env:
-         env.update(extra_env)
+        # Other environment stuff
+        if extra_env:
+            env.update(extra_env)
 
-     cdist.exec.shell_run_or_debug_fail(manifest, [manifest], env=env)
+        cdist.exec.shell_run_or_debug_fail(manifest, [manifest], env=env)
 
- def object_run(self, cdist_object, mode):
-     """Run gencode or code for an object"""
-     log.debug("Running %s from %s", mode, cdist_object)
-     file=os.path.join(self.path.object_dir(cdist_object), "require")
-     requirements = cdist.path.file_to_list(file)
-     type = self.path.get_type_from_object(cdist_object)
-         
-     for requirement in requirements:
-         log.debug("Object %s requires %s", cdist_object, requirement)
-         self.object_run(requirement, mode=mode)
+    def object_run(self, cdist_object, mode):
+        """Run gencode or code for an object"""
+        log.debug("Running %s from %s", mode, cdist_object)
+        file=os.path.join(self.path.object_dir(cdist_object), "require")
+        requirements = cdist.path.file_to_list(file)
+        type = self.path.get_type_from_object(cdist_object)
+            
+        for requirement in requirements:
+            log.debug("Object %s requires %s", cdist_object, requirement)
+            self.object_run(requirement, mode=mode)
 
-     #
-     # Setup env Variable:
-     # 
-     env = os.environ.copy()
-     env['__target_host']    = self.target_host
-     env['__global']         = self.path.out_dir
-     env["__object"]         = self.path.object_dir(cdist_object)
-     env["__object_id"]      = self.path.get_object_id_from_object(cdist_object)
-     env["__object_fq"]      = cdist_object
-     env["__type"]           = self.path.type_dir(type)
+        #
+        # Setup env Variable:
+        # 
+        env = os.environ.copy()
+        env['__target_host']    = self.target_host
+        env['__global']         = self.path.out_dir
+        env["__object"]         = self.path.object_dir(cdist_object)
+        env["__object_id"]      = self.path.get_object_id_from_object(cdist_object)
+        env["__object_fq"]      = cdist_object
+        env["__type"]           = self.path.type_dir(type)
 
-     if mode == "gencode":
-         paths = [
-             self.path.type_dir(type, "gencode-local"),
-             self.path.type_dir(type, "gencode-remote")
-         ]
-         for bin in paths:
-             if os.path.isfile(bin):
-                 # omit "gen" from gencode and use it for output base
-                 outfile=os.path.join(self.path.object_dir(cdist_object), 
-                     os.path.basename(bin)[3:])
+        if mode == "gencode":
+            paths = [
+                self.path.type_dir(type, "gencode-local"),
+                self.path.type_dir(type, "gencode-remote")
+            ]
+            for bin in paths:
+                if os.path.isfile(bin):
+                    # omit "gen" from gencode and use it for output base
+                    outfile=os.path.join(self.path.object_dir(cdist_object), 
+                        os.path.basename(bin)[3:])
 
-                 outfile_fd = open(outfile, "w")
+                    outfile_fd = open(outfile, "w")
 
-                 # Need to flush to ensure our write is done before stdout write
-                 # FIXME: CODE_HEADER needed in our sh -e scenario?
-                 outfile_fd.write(CODE_HEADER)
-                 outfile_fd.flush()
+                    # Need to flush to ensure our write is done before stdout write
+                    outfile_fd.write(CODE_HEADER)
+                    outfile_fd.flush()
 
-                 cdist.exec.shell_run_or_debug_fail(bin, [bin], env=env, stdout=outfile_fd)
-                 outfile_fd.close()
+                    cdist.exec.shell_run_or_debug_fail(bin, [bin], env=env, stdout=outfile_fd)
+                    outfile_fd.close()
 
-                 status = os.stat(outfile)
+                    status = os.stat(outfile)
 
-                 # Remove output if empty, else make it executable
-                 if status.st_size == len(CODE_HEADER):
-                     os.unlink(outfile)
-                 else:
-                     # Add header and make executable - identically to 0o700
-                     os.chmod(outfile, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
+                    # Remove output if empty, else make it executable
+                    if status.st_size == len(CODE_HEADER):
+                        os.unlink(outfile)
+                    else:
+                        # Add header and make executable - identically to 0o700
+                        os.chmod(outfile, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
 
-                     # Mark object as changed
-                     open(os.path.join(self.path.object_dir(cdist_object), "changed"), "w").close()
+                        # Mark object as changed
+                        open(os.path.join(self.path.object_dir(cdist_object), "changed"), "w").close()
 
 
-     if mode == "code":
-         local_dir   = self.path.object_dir(cdist_object)
-         remote_dir  = self.path.remote_object_dir(cdist_object)
+        if mode == "code":
+            local_dir   = self.path.object_dir(cdist_object)
+            remote_dir  = self.path.remote_object_dir(cdist_object)
 
-         bin = os.path.join(local_dir, "code-local")
-         if os.path.isfile(bin):
-             cdist.exec.run_or_fail([bin])
-             
+            bin = os.path.join(local_dir, "code-local")
+            if os.path.isfile(bin):
+                cdist.exec.run_or_fail([bin])
+                
 
-         local_remote_code = os.path.join(local_dir, "code-remote")
-         remote_remote_code = os.path.join(remote_dir, "code-remote")
-         if os.path.isfile(local_remote_code):
-             self.path.transfer_file(local_remote_code, remote_remote_code)
-             # FIXME: remote_prefix
-             cdist.exec.run_or_fail([remote_remote_code], remote_prefix=self.remote_prefix)
-             
- def stage_prepare(self):
-     """Do everything for a deploy, minus the actual code stage"""
-     self.init_deploy()
-     self.run_global_explorers()
-     self.run_initial_manifest()
-     
-     log.info("Running object manifests and type explorers")
+            local_remote_code = os.path.join(local_dir, "code-remote")
+            remote_remote_code = os.path.join(remote_dir, "code-remote")
+            if os.path.isfile(local_remote_code):
+                self.path.transfer_file(local_remote_code, remote_remote_code)
+                cdist.exec.run_or_fail([remote_remote_code], remote_prefix=True)
+                
+    def stage_prepare(self):
+        """Do everything for a deploy, minus the actual code stage"""
+        self.init_deploy()
+        self.run_global_explores()
+        self.run_initial_manifest()
+        
+        log.info("Running object manifests and type explorers")
 
-     old_objects = []
-     objects = self.path.list_objects()
+        old_objects = []
+        objects = self.path.list_objects()
 
-     # Continue process until no new objects are created anymore
-     while old_objects != objects:
-         old_objects = list(objects)
-         for cdist_object in objects:
-             if cdist_object in self.objects_prepared:
-                 log.debug("Skipping rerun of object %s", cdist_object)
-                 continue
-             else:
-                 # FIXME: run_type_explorer:
-                 # object can return type
-                 # type has explorers
-                 # path knows about where to save explorer output
-                 # type = self.path.objects[object].type()
-                 # self.path.types['type'].explorers()
-                 # for explorer in explorers:
-                 #  output = cdist.exec.run_debug_or_fail_shell(explorer) 
-                 #  if output:
-                 #      write_output_to(output, os.path.join(self.path.objects[object].explorer_dir(),explorer) )
-                 # 
-                 self.run_type_explorer(cdist_object)
-                 self.run_type_manifest(cdist_object)
-                 self.objects_prepared.append(cdist_object)
+        # Continue process until no new objects are created anymore
+        while old_objects != objects:
+            old_objects = list(objects)
+            for cdist_object in objects:
+                if cdist_object in self.objects_prepared:
+                    log.debug("Skipping rerun of object %s", cdist_object)
+                    continue
+                else:
+                    self.run_type_explorer(cdist_object)
+                    self.run_type_manifest(cdist_object)
+                    self.objects_prepared.append(cdist_object)
 
-         objects = self.path.list_objects()
+            objects = self.path.list_objects()
 
- def stage_run(self):
-     """The final (and real) step of deployment"""
-     log.info("Generating and executing code")
-     # Now do the final steps over the existing objects
-     for cdist_object in self.path.list_objects():
-         log.debug("Run object: %s", cdist_object)
-         self.object_run(cdist_object, mode="gencode")
-         self.object_run(cdist_object, mode="code")
+    def stage_run(self):
+        """The final (and real) step of deployment"""
+        log.info("Generating and executing code")
+        # Now do the final steps over the existing objects
+        for cdist_object in self.path.list_objects():
+            log.debug("Run object: %s", cdist_object)
+            self.object_run(cdist_object, mode="gencode")
+            self.object_run(cdist_object, mode="code")
 
- def deploy_to(self):
-     """Mimic the old deploy to: Deploy to one host"""
-     log.info("Deploying to " + self.target_host)
-     time_start = datetime.datetime.now()
+    def deploy_to(self):
+        """Mimic the old deploy to: Deploy to one host"""
+        log.info("Deploying to " + self.target_host)
+        time_start = datetime.datetime.now()
 
-     self.stage_prepare()
-     self.stage_run()
+        self.stage_prepare()
+        self.stage_run()
 
-     time_end = datetime.datetime.now()
-     duration = time_end - time_start
-     log.info("Finished run of %s in %s seconds", 
-         self.target_host,
-         duration.total_seconds())
-
- def deploy_and_cleanup(self):
-     """Do what is most often done: deploy & cleanup"""
-     self.deploy_to()
-     self.cleanup()
+        time_end = datetime.datetime.now()
+        duration = time_end - time_start
+        log.info("Finished run of %s in %s seconds", 
+            self.target_host,
+            duration.total_seconds())