Repository: ambari
Updated Branches:
  refs/heads/trunk 1bf2795a4 -> c079e9066


AMBARI-8068. Ambari Server HA Phase 1: Provide backup and restore capability 
(via ncole)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/c079e906
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/c079e906
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/c079e906

Branch: refs/heads/trunk
Commit: c079e906674c274c23f4c46f5fb312ec3e97c8fc
Parents: 1bf2795
Author: Nate Cole <nc...@hortonworks.com>
Authored: Fri Nov 7 09:35:03 2014 -0500
Committer: Nate Cole <nc...@hortonworks.com>
Committed: Fri Nov 7 09:35:03 2014 -0500

----------------------------------------------------------------------
 ambari-agent/conf/unix/ambari-agent             |  28 ++-
 ambari-agent/etc/init.d/ambari-agent            |   5 +-
 .../src/main/python/ambari_agent/main.py        |  29 +++
 .../src/test/python/ambari_agent/TestMain.py    |  41 +++-
 ambari-server/sbin/ambari-server                |  10 +-
 ambari-server/src/main/python/ambari-server.py  |  37 +++-
 .../main/python/ambari_server/BackupRestore.py  | 186 +++++++++++++++++++
 .../src/test/python/TestAmbariServer.py         |  75 ++++++++
 .../src/test/python/TestBackupRestore.py        |  93 ++++++++++
 9 files changed, 498 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-agent/conf/unix/ambari-agent
----------------------------------------------------------------------
diff --git a/ambari-agent/conf/unix/ambari-agent 
b/ambari-agent/conf/unix/ambari-agent
old mode 100644
new mode 100755
index 9fa0024..9921a37
--- a/ambari-agent/conf/unix/ambari-agent
+++ b/ambari-agent/conf/unix/ambari-agent
@@ -175,7 +175,7 @@ case "$1" in
           tput bold
           echo "$AMBARI_AGENT currently not running"
           tput sgr0
-          echo "Usage: /usr/sbin/ambari-agent {start|stop|restart|status}"
+          echo "Usage: /usr/sbin/ambari-agent {start|stop|restart|status|reset 
<server_hostname>}"
           retcode=3
         fi
         ;;
@@ -212,9 +212,33 @@ case "$1" in
         $0 start "$@"
         retcode=$?
         ;;
+  reset)
+          if [ "$#" -ne 2 ]; then
+            echo "You must supply the hostname of the Ambari Server with the 
restore option. (e.g. ambari-agent reset c6401.ambari.apache.org)"
+            exit 1
+          fi
+          if [ -f $PIDFILE ]; then
+            echo "$AMBARI_AGENT is running. You must stop it before using 
reset."
+            exit 1
+          fi
+          echo -e "Resetting $AMBARI_AGENT"
+          $PYTHON $AGENT_SCRIPT reset $2
+          retcode=$?
+
+          if [ $retcode -eq 0 ]; then
+            tput bold
+            echo "$AMBARI_AGENT has been reset successfully. Changed Ambari 
Server hostname to $2 and certificates were cleared."
+            tput sgr0
+          else
+            tput bold
+            echo "$AMBARI_AGENT could not be reset."
+            tput sgr0
+          fi
+          ;;
+
   *)
         tput bold
-        echo "Usage: /usr/sbin/ambari-agent {start|stop|restart|status}"
+        echo "Usage: /usr/sbin/ambari-agent {start|stop|restart|status|reset 
<server_hostname>}"
         tput sgr0
         retcode=1
 esac

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-agent/etc/init.d/ambari-agent
----------------------------------------------------------------------
diff --git a/ambari-agent/etc/init.d/ambari-agent 
b/ambari-agent/etc/init.d/ambari-agent
index 898b07e..994ca77 100644
--- a/ambari-agent/etc/init.d/ambari-agent
+++ b/ambari-agent/etc/init.d/ambari-agent
@@ -34,8 +34,11 @@ case "$1" in
         $0 stop
         $0 start
         ;;
+  reset)
+        /usr/sbin/ambari-agent $@
+        ;;
   *)
-        echo "Usage: $0 {start|stop|status|restart}"
+        echo "Usage: $0 {start|stop|status|restart|reset <server_hostname>}"
         exit 1
 esac
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-agent/src/main/python/ambari_agent/main.py
----------------------------------------------------------------------
diff --git a/ambari-agent/src/main/python/ambari_agent/main.py 
b/ambari-agent/src/main/python/ambari_agent/main.py
index 5d33ca4..622e86f 100644
--- a/ambari-agent/src/main/python/ambari_agent/main.py
+++ b/ambari-agent/src/main/python/ambari_agent/main.py
@@ -180,6 +180,32 @@ def stop_agent():
       os.kill(pid, signal.SIGKILL)
     os._exit(1)
 
