ccmlib/hcd/hcd_cluster.py (107 lines of code) (raw):
# 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.
# DataStax Hyper-Converged Database (HCD) clusters
from __future__ import absolute_import
import os
import re
import subprocess
import shutil
import tarfile
import tempfile
from argparse import ArgumentError
from distutils.version import LooseVersion
from ccmlib import common, repository
from ccmlib.cluster import Cluster
from ccmlib.common import ArgumentError, rmdirs
from ccmlib.hcd.hcd_node import HcdNode
try:
import ConfigParser
except ImportError:
import configparser as ConfigParser
HCD_CASSANDRA_CONF_DIR = "resources/cassandra/conf"
HCD_ARCHIVE = "https://downloads.datastax.com/hcd/hcd-%s-bin.tar.gz"
def isHcd(install_dir, options=None):
if install_dir is None:
raise ArgumentError('Undefined installation directory')
bin_dir = os.path.join(install_dir, common.BIN_DIR)
if options and options.hcd and './' != install_dir and not os.path.exists(bin_dir):
raise ArgumentError('Installation directory does not contain a bin directory: %s' % install_dir)
if options and options.hcd:
return True
hcd_script = os.path.join(bin_dir, 'hcd')
if options and not options.hcd and './' != install_dir and os.path.exists(hcd_script):
raise ArgumentError('Installation directory is HCD but options did not specify `--hcd`: %s' % install_dir)
return os.path.exists(hcd_script)
def isHcdClusterType(install_dir, options=None):
if isHcd(install_dir, options):
return HcdCluster
return None
class HcdCluster(Cluster):
@staticmethod
def getConfDir(install_dir):
if isHcd(install_dir):
return os.path.join(install_dir, HCD_CASSANDRA_CONF_DIR)
raise RuntimeError("illegal call to HcdCluster.getConfDir() when not HCD")
@staticmethod
def getNodeClass():
return HcdNode
def __init__(self, path, name, partitioner=None, install_dir=None, create_directory=True, version=None, verbose=False, derived_cassandra_version=None, options=None):
self._cassandra_version = None
if derived_cassandra_version:
self._cassandra_version = derived_cassandra_version
super(HcdCluster, self).__init__(path, name, partitioner, install_dir, create_directory, version, verbose, options)
def can_generate_tokens(self):
return False
def load_from_repository(self, version, verbose):
return setup_hcd(version, verbose)
def create_node(self, name, auto_bootstrap, thrift_interface, storage_interface, jmx_port, remote_debug_port, initial_token, save=True, binary_interface=None, byteman_port='0', environment_variables=None, derived_cassandra_version=None, **kwargs):
return HcdNode(name, self, auto_bootstrap, thrift_interface, storage_interface, jmx_port, remote_debug_port, initial_token, save, binary_interface, byteman_port, environment_variables=environment_variables, derived_cassandra_version=derived_cassandra_version, **kwargs)
def cassandra_version(self):
if self._cassandra_version is None:
self._cassandra_version = get_hcd_cassandra_version(self.get_install_dir())
return self._cassandra_version
def download_hcd_version(version, verbose=False):
url = HCD_ARCHIVE
if repository.CCM_CONFIG.has_option('repositories', 'hcd'):
url = repository.CCM_CONFIG.get('repositories', 'hcd')
url = url % version
_, target = tempfile.mkstemp(suffix=".tar.gz", prefix="ccm-")
try:
repository.__download(url, target, show_progress=verbose)
common.debug("Extracting {} as version {} ...".format(target, version))
tar = tarfile.open(target)
dir = tar.next().name.split("/")[0] # pylint: disable=all
tar.extractall(path=repository.__get_dir())
tar.close()
target_dir = os.path.join(repository.__get_dir(), version)
if os.path.exists(target_dir):
rmdirs(target_dir)
shutil.move(os.path.join(repository.__get_dir(), dir), target_dir)
except urllib.error.URLError as e:
msg = "Invalid version %s" % version if url is None else "Invalid url %s" % url
msg = msg + " (underlying error is: %s)" % str(e)
raise ArgumentError(msg)
except tarfile.ReadError as e:
raise ArgumentError("Unable to uncompress downloaded file: %s" % str(e))
def setup_hcd(version, verbose=False):
(cdir, version, fallback) = repository.__setup(version, verbose)
if cdir:
return (cdir, version)
cdir = repository.version_directory(version)
if cdir is None:
download_hcd_version(version, verbose=verbose)
cdir = repository.version_directory(version)
return (cdir, version)
def get_hcd_version(install_dir):
""" look for a hcd*.jar and extract the version number """
for root, dirs, files in os.walk(install_dir):
for file in files:
match = re.search('^hcd(?:-core)?-([0-9.]+)(?:-.*)?\.jar', file)
if match:
return match.group(1)
return None
def get_hcd_cassandra_version(install_dir):
# for this to work, the current JAVA_HOME must already be appropriate
hcd_cmd = os.path.join(install_dir, 'bin', 'hcd')
(output, stderr) = subprocess.Popen([hcd_cmd, "cassandra", '-v'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
# just take the last line to avoid any possible logback log lines
output = output.decode('utf-8').rstrip().split('\n')[-1]
match = re.search('([0-9.]+)(?:-.*)?', str(output))
if match:
return LooseVersion(match.group(1))
raise ArgumentError("Unable to determine Cassandra version in: %s.\n\tstdout: '%s'\n\tstderr: '%s'"
% (install_dir, output, stderr))