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 import yaml
from octane.handlers.backup_restore import base from octane.handlers.backup_restore import base
from octane import magic_consts as consts
from octane.util import auth
from octane.util import puppet from octane.util import puppet
class AstuteArchivator(base.PathArchivator): class AstuteArchivator(base.PathArchivator):
path = "/etc/fuel/astute.yaml" path = consts.ASTUTE_YAML
name = "astute/astute.yaml" name = "astute/astute.yaml"
keys_to_restore = [ keys_to_restore = [
@ -59,6 +61,15 @@ class AstuteArchivator(base.PathArchivator):
("FUEL_ACCESS", ["user", "password"]), ("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): def get_backup_dict(self):
return yaml.load(self.archive.extractfile(self.name)) return yaml.load(self.archive.extractfile(self.name))
@ -67,13 +78,24 @@ class AstuteArchivator(base.PathArchivator):
return yaml.load(current) return yaml.load(current)
def pre_restore_check(self): def pre_restore_check(self):
backup_ip = self.get_backup_dict()["ADMIN_NETWORK"]["ipaddress"] backup = self.get_backup_dict()
current_ip = self.get_current_dict()["ADMIN_NETWORK"]["ipaddress"] current = self.get_current_dict()
backup_ip = backup["ADMIN_NETWORK"]["ipaddress"]
current_ip = current["ADMIN_NETWORK"]["ipaddress"]
if backup_ip != current_ip: if backup_ip != current_ip:
raise Exception( raise Exception(
"Restore allowed on machine with same ipaddress. " "Restore allowed on machine with same ipaddress. "
"Use fuel-menu to set up ipaddress to {0}".format(backup_ip)) "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): def restore(self):
backup_yaml = self.get_backup_dict() backup_yaml = self.get_backup_dict()
current_yaml = self.get_current_dict() current_yaml = self.get_current_dict()

View File

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

View File

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

View File

@ -162,3 +162,5 @@ COMPUTE_PREUPGRADE_PACKAGES = {
"python-concurrent.futures", "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"), (ssh.SshArchivator, "/root/.ssh/", "ssh"),
]) ])
def test_path_backup(mocker, cls, path, name): 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() test_archive = mocker.Mock()
cls(test_archive).backup() cls(test_archive).backup()
test_archive.add.assert_called_once_with(path, name) 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_patch = mocker.patch("octane.util.patch.applied_patch")
mock_copyfileobj = mocker.patch("shutil.copyfileobj") 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_apply_task = mocker.patch("octane.util.puppet.apply_task")
mock_context = mock.Mock() 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().__enter__(),
mock.call.admin_token().__exit__(None, None, None), 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", [ @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): def test_post_restore_puppet_apply_tasks(mocker, mock_subprocess):
context = backup_restore.NailgunCredentialsContext( context = backup_restore.NailgunCredentialsContext(
user="admin", password="user_pswd") 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_apply = mocker.patch("octane.util.puppet.apply_all_tasks")
mock_admin_token = mocker.patch("octane.util.keystone.admin_token_auth") 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() archivator.restore()
assert mock_apply.called assert mock_apply.called
mock_set_astute_password.assert_called_once_with(context)
expected_pipelines = [ expected_pipelines = [
"pipeline:public_api", "pipeline:public_api",
"pipeline:admin_api", "pipeline:admin_api",
@ -600,7 +594,10 @@ def test_post_restore_puppet_apply_tasks(mocker, mock_subprocess):
def test_logs_restore( def test_logs_restore(
mocker, mock_open, mock_subprocess, nodes, is_dir, exception): mocker, mock_open, mock_subprocess, nodes, is_dir, exception):
domain_name = "test_domain" 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 = [] domain_names = []
fuel_client_values = [] fuel_client_values = []
is_link_exists = [] is_link_exists = []

View File

@ -12,49 +12,28 @@
import mock import mock
import pytest import pytest
import requests
from octane.handlers import backup_restore
from octane.util import auth from octane.util import auth
class TestException(Exception): @pytest.mark.parametrize("status,valid", [
pass (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]) if not isinstance(valid, bool):
def test_set_astute_password(mocker, mock_open, exc_on_apply): mock_resp.raise_for_status.side_effect = valid()
fd_mock = mock.Mock() with pytest.raises(valid):
close_mock = mocker.patch("os.close") auth.is_creds_valid('a', 'b')
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")
else: else:
with auth.set_astute_password(context): assert auth.is_creds_valid('a', 'b') == valid
pass if not valid:
assert mock_open.call_args_list == [ assert not mock_resp.raise_for_status.called
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)

View File

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

View File

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