+def reset_agent(options):
+  try:
+    # update agent config file
+    agent_config = ConfigParser.ConfigParser()
+    agent_config.read(configFile)
+    server_host = agent_config.get('server', 'hostname')
+    new_host = options[2]
+    if new_host is not None and server_host != new_host:
+      print "Updating server host from " + server_host + " to " + new_host
+      agent_config.set('server', 'hostname', new_host)
+      with (open(configFile, "wb")) as new_agent_config:
+        agent_config.write(new_agent_config)
+
+    # clear agent certs
+    agent_keysdir = agent_config.get('security', 'keysdir')
+    print "Removing Agent certificates..."
+    for root, dirs, files in os.walk(agent_keysdir, topdown=False):
+      for name in files:
+        os.remove(os.path.join(root, name))
+      for name in dirs:
+        os.rmdir(os.path.join(root, name))
+  except Exception, err:
+    print("A problem occurred while trying to reset the agent: " + str(err))
+    os._exit(1)
+
+  os._exit(0)
 
 def main():
   global config
@@ -201,6 +227,9 @@ def main():
   if (len(sys.argv) > 1) and sys.argv[1] == 'stop':
     stop_agent()
 
+  if (len(sys.argv) > 2) and sys.argv[1] == 'reset':
+    reset_agent(sys.argv)
+
   # Check for ambari configuration file.
   config = resolve_ambari_config()
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-agent/src/test/python/ambari_agent/TestMain.py
----------------------------------------------------------------------
diff --git a/ambari-agent/src/test/python/ambari_agent/TestMain.py 
b/ambari-agent/src/test/python/ambari_agent/TestMain.py
index 7b1a8c8..f930c4a 100644
--- a/ambari-agent/src/test/python/ambari_agent/TestMain.py
+++ b/ambari-agent/src/test/python/ambari_agent/TestMain.py
@@ -25,8 +25,9 @@ import signal
 import os
 import socket
 import tempfile
+import ConfigParser
 
-from mock.mock import MagicMock, patch, ANY
+from mock.mock import MagicMock, patch, ANY, Mock
 
 with patch("platform.linux_distribution", return_value = 
('Suse','11','Final')):
   from ambari_agent import NetUtil, security
@@ -225,6 +226,44 @@ class TestMain(unittest.TestCase):
     ProcessHelper.pidfile = oldpid
     os.remove(tmpoutfile)
 
+  @patch("os.rmdir")
+  @patch("os.path.join")
+  @patch('__builtin__.open')
+  @patch.object(ConfigParser, "ConfigParser")
+  @patch("os._exit")
+  @patch("os.walk")
+  @patch("os.remove")
+  def test_reset(self, os_remove_mock, os_walk_mock, os_exit_mock, 
config_parser_mock, open_mock, os_path_join_mock, os_rmdir_mock):
+    # Agent config update
+    config_mock = MagicMock()
+    os_walk_mock.return_value = [('/', ('',), ('file1.txt', 'file2.txt'))]
+    config_parser_mock.return_value= config_mock
+    config_mock.get('server', 'hostname').return_value = "old_host"
+    main.reset_agent(["test", "reset", "new_hostname"])
+    self.assertEqual(config_mock.get.call_count, 3)
+    self.assertEqual(config_mock.set.call_count, 1)
+    self.assertEqual(os_remove_mock.call_count, 2)
+
+  @patch("os.rmdir")
+  @patch("os.path.join")
+  @patch('__builtin__.open')
+  @patch.object(ConfigParser, "ConfigParser")
+  @patch("os._exit")
+  @patch("os.walk")
+  @patch("os.remove")
+  def test_reset_invalid_path(self, os_remove_mock, os_walk_mock, 
os_exit_mock, config_parser_mock, open_mock, os_path_join_mock, os_rmdir_mock):
+    # Agent config file cannot be accessed
+    config_mock = MagicMock()
+    os_walk_mock.return_value = [('/', ('',), ('file1.txt', 'file2.txt'))]
+    config_parser_mock.return_value= config_mock
+    config_mock.get('server', 'hostname').return_value = "old_host"
+    open_mock.side_effect = Exception("Invalid Path!")
+    try:
+      main.reset_agent(["test", "reset", "new_hostname"])
+      self.fail("Should have thrown exception!")
+    except:
+      self.assertTrue(True)
+
 
   @patch.object(socket, "gethostbyname")
   @patch.object(main, "setup_logging")

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-server/sbin/ambari-server
----------------------------------------------------------------------
diff --git a/ambari-server/sbin/ambari-server b/ambari-server/sbin/ambari-server
old mode 100644
new mode 100755
index d4619e9..c034f5d
--- a/ambari-server/sbin/ambari-server
+++ b/ambari-server/sbin/ambari-server
@@ -127,9 +127,17 @@ case "$1" in
         echo -e "Refreshing stack hashes..."
         $PYTHON /usr/sbin/ambari-server.py $@
         ;;
