fuel-web/shotgun/shotgun/driver.py

258 lines
9.4 KiB
Python

# Copyright 2013 Mirantis, Inc.
#
# Licensed 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 logging
import os
import pprint
import pwd
import re
import stat
import sys
import xmlrpclib
import fabric.api
from shotgun import utils
logger = logging.getLogger(__name__)
class CommandOut(object):
stdout = None
return_code = None
stderr = None
def __eq__(self, other):
return (
str(self.stdout) == str(other.stdout) and
str(self.stderr) == str(other.stderr) and
str(self.return_code) == str(other.return_code)
)
class Driver(object):
@classmethod
def getDriver(cls, data, conf):
driver_type = data["type"]
return {
"file": File,
"dir": Dir,
"postgres": Postgres,
"xmlrpc": XmlRpc,
"command": Command,
}.get(driver_type, cls)(data, conf)
def __init__(self, data, conf):
logger.debug("Initializing driver %s: host=%s",
self.__class__.__name__, data.get("host"))
self.data = data
self.host = self.data.get("host", {}).get("address", "localhost")
self.ssh_key = self.data.get("host", {}).get("ssh-key")
self.local = utils.is_local(self.host)
self.conf = conf
self.timeout = self.data.get("timeout", self.conf.timeout)
def snapshot(self):
raise NotImplementedError
def command(self, command):
out = CommandOut()
raw_stdout = utils.CCStringIO(writers=sys.stdout)
try:
if not self.local:
with fabric.api.settings(
host_string=self.host, # destination host
key_filename=self.ssh_key, # a path to ssh key
timeout=2, # a network connection timeout
command_timeout=self.timeout, # command execution timeout
warn_only=True, # don't exit on error
abort_on_prompts=True, # non-interactive mode
):
logger.debug("Running remote command: "
"host: %s command: %s", self.host, command)
try:
output = fabric.api.run(command, stdout=raw_stdout)
except SystemExit:
logger.error("Fabric aborted this iteration")
# NOTE(prmtl): because of pty=True (default) and
# combine_stderr=True (default) stderr is combined
# with stdout
out.stdout = raw_stdout.getvalue()
out.return_code = output.return_code
else:
logger.debug("Running local command: %s", command)
out.return_code, out.stdout, out.stderr = utils.execute(
command)
except Exception as e:
logger.error("Error occured: %s", str(e))
out.stdout = raw_stdout.getvalue()
return out
def get(self, path, target_path):
"""Get remote or local file
target_path must be the directory where to put
copied files or directories
"""
try:
if not self.local:
with fabric.api.settings(
host_string=self.host, # destination host
key_filename=self.ssh_key, # a path to ssh key
timeout=2, # a network connection timeout
warn_only=True, # don't exit on error
abort_on_prompts=True, # non-interactive mode
):
logger.debug("Getting remote file: %s %s",
path, target_path)
utils.execute('mkdir -p "{0}"'.format(target_path))
try:
return fabric.api.get(path, target_path)
except SystemExit:
logger.error("Fabric aborted this iteration")
else:
logger.debug("Getting local file: cp -r %s %s",
path, target_path)
utils.execute('mkdir -p "{0}"'.format(target_path))
return utils.execute('cp -r "{0}" "{1}"'.format(path,
target_path))
except Exception as e:
logger.error("Error occured: %s", str(e))
class File(Driver):
def __init__(self, data, conf):
super(File, self).__init__(data, conf)
self.path = self.data["path"]
self.exclude = self.data.get('exclude', [])
logger.debug("File to get: %s", self.path)
self.target_path = str(os.path.join(
self.conf.target, self.host,
os.path.dirname(self.path).lstrip("/")))
self.full_dst_path = os.path.join(
self.conf.target, self.host,
self.path.lstrip("/"))
logger.debug("File to save: %s", self.target_path)
def snapshot(self):
"""Make a snapshot
Example:
self.conf.target IS /target
self.host IS host.domain.tld
self.path IS /var/log/somedir
self.target_path IS /target/host.domain.tld/var/log
"""
self.get(self.path, self.target_path)
if self.exclude:
utils.remove(self.full_dst_path, self.exclude)
Dir = File
class Postgres(Driver):
def __init__(self, data, conf):
super(Postgres, self).__init__(data, conf)
self.dbhost = self.data.get("dbhost", "localhost")
self.dbname = self.data["dbname"]
self.username = self.data.get("username", "postgres")
self.password = self.data.get("password")
self.target_path = str(os.path.join(self.conf.target,
self.host, "pg_dump"))
def snapshot(self):
if self.password:
authline = "{host}:{port}:{dbname}:{username}:{password}".format(
host=self.dbhost, port="5432", dbname=self.dbname,
username=self.username, password=self.password)
home_dir = pwd.getpwuid(os.getuid()).pw_dir
pgpass = os.path.join(home_dir, ".pgpass")
with open(pgpass, "a+") as fo:
fo.seek(0)
auth = False
for line in fo:
if re.search(ur"^{0}$".format(authline), line):
auth = True
break
if not auth:
fo.seek(0, 2)
fo.write("{0}\n".format(authline))
os.chmod(pgpass, stat.S_IRUSR + stat.S_IWUSR)
temp = self.command("mktemp").stdout.strip()
self.command("pg_dump -h {dbhost} -U {username} -w "
"-f {file} {dbname}".format(
dbhost=self.dbhost, username=self.username,
file=temp, dbname=self.dbname))
utils.execute('mkdir -p "{0}"'.format(self.target_path))
dump_basename = "{0}_{1}.sql".format(self.dbhost, self.dbname)
utils.execute('mv -f "{0}" "{1}"'.format(
temp,
os.path.join(self.target_path, dump_basename)))
class XmlRpc(Driver):
def __init__(self, data, conf):
super(XmlRpc, self).__init__(data, conf)
self.server = self.data.get("server", "localhost")
self.methods = self.data.get("methods", [])
self.to_file = self.data.get("to_file")
self.target_path = os.path.join(
self.conf.target, self.host, "xmlrpc", self.to_file)
def snapshot(self):
utils.execute('mkdir -p "{0}"'.format(os.path.dirname(
self.target_path)))
server = xmlrpclib.Server(self.server)
with open(self.target_path, "w") as f:
for method in self.methods:
if hasattr(server, method):
response = getattr(server, method)()
response = pprint.pformat(response, indent=2)
else:
response = "no such method on remote server"
f.write("===== {0} =====\n{1}\n\n".format(method, response))
class Command(Driver):
def __init__(self, data, conf):
super(Command, self).__init__(data, conf)
self.cmdname = self.data["command"]
self.to_file = self.data["to_file"]
self.target_path = os.path.join(
self.conf.target, self.host, "commands", self.to_file)
def snapshot(self):
out = self.command(self.cmdname)
utils.execute('mkdir -p "{0}"'.format(os.path.dirname(
self.target_path)))
with open(self.target_path, "w") as f:
f.write("===== COMMAND =====: {0}\n".format(self.cmdname))
f.write("===== RETURN CODE =====: {0}\n".format(out.return_code))
f.write("===== STDOUT =====:\n")
if out.stdout:
f.write(out.stdout)
f.write("\n===== STDERR =====:\n")
if out.stderr:
f.write(out.stderr)