Check that astute.yaml password is valid before backup

This commit adds astute.yaml password validation in backup procedure. If
we have a valid password we don't need ask a user about it and could use
astute.yaml "as is" without fixing password there.

Change-Id: I0dd79ec624159dd1132ca06a86b35d80941c8c39
Partial-Bug: #1606613
This commit is contained in:
Nikita Zubkov 2016-09-07 18:45:10 +03:00
parent f9d9919da5
commit d678e14535
9 changed files with 74 additions and 76 deletions

View File

@ -14,11 +14,13 @@ import shutil
import yaml
from octane.handlers.backup_restore import base
from octane import magic_consts as consts
from octane.util import auth
from octane.util import puppet
class AstuteArchivator(base.PathArchivator):
path = "/etc/fuel/astute.yaml"
path = consts.ASTUTE_YAML
name = "astute/astute.yaml"
keys_to_restore = [
@ -59,6 +61,15 @@ class AstuteArchivator(base.PathArchivator):
("FUEL_ACCESS", ["user", "password"]),
]
def backup(self):
super(AstuteArchivator, self).backup()
creds = self.get_backup_dict()['FUEL_ACCESS']
if not auth.is_creds_valid(creds['user'], creds['password']):
raise Exception(
"astute.yaml file contains invalid Fuel username or password"
)
def get_backup_dict(self):
return yaml.load(self.archive.extractfile(self.name))
@ -67,13 +78,24 @@ class AstuteArchivator(base.PathArchivator):
return yaml.load(current)
def pre_restore_check(self):
backup_ip = self.get_backup_dict()["ADMIN_NETWORK"]["ipaddress"]
current_ip = self.get_current_dict()["ADMIN_NETWORK"]["ipaddress"]
backup = self.get_backup_dict()
current = self.get_current_dict()
backup_ip = backup["ADMIN_NETWORK"]["ipaddress"]
current_ip = current["ADMIN_NETWORK"]["ipaddress"]
if backup_ip != current_ip:
raise Exception(
"Restore allowed on machine with same ipaddress. "
"Use fuel-menu to set up ipaddress to {0}".format(backup_ip))
creds = backup.get('FUEL_ACCESS')
if creds and not auth.is_creds_valid(creds['user'], creds['password']):
raise Exception(
"Backup's astute.yaml file contains invalid"
" Fuel username or password"
)
def restore(self):
backup_yaml = self.get_backup_dict()
current_yaml = self.get_current_dict()

View File

@ -15,7 +15,6 @@ import six
from octane.handlers.backup_restore import base
from octane import magic_consts
from octane.util import auth
from octane.util import keystone
from octane.util import patch
from octane.util import puppet
@ -44,8 +43,8 @@ class PostgresArchivator(base.CmdArchivator):
with subprocess.popen(["sudo", "-u", "postgres", "psql"],
stdin=subprocess.PIPE) as process:
shutil.copyfileobj(dump, process.stdin)
with auth.set_astute_password(self.context):
puppet.apply_task(self.db)
puppet.apply_task(self.db)
class NailgunArchivator(PostgresArchivator):

View File

@ -12,7 +12,6 @@
from octane.handlers.backup_restore import base
from octane import magic_consts
from octane.util import auth
from octane.util import keystone
from octane.util import puppet
from octane.util import subprocess
@ -33,7 +32,6 @@ class PuppetApplyTasks(base.Base):
def restore(self):
subprocess.call(["systemctl", "stop"] + self.services)
with auth.set_astute_password(self.context), \
keystone.admin_token_auth(magic_consts.KEYSTONE_PASTE,
magic_consts.KEYSTONE_PIPELINES):
with keystone.admin_token_auth(magic_consts.KEYSTONE_PASTE,
magic_consts.KEYSTONE_PIPELINES):
puppet.apply_all_tasks()

View File

@ -162,3 +162,5 @@ COMPUTE_PREUPGRADE_PACKAGES = {
"python-concurrent.futures",
]
}
ASTUTE_YAML = "/etc/fuel/astute.yaml"

View File

@ -39,6 +39,11 @@ from octane.handlers.backup_restore import version
(ssh.SshArchivator, "/root/.ssh/", "ssh"),
])
def test_path_backup(mocker, cls, path, name):
mocker.patch.object(
astute.AstuteArchivator, 'get_backup_dict',
return_value={'FUEL_ACCESS': {'user': '1', 'password': '2'}}
)
mocker.patch('octane.util.auth.is_creds_valid', return_value=True)
test_archive = mocker.Mock()
cls(test_archive).backup()
test_archive.add.assert_called_once_with(path, name)

View File