+  backup)
+        echo -e "Backing up Ambari File System state... *this will not backup 
the server database*"
+        $PYTHON /usr/sbin/ambari-server.py $@
+        ;;
+  restore)
+        echo -e "Restoring Ambari File System state"
+        $PYTHON /usr/sbin/ambari-server.py $@
+        ;;
   *)
         echo "Usage: /usr/sbin/ambari-server
-        
{start|stop|restart|setup|upgrade|status|upgradestack|setup-ldap|sync-ldap|setup-security|refresh-stack-hash}
 [options]
+        
{start|stop|restart|setup|upgrade|status|upgradestack|setup-ldap|sync-ldap|setup-security|refresh-stack-hash|backup|restore}
 [options]
         Use usr/sbin/ambari-server <action> --help to get details on options 
available.
         Or, simply invoke ambari-server.py --help to print the options."
         exit 1

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-server/src/main/python/ambari-server.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/python/ambari-server.py 
b/ambari-server/src/main/python/ambari-server.py
index b9eaae3..cf70f48 100755
--- a/ambari-server/src/main/python/ambari-server.py
+++ b/ambari-server/src/main/python/ambari-server.py
@@ -44,7 +44,7 @@ import json
 import base64
 from threading import Thread
 from ambari_commons import OSCheck, OSConst, Firewall
-from ambari_server import utils
+from ambari_server import utils, BackupRestore
 
 # debug settings
 VERBOSE = False
@@ -78,6 +78,8 @@ SETUP_NAGIOS_HTTPS_ACTION = "setup-nagios-https"
 ENCRYPT_PASSWORDS_ACTION = "encrypt-passwords"
 SETUP_SECURITY_ACTION = "setup-security"
 REFRESH_STACK_HASH_ACTION = "refresh-stack-hash"
+BACKUP_ACTION = "backup"
+RESTORE_ACTION = "restore"
 
 ACTION_REQUIRE_RESTART = [RESET_ACTION, UPGRADE_ACTION, UPGRADE_STACK_ACTION,
                           SETUP_SECURITY_ACTION, LDAP_SETUP_ACTION]
@@ -4387,6 +4389,25 @@ def refresh_stack_hash():
                                                 resources_location, str(ex))
     raise FatalException(-1, msg)
 
+def backup(path):
+  print "Backup requested."
+  if path is None:
+    backup_command = [BackupRestore, 'backup']
+  else:
+    backup_command = [BackupRestore, 'backup', path]
+
+
+  BackupRestore.main(backup_command)
+
+def restore(path):
+  print "Restore requested."
+  if path is None:
+    restore_command = [BackupRestore, 'restore']
+  else:
+    restore_command = [BackupRestore, 'restore', path]
+
+
+  BackupRestore.main(restore_command)
 
 #
 # Main.
@@ -4547,6 +4568,8 @@ def main():
 
   if action == UPGRADE_STACK_ACTION:
     possible_args_numbers = [2,4] # OR
+  elif action == BACKUP_ACTION or action == RESTORE_ACTION:
+    possible_args_numbers = [1,2]
   else:
     possible_args_numbers = [1]
 
@@ -4593,6 +4616,18 @@ def main():
       need_restart = setup_security(options)
     elif action == REFRESH_STACK_HASH_ACTION:
       refresh_stack_hash()
+    elif action == BACKUP_ACTION:
+      if len(args) == 2:
+        path = args[1]
+      else:
+        path = None
+      backup(path)
+    elif action == RESTORE_ACTION:
+      if len(args) == 2:
+        path = args[1]
+      else:
+        path = None
+      restore(path)
     else:
       parser.error("Invalid action")
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-server/src/main/python/ambari_server/BackupRestore.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/python/ambari_server/BackupRestore.py 
b/ambari-server/src/main/python/ambari_server/BackupRestore.py
new file mode 100644
index 0000000..6c6afd2
--- /dev/null
+++ b/ambari-server/src/main/python/ambari_server/BackupRestore.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env ambari-python-wrap
+
+'''
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
+
+import sys
+import zipfile
+import os
+
+# Default values are hardcoded here
+BACKUP_PROCESS = 'backup'
+RESTORE_PROCESS = 'restore'
+SUPPORTED_PROCESSES = [BACKUP_PROCESS, RESTORE_PROCESS]
+
+# The list of files where the ambari server state is kept on the filesystem
+AMBARI_FILESYSTEM_STATE = ["/etc/ambari-server/conf",
+                           "/var/lib/ambari-server/resources",
+                           "/var/run/ambari-server/bootstrap/",
+                           "/var/run/ambari-server/stack-recommendations"]
+
+# What to use when no path/archive is specified
+DEFAULT_ARCHIVE = "/var/lib/ambari-server/Ambari_State_Backup.zip"
+
+# Responsible for managing the Backup/Restore process
+class BackupRestore:
+  def __init__(self, state_file_list, zipname, zip_folder_path):
+    """
+    Zip file creator
+    :param state_file_list: the list of files where the Ambari State is kept 
on the filesystem
+    :param zipname: the name of the archive to use
+    :param zip_folder_path: the path of the archive
+    :return:
+    """
+    self.state_file_list = state_file_list
+    self.zipname = zipname
+    self.zip_folder_path = zip_folder_path
+
+  def perform_backup(self):
+    """
+    Used to perform the actual backup, by creating the zip archive
+    :return:
+    """
+    try:
+      print("Creating zip file...")
+      zipf = zipfile.ZipFile(self.zip_folder_path + self.zipname, 'w')
+      zipdir(zipf, self.state_file_list, self.zipname)
+    except Exception, e:
+      sys.exit("Could not create zip file. Details: " + str(e))
+
+    print("Zip file created at " + self.zip_folder_path + self.zipname)
+
+  def perform_restore(self):
+    """
+    Used to perform the restore process
+    :return:
+    """
+    try:
+      print("Extracting the archive " + self.zip_folder_path + self.zipname)
+      unzip(self.zip_folder_path + self.zipname, '/')
+    except Exception, e:
+      sys.exit("Could not extract the zipfile " + self.zip_folder_path + 
self.zipname
+               + " Details: " + str(e))
+
+
+def unzip(source_filename, dest_dir):
+  """
+  Zip archive extractor
+  :param source_filename: the absolute path of the file to unzip
+  :param dest_dir: the destination of the zip content
+  :return:
+  """
+  zf = zipfile.ZipFile(source_filename)
+  try:
+    zf.extractall(dest_dir)
+  except Exception, e:
+    print("A problem occurred while unzipping. Details: " + str(e))
+    raise e
+  finally:
+    zf.close()
+
+
+def zipdir(zipf, state_file_list, zipname):
+  """
+  Used to archive the specified directory
+  :param zipf: the zipfile
+  :param state_file_list: the file list to archive
+  :param zipname: the name of the zip
+  :return:
+  """
+  try:
+    for path in state_file_list:
+      for root, dirs, files in os.walk(path):
+        for file in files:
+          if not file == zipname:
+            zipf.write(os.path.join(root, file))
+  except Exception, e:
+    print("A problem occurred while unzipping. Details: " + str(e))
+    raise e
+  finally:
+    zipf.close()
+
+def print_usage():
+  """
+  Usage instructions
+  :return:
+  """
+  print("Usage: python BackupRestore.py <processType> 
[zip-folder-path|zip-file-path]\n\n"
+        + "    processType - backup : backs up the filesystem state of the 
Ambari server into a zip file\n"
+        + "    processType - restore : restores the filesystem state of the 
Ambari server\n"
+        + "    [zip-folder-path] used with backup specifies the path of the 
folder where the zip file to be created\n"
+        + "    [zip-folder-path] used with restore specifies the path of the 
Ambari folder where the zip file to restore from is located\n")
+
+
+def validate_folders(folders):
+  """
+  Used to validate folder existence on the machine
+  :param folders: folder list containing paths to validate
+  :return:
+  """
+  for folder in folders:
+    if not os.path.isdir(folder):
+      sys.exit("Error while validating folders. Folder " + folder + " does not 
exist.")
+
+def retrieve_path_and_zipname(archive_absolute_path):
+  target = {'path': None , 'zipname': None}
+  try:
+    elements = archive_absolute_path.split("/")
+    if elements is not None and len(elements)>0:
+      target['zipname'] = elements[len(elements)-1]
+      target['path'] = 
archive_absolute_path.replace(elements[len(elements)-1], "")
+  except Exception, e:
+    sys.exit("Could not retrieve path and zipname from the absolute path " + 
archive_absolute_path + ". Please check arguments."
+             + " Details: " + str(e))
+
+  return target
+
+def main(argv=None):
+  # Arg checks
+  if len(argv) != 3 and len(argv) != 2:
+    print_usage()
+    sys.exit("Invalid usage.")
+  else:
+    process_type = argv[1]
+    if not (SUPPORTED_PROCESSES.__contains__(process_type)):
+      sys.exit("Unsupported process type: " + process_type)
+    # if no archive is specified
+    if len(argv) == 2:
+      print "No path specified. Will use " + DEFAULT_ARCHIVE
+      location_data = retrieve_path_and_zipname(DEFAULT_ARCHIVE)
+    else:
+      location_data = retrieve_path_and_zipname(argv[2])
+
+    validate_folders([location_data['path']])
+    zip_file_path = location_data['path']
+    ambari_backup_zip_filename = location_data['zipname']
+
+  backup_restore = BackupRestore(AMBARI_FILESYSTEM_STATE, 
ambari_backup_zip_filename, zip_file_path)
+
+  print(process_type.title() + " process initiated.")
+  if process_type == BACKUP_PROCESS:
+    validate_folders(AMBARI_FILESYSTEM_STATE)
+    backup_restore.perform_backup()
+    print(BACKUP_PROCESS.title() + " complete.")
+  if process_type == RESTORE_PROCESS:
+    backup_restore.perform_restore()
+    print(RESTORE_PROCESS.title() + " complete.")
+
+
+if __name__ == '__main__':
+  main(sys.argv)
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-server/src/test/python/TestAmbariServer.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/python/TestAmbariServer.py 
b/ambari-server/src/test/python/TestAmbariServer.py
index 48820b4..0641929 100644
--- a/ambari-server/src/test/python/TestAmbariServer.py
+++ b/ambari-server/src/test/python/TestAmbariServer.py
@@ -32,6 +32,7 @@ import platform
 import shutil
 from pwd import getpwnam
 from ambari_server.resourceFilesKeeper import ResourceFilesKeeper, 
KeeperException
+from ambari_server import BackupRestore
  
 # We have to use this import HACK because the filename contains a dash
 from ambari_commons import Firewall, OSCheck, OSConst, FirewallChecks
@@ -400,6 +401,61 @@ class TestAmbariServer(TestCase):
 
     self.assertTrue(ambari_server.SERVER_DEBUG_MODE)
 
+  @patch.object(ambari_server, 'setup')
+  @patch.object(ambari_server, 'start')
+  @patch.object(ambari_server, 'stop')
+  @patch.object(ambari_server, 'reset')
+  @patch.object(ambari_server, 'backup')
+  @patch.object(ambari_server, 'restore')
+  @patch('optparse.OptionParser')
+  def test_main_test_backup(self, OptionParserMock, restore_mock, backup_mock, 
reset_method, stop_method,
+                           start_method, setup_method):
+    opm = OptionParserMock.return_value
+    options = MagicMock()
+    args = ["backup"]
+    opm.parse_args.return_value = (options, args)
+
+    options.dbms = None
+    options.sid_or_sname = "sname"
+    ambari_server.main()
+
+    self.assertTrue(backup_mock.called)
+    self.assertFalse(restore_mock.called)
+    self.assertFalse(setup_method.called)
+    self.assertFalse(start_method.called)
+    self.assertFalse(stop_method.called)
+    self.assertFalse(reset_method.called)
+
+    self.assertFalse(False, ambari_server.VERBOSE)
+    self.assertFalse(False, ambari_server.SILENT)
+
+  @patch.object(ambari_server, 'setup')
+  @patch.object(ambari_server, 'start')
+  @patch.object(ambari_server, 'stop')
+  @patch.object(ambari_server, 'reset')
+  @patch.object(ambari_server, 'backup')
+  @patch.object(ambari_server, 'restore')
+  @patch('optparse.OptionParser')
+  def test_main_test_restore(self, OptionParserMock, restore_mock, 
backup_mock, reset_method, stop_method,
+                            start_method, setup_method):
+    opm = OptionParserMock.return_value
+    options = MagicMock()
+    args = ["restore"]
+    opm.parse_args.return_value = (options, args)
+
+    options.dbms = None
+    options.sid_or_sname = "sname"
+    ambari_server.main()
+
+    self.assertTrue(restore_mock.called)
+    self.assertFalse(backup_mock.called)
+    self.assertFalse(setup_method.called)
+    self.assertFalse(start_method.called)
+    self.assertFalse(stop_method.called)
+    self.assertFalse(reset_method.called)
+
+    self.assertFalse(False, ambari_server.VERBOSE)
+    self.assertFalse(False, ambari_server.SILENT)
 
   @patch.object(ambari_server, 'setup')
   @patch.object(ambari_server, 'start')
@@ -2941,6 +2997,25 @@ 
MIIFHjCCAwYCCQDpHKOBI+Lt0zANBgkqhkiG9w0BAQUFADBRMQswCQYDVQQGEwJV
     self.assertTrue(killMock.called)
     self.assertTrue(removeMock.called)
 
+  @patch.object(BackupRestore, "main")
+  def test_backup(self, bkrestore_mock):
+    ambari_server.backup("/some/path/file.zip")
+    self.assertTrue(bkrestore_mock.called)
+
+  @patch.object(BackupRestore, "main")
+  def test_backup_no_path(self, bkrestore_mock):
+    ambari_server.backup(None)
+    self.assertTrue(bkrestore_mock.called)
+
+  @patch.object(BackupRestore, "main")
+  def test_restore(self, bkrestore_mock):
+    ambari_server.restore("/some/path/file.zip")
+    self.assertTrue(bkrestore_mock.called)
+
+  @patch.object(BackupRestore, "main")
+  def test_restore_no_path(self, bkrestore_mock):
+    ambari_server.restore(None)
+    self.assertTrue(bkrestore_mock.called)
 
   @patch.object(ambari_server, "is_root")
   @patch.object(ambari_server, "check_database_name_property")

http://git-wip-us.apache.org/repos/asf/ambari/blob/c079e906/ambari-server/src/test/python/TestBackupRestore.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/python/TestBackupRestore.py 
b/ambari-server/src/test/python/TestBackupRestore.py
new file mode 100644
index 0000000..8a377ec
--- /dev/null
+++ b/ambari-server/src/test/python/TestBackupRestore.py
@@ -0,0 +1,93 @@
+'''
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
+
+
+import unittest
+
+import mock
+from mock.mock import MagicMock, patch
+from ambari_server import BackupRestore
+
+
+class TestBKRestore(unittest.TestCase):
+
+  @patch('os.walk')
+  @patch('zipfile.ZipFile')
+  @patch.object(BackupRestore, "zipdir")
+  def test_perform_backup(self, zipdir_mock, zipfile_mock, oswalk_mock):
+    #set up the mocks
+    files_mock = MagicMock()
+    files_mock.__iter__.return_value = ['file1', 'file2']
+    path_mock =  MagicMock()
+    zipname_mock = MagicMock()
+
+
+    bkRestore = BackupRestore.BackupRestore(files_mock, zipname_mock, 
path_mock)
+    bkRestore.perform_backup()
+
+    self.assertEqual(zipfile_mock.call_count, 1)
+    self.assertEqual(zipdir_mock.call_count, 1)
+
+    zipfile_mock.side_effect = Exception('Invalid path!')
+    try:
+      bkRestore.perform_backup()
+      self.fail("should throw exception")
+    except:
+      self.assertTrue(True)
+
+
+  @patch('zipfile.ZipFile')
+  @patch.object(BackupRestore, "unzip")
+  def test_perform_restore(self, unzip_mock, zipfile_mock):
+    #set up the mocks
+    path_mock =  MagicMock()
+    zipname_mock = MagicMock()
+    files_mock = MagicMock()
+    files_mock.__iter__.return_value = ['file1', 'file2']
+
+
+    bkRestore = BackupRestore.BackupRestore(files_mock, zipname_mock, 
path_mock)
+    bkRestore.perform_restore()
+
+    self.assertEqual(unzip_mock.call_count, 1)
+
+    unzip_mock.side_effect = Exception('Invalid path!')
+    try:
+      bkRestore.perform_restore()
+      self.fail("should throw exception")
+    except:
+      self.assertTrue(True)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Reply via email to