| # 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. |
| |
| # Line too long - pylint: disable=C0301 |
| |
| from optparse import OptionGroup |
| import os |
| import sys |
| import tarfile |
| |
| try: |
| from gppylib import gplog, pgconf |
| from gppylib.commands import gp |
| from gppylib.commands.base import Command, ExecutionError |
| from gppylib.commands.unix import curr_platform, SUNOS |
| from gppylib.db import dbconn |
| from gppylib.gpversion import GpVersion |
| from gppylib.gpparseopts import OptParser, OptChecker |
| from gppylib.mainUtils import addMasterDirectoryOptionForSingleClusterProgram, addStandardLoggingAndHelpOptions, ExceptionNoStackTraceNeeded |
| from gppylib.operations.package import MigratePackages, InstallPackage, UninstallPackage, QueryPackage, BuildGppkg, UpdatePackage, CleanGppkg, Gppkg, GPPKG_EXTENSION, GPPKG_ARCHIVE_PATH |
| from gppylib.operations.unix import ListFilesByPattern |
| from hawqpylib.hawqlib import get_hawq_hostname_all, HawqXMLParser |
| |
| import yaml |
| except ImportError, ex: |
| sys.exit('Cannot import modules. Please check that you have sourced greenplum_path.sh. Detail: ' + str(ex)) |
| |
| logger = gplog.get_default_logger() |
| |
| class GpPkgProgram: |
| """ This is the CLI entry point to package management code. """ |
| def __init__(self, options, args): |
| self.master_datadir = options.masterDataDirectory |
| |
| # TODO: AK: Program logic should not be dictating master, standby, and segment information |
| # In other words, the fundamental Operations should have APIs that preclude the need for this. |
| self.master_host = None |
| self.standby_host = None |
| self.segment_host_list = None |
| |
| self.query = options.query |
| self.build = options.build |
| self.install = options.install |
| self.remove = options.remove |
| self.update = options.update |
| self.clean = options.clean |
| self.migrate = options.migrate |
| |
| # only one of the following may be provided: --install, --remove, --update, --query, --build, --clean, --migrate |
| count = sum([1 for opt in ['install', 'remove', 'update', 'query', 'build', 'clean', 'migrate'] if getattr(self, opt)]) |
| if count != 1: |
| raise ExceptionNoStackTraceNeeded('Exactly one of the following must be provided: --install, --remove, -update, --query, --clean, --migrate') |
| |
| if self.query: |
| # gppkg -q can be supplemented with --info, --list, --all |
| count = sum([1 for opt in ['info', 'list', 'all'] if options.__dict__[opt]]) |
| if count > 1: |
| raise ExceptionNoStackTraceNeeded('For --query, at most one of the following can be provided: --info, --list, --all') |
| # for all query options other than --all, a package path must be provided |
| if not options.all and len(args) != 1: |
| raise ExceptionNoStackTraceNeeded('A package must be specified for -q, -q --info, and -q --list.') |
| |
| if options.info: |
| self.query = (QueryPackage.INFO, args[0]) |
| elif options.list: |
| self.query = (QueryPackage.LIST, args[0]) |
| elif options.all: |
| self.query = (QueryPackage.ALL, None) |
| else: |
| self.query = (None, args[0]) |
| elif self.migrate: |
| if len(args) != 2: |
| raise ExceptionNoStackTraceNeeded('Invalid syntax, expecting "gppkg --migrate <from_gphome> <to_gphome>".') |
| self.migrate = (args[0], args[1]) |
| |
| @staticmethod |
| def create_parser(): |
| parser = OptParser(option_class=OptChecker, |
| description="Greenplum Package Manager", |
| version='%prog version $Revision: #1 $') |
| parser.setHelp([]) |
| |
| addStandardLoggingAndHelpOptions(parser, includeNonInteractiveOption=True) |
| |
| parser.remove_option('-q') |
| parser.remove_option('-l') |
| |
| add_to = OptionGroup(parser, 'General Options') |
| parser.add_option_group(add_to) |
| |
| addMasterDirectoryOptionForSingleClusterProgram(add_to) |
| |
| # TODO: AK: Eventually, these options may need to be flexible enough to accept mutiple packages |
| # in one invocation. If so, the structure of this parser may need to change. |
| add_to.add_option('-i', '--install', help='install the given gppkg', metavar='<package>') |
| add_to.add_option('-u', '--update', help='update the given gppkg', metavar='<package>') |
| add_to.add_option('-r', '--remove', help='remove the given gppkg', metavar='<name>-<version>') |
| add_to.add_option('-q', '--query', help='query the gppkg database or a particular gppkg', action='store_true') |
| add_to.add_option('-b', '--build', help='build a gppkg', metavar='<directory>') |
| add_to.add_option('-c', '--clean', help='clean the cluster of the given gppkg', action='store_true') |
| add_to.add_option('--migrate', help='migrate gppkgs from a separate $GPHOME', metavar='<from_gphome> <to_gphome>', action='store_true', default=False) |
| |
| add_to = OptionGroup(parser, 'Query Options') |
| parser.add_option_group(add_to) |
| add_to.add_option('--info', action='store_true', help='print information about the gppkg including name, version, description') |
| add_to.add_option('--list', action='store_true', help='print all the files present in the gppkg') |
| add_to.add_option('--all', action='store_true', help='print all the gppkgs installed by gppkg') |
| |
| return parser |
| |
| @staticmethod |
| def create_program(options, args): |
| """ TODO: AK: This convention may be unnecessary. """ |
| return GpPkgProgram(options, args) |
| |
| def _get_gpdb_host_list(self): |
| """ |
| TODO: AK: Get rid of this. Program logic should not be driving host list building . |
| |
| This method gets the host names |
| of all hosts in the gpdb array. |
| It sets the following variables |
| GpPkgProgram.master_host to master |
| GpPkgProgram.standby_host to standby |
| GpPkgProgram.segment_host_list to segment hosts |
| """ |
| |
| logger.debug('_get_gpdb_host_list') |
| |
| #Get host list |
| GPHOME = os.getenv('GPHOME') |
| if GPHOME == '' or not GPHOME: |
| logger.info('GPHOME is not set.') |
| sys.exit(1) |
| hawq_site = HawqXMLParser(GPHOME) |
| hawq_site.get_all_values() |
| master_port = hawq_site.hawq_dict['hawq_master_address_port'] |
| master_host = "" |
| standby_host = None |
| segment_host_list = [] |
| host_list = get_hawq_hostname_all(master_port) |
| |
| for host, status in host_list['master'].iteritems(): |
| master_host = host |
| for host, status in host_list['standby'].iteritems(): |
| standby_host = host |
| for host, status in host_list['segment'].iteritems(): |
| segment_host_list.append(host) |
| |
| #Deduplicate the hosts so that we |
| #dont install multiple times on the same host |
| segment_host_list = list(set(segment_host_list)) |
| |
| #Segments might exist on the master host. Since we store the |
| #master host separately in self.master_host, storing the master_host |
| #in the segment_host_list is redundant. |
| for host in segment_host_list: |
| if host == master_host or host == standby_host: |
| segment_host_list.remove(host) |
| |
| self.master_host = master_host |
| self.standby_host = standby_host |
| self.segment_host_list = segment_host_list |
| |
| def _get_master_port(self, datadir): |
| |
| ''' |
| Obtain the master port from the pgconf file |
| ''' |
| |
| logger.debug('_get_master_port') |
| pgconf_dict = pgconf.readfile(os.path.join(datadir, 'postgresql.conf')) |
| return pgconf_dict.int('port') |
| |
| def run(self): |
| if self.build: |
| BuildGppkg(self.build).run() |
| return |
| |
| #Check for RPM and Solaris OS |
| if curr_platform == SUNOS: |
| raise ExceptionNoStackTraceNeeded('gppkg is not supported on Solaris') |
| |
| try: |
| cmd = Command(name = 'Check for rpm', cmdStr = 'rpm --version') |
| cmd.run(validateAfter = True) |
| results = cmd.get_results().stdout.strip() |
| rpm_version_string = results.split(' ')[-1] |
| |
| if not rpm_version_string.startswith('4.'): |
| raise ExceptionNoStackTraceNeeded('gppkg requires rpm version 4.x') |
| |
| except ExecutionError, ex: |
| results = ex.cmd.get_results().stderr.strip() |
| if len(results) != 0 and 'not found' in results: |
| raise ExceptionNoStackTraceNeeded('gppkg requires RPM to be available in PATH') |
| |
| if self.migrate: |
| MigratePackages(from_gphome = self.migrate[0], |
| to_gphome = self.migrate[1]).run() |
| return |
| |
| # MASTER_DATA_DIRECTORY and PGPORT must not need to be set for |
| # --build and --migrate to function properly |
| if self.master_datadir is None: |
| self.master_datadir = gp.get_masterdatadir() |
| self.master_port = self._get_master_port(self.master_datadir) |
| |
| # TODO: AK: Program logic should not drive host decisions. |
| self._get_gpdb_host_list() |
| |
| if self.install: |
| pkg = Gppkg.from_package_path(self.install) |
| InstallPackage(pkg, self.master_host, self.standby_host, self.segment_host_list).run() |
| elif self.query: |
| query_type, package_path = self.query |
| QueryPackage(query_type, package_path).run() |
| elif self.remove: |
| if self.remove.count('-') > 3: |
| raise ExceptionNoStackTraceNeeded('Please specify the correct <name>-<version>.') |
| pkg_file_list = ListFilesByPattern(GPPKG_ARCHIVE_PATH, self.remove + '-*-*' + GPPKG_EXTENSION).run() |
| if len(pkg_file_list) == 0: |
| raise ExceptionNoStackTraceNeeded('Package %s has not been installed.' % self.remove) |
| assert len(pkg_file_list) == 1 |
| pkg_file = pkg_file_list[0] |
| pkg = Gppkg.from_package_path(os.path.join(GPPKG_ARCHIVE_PATH, pkg_file)) |
| UninstallPackage(pkg, self.master_host, self.standby_host, self.segment_host_list).run() |
| elif self.update: |
| pkg = Gppkg.from_package_path(self.update) |
| UpdatePackage(pkg, self.master_host, self.standby_host, self.segment_host_list).run() |
| elif self.clean: |
| CleanGppkg(self.standby_host, self.segment_host_list).run() |
| |
| def cleanup(self): |
| pass |