| #!/usr/bin/env python |
| # 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. |
| |
| ''' |
| gpscp -- scp to multiple hosts at once |
| |
| Usage: gpscp [--version] [-?v] [-r] [-p port] [-u user] |
| [-h host] [-f hostfile] [-J host_substitution_character] [[user@]host1:]file1 [...] [[user@]hosts2:]file2 |
| |
| --version : print version information |
| -? : print this help screen |
| -v : verbose mode |
| -r : recursively copy entire directories |
| -h host : ssh host to connect to (multiple -h is okay) |
| -f file : a file listing all hosts to connect to |
| -J character : character to be substitute as hostname [default='='] |
| ''' |
| |
| #disable deprecationwarnings |
| import warnings |
| warnings.simplefilter('ignore', DeprecationWarning) |
| |
| import os |
| import sys |
| import getopt |
| import subprocess |
| from gppylib.util import ssh_utils |
| from gppylib.gpparseopts import OptParser |
| from gppylib.gpcoverage import GpCoverage |
| from gppylib.parseutils import canonicalize_address |
| |
| |
| progname = os.path.split(sys.argv[0])[-1] |
| |
| if sys.version_info < (2, 5, 0): |
| sys.exit( |
| '''Error: %s is supported on Python versions 2.5 or greater |
| Please upgrade python installed on this machine.''' % progname) |
| |
| # |
| # all the command line options |
| # |
| class Global: |
| script_name = os.path.split(__file__)[-1] |
| USER = os.environ.get('LOGNAME') or os.environ.get('USER') |
| opt = {} |
| opt['-v'] = False |
| opt['-h'] = [] |
| opt['-f'] = None |
| opt['-J'] = '=:' |
| opt['-r'] = False |
| opt['--ignore-bad-hosts'] = False |
| filePath = [] |
| |
| GV = Global() |
| |
| ################ |
| def usage(exitarg): |
| parser = OptParser() |
| try: |
| parser.print_help() |
| except: |
| print __doc__ |
| sys.exit(0) |
| |
| |
| ############# |
| def print_version(): |
| print '%s version $Revision$' % GV.script_name |
| sys.exit(0) |
| |
| ############# |
| def parseCommandLine(): |
| try: |
| (options, args) = getopt.getopt(sys.argv[1:], '?vrJ:p:u:h:f:', ['version', 'ignore-bad-hosts']) |
| except Exception, e: |
| usage('[ERROR] ' + str(e)) |
| |
| for (switch, val) in options: |
| if (switch == '-?'): usage(0) |
| elif (switch == '-v'): GV.opt[switch] = True |
| elif (switch == '-f'): GV.opt[switch] = val |
| elif (switch == '-h'): GV.opt[switch].append(val) |
| elif (switch == '-J'): GV.opt[switch] = val + ':' |
| elif (switch == '-r'): GV.opt[switch] = True |
| elif (switch == '--version'): print_version() |
| elif (switch == '--ignore-bad-hosts'): GV.opt[switch] = True |
| |
| hf = (len(GV.opt['-h']) and 1 or 0) + (GV.opt['-f'] and 1 or 0) |
| if hf != 1: |
| usage('Error: please specify at least one of -h or -f args, but not both') |
| |
| if (len(args) < 2): |
| usage('Error: please specify local and remote file paths') |
| |
| GV.filePath = args |
| |
| |
| ############# |
| def run(cmd, peer): |
| if GV.opt['-v']: print '[INFO]', cmd |
| p = subprocess.Popen(cmd, shell=True) |
| p.x_cmd = cmd |
| p.x_peer = peer |
| return p |
| |
| ############# |
| coverage = GpCoverage() |
| coverage.start() |
| |
| try: |
| parseCommandLine() |
| hostlist = ssh_utils.HostList() |
| for h in GV.opt['-h']: |
| hostlist.add(h) |
| if GV.opt['-f']: |
| hostlist.parseFile(GV.opt['-f']) |
| |
| if GV.opt['--ignore-bad-hosts']: |
| original_hostlist = hostlist.list |
| bad_hosts = hostlist.removeBadHosts() |
| if len(bad_hosts) == len(original_hostlist): |
| sys.exit('[ERROR]: Unable to SSH to any of the hosts {0}'.format(original_hostlist)) |
| if len(bad_hosts) > 0: |
| print "[WARN]: Skipping syncing configuration file on hosts {0}, as ssh test failed".format(bad_hosts) |
| else: |
| try: |
| hostlist.checkSSH() |
| except ssh_utils.SSHError, e: |
| sys.exit('[ERROR] ' + str(e)) |
| |
| GV.opt['-h'] = hostlist.filterMultiHomedHosts() |
| |
| if len(GV.opt['-h']) == 0: |
| usage('Error: missing hosts in -h and/or -f arguments') |
| scp = 'scp -o "BatchMode yes" -o "StrictHostKeyChecking no"' |
| if GV.opt['-r']: scp += ' -r' |
| |
| proc = [] |
| for peer in GV.opt['-h']: |
| peer = canonicalize_address(peer) # MPP-13617 |
| cmd = scp + ' ' |
| for f in GV.filePath: |
| cmd += f.replace(GV.opt['-J'], '%s:' % peer) + ' ' |
| proc.append(run(cmd, peer)) |
| |
| errmsg = None |
| for p in proc: |
| p.communicate() |
| if p.returncode: |
| errmsg = '[ERROR %s] exit %d, cmd - %s' % (p.x_peer, p.returncode, p.x_cmd) |
| print errmsg |
| |
| if errmsg: sys.exit(1) |
| if GV.opt['-v']: print '[INFO] completed successfully' |
| |
| except KeyboardInterrupt: |
| sys.exit('\nInterrupted...') |
| finally: |
| coverage.stop() |
| coverage.generate_report() |