@ -283,8 +283,6 @@ def test_postgres_restore(mocker, cls, db, services):
mock_patch = mocker.patch("octane.util.patch.applied_patch")
mock_copyfileobj = mocker.patch("shutil.copyfileobj")
mock_set_astute_password = mocker.patch(
"octane.util.auth.set_astute_password")
mock_apply_task = mocker.patch("octane.util.puppet.apply_task")
mock_context = mock.Mock()
@ -329,7 +327,6 @@ def test_postgres_restore(mocker, cls, db, services):
mock.call.admin_token().__enter__(),
mock.call.admin_token().__exit__(None, None, None),
]
mock_set_astute_password.assert_called_once_with(mock_context)
@pytest.mark.parametrize("keys_in_dump_file,restored", [
@ -567,8 +564,6 @@ def test_release_restore(mocker, mock_open, content, existing_releases, calls):
def test_post_restore_puppet_apply_tasks(mocker, mock_subprocess):
context = backup_restore.NailgunCredentialsContext(
user="admin", password="user_pswd")
mock_set_astute_password = mocker.patch(
"octane.util.auth.set_astute_password")
mock_apply = mocker.patch("octane.util.puppet.apply_all_tasks")
mock_admin_token = mocker.patch("octane.util.keystone.admin_token_auth")
@ -576,7 +571,6 @@ def test_post_restore_puppet_apply_tasks(mocker, mock_subprocess):
archivator.restore()
assert mock_apply.called
mock_set_astute_password.assert_called_once_with(context)
expected_pipelines = [
"pipeline:public_api",
"pipeline:admin_api",
@ -600,7 +594,10 @@ def test_post_restore_puppet_apply_tasks(mocker, mock_subprocess):
def test_logs_restore(
mocker, mock_open, mock_subprocess, nodes, is_dir, exception):
domain_name = "test_domain"
mocker.patch("yaml.load", return_value={"DNS_DOMAIN": domain_name})
mocker.patch("yaml.load", return_value={
"DNS_DOMAIN": domain_name,
'OS_USERNAME': 'a', 'OS_PASSWORD': 'b',
})
domain_names = []
fuel_client_values = []
is_link_exists = []

View File

@ -12,49 +12,28 @@
import mock
import pytest
import requests
from octane.handlers import backup_restore
from octane.util import auth
class TestException(Exception):
pass
@pytest.mark.parametrize("status,valid", [
(401, False),
(500, requests.HTTPError),
(200, True),
])
def test_is_creds_valid(mocker, status, valid):
mock_resp = mock.Mock(spec=["status_code", "raise_for_status"])
mock_resp.status_code = status
mocker.patch("fuelclient.client.APIClient.get_request_raw",
return_value=mock_resp)
@pytest.mark.parametrize("exc_on_apply", [True, False])
def test_set_astute_password(mocker, mock_open, exc_on_apply):
fd_mock = mock.Mock()
close_mock = mocker.patch("os.close")
mkstemp_mock = mocker.patch(
"tempfile.mkstemp",
return_value=(fd_mock, "/etc/fuel/.astute.yaml.bac"))
mock_copy = mocker.patch("shutil.copy2")
mock_move = mocker.patch("shutil.move")
yaml_load = mocker.patch(
"yaml.load", return_value={"FUEL_ACCESS": {"password": "dump_pswd"}})
yaml_dump = mocker.patch("yaml.safe_dump")
context = backup_restore.NailgunCredentialsContext(
user="admin", password="user_pswd")
if exc_on_apply:
with pytest.raises(TestException):
with auth.set_astute_password(context):
raise TestException("text exception")
if not isinstance(valid, bool):
mock_resp.raise_for_status.side_effect = valid()
with pytest.raises(valid):
auth.is_creds_valid('a', 'b')
else:
with auth.set_astute_password(context):
pass
assert mock_open.call_args_list == [
mock.call("/etc/fuel/astute.yaml", "r"),
mock.call("/etc/fuel/astute.yaml", "w"),
]
yaml_load.assert_called_once_with(mock_open.return_value)
yaml_dump.assert_called_once_with(
{'FUEL_ACCESS': {'password': 'user_pswd'}},
mock_open.return_value,
default_flow_style=False)
mock_copy.assert_called_once_with("/etc/fuel/astute.yaml",
"/etc/fuel/.astute.yaml.bac")
mock_move.assert_called_once_with("/etc/fuel/.astute.yaml.bac",
"/etc/fuel/astute.yaml")
mkstemp_mock.assert_called_once_with(
dir="/etc/fuel", prefix=".astute.yaml.octane")
close_mock.assert_called_once_with(fd_mock)
assert auth.is_creds_valid('a', 'b') == valid
if not valid:
assert not mock_resp.raise_for_status.called

View File

@ -10,25 +10,19 @@
# License for the specific language governing permissions and limitations
# under the License.
import shutil
import yaml
import collections
from fuelclient import client
import contextlib
from octane.util import helpers
from octane.util import tempfile
from octane.util import fuel_client
@contextlib.contextmanager
def set_astute_password(auth_context):
tmp_file_name = tempfile.get_tempname(
dir="/etc/fuel", prefix=".astute.yaml.octane")
shutil.copy2("/etc/fuel/astute.yaml", tmp_file_name)
try:
data = helpers.get_astute_dict()
data["FUEL_ACCESS"]["password"] = auth_context.password
with open("/etc/fuel/astute.yaml", "w") as current:
yaml.safe_dump(data, current, default_flow_style=False)
yield
finally:
shutil.move(tmp_file_name, "/etc/fuel/astute.yaml")
Context = collections.namedtuple('Context', ('user', 'password'))
def is_creds_valid(user, password):
with fuel_client.set_auth_context(Context(user, password)):
resp = client.APIClient.get_request_raw('/clusters')
if resp.status_code != 401:
resp.raise_for_status()
return True
return False

View File

@ -14,6 +14,8 @@ import re
import yaml
from octane import magic_consts
def merge_dicts(base_dict, update_dict):
result = base_dict.copy()
@ -26,7 +28,7 @@ def merge_dicts(base_dict, update_dict):
def get_astute_dict():
return load_yaml("/etc/fuel/astute.yaml")
return load_yaml(magic_consts.ASTUTE_YAML)
def load_yaml(filename):