UNIT_DIR = '/etc/systemd/system'
LOG_DIR_MODE = 0o770
DATA_DIR_MODE = 0o700
+CONTAINER_INIT=False
CONTAINER_PREFERENCE = ['podman', 'docker'] # prefer podman to docker
CUSTOM_PS1 = r'[ceph: \u@\h \W]\$ '
DEFAULT_TIMEOUT = None # in seconds
import pwd
import random
import select
+import shlex
import shutil
import socket
import string
],
},
"grafana": {
- "image": "docker.io/ceph/ceph-grafana:6.6.2",
+ "image": "docker.io/ceph/ceph-grafana:6.7.4",
"cpus": "2",
"memory": "4GB",
"args": [],
},
} # type: ignore
+ @staticmethod
+ def get_version(container_path, container_id, daemon_type):
+ # type: (str, str, str) -> str
+ """
+ :param: daemon_type Either "prometheus", "alertmanager" or "node-exporter"
+ """
+ assert daemon_type in ('prometheus', 'alertmanager', 'node-exporter')
+ cmd = daemon_type.replace('-', '_')
+ code = -1
+ err = ''
+ version = ''
+ if daemon_type == 'alertmanager':
+ for cmd in ['alertmanager', 'prometheus-alertmanager']:
+ _, err, code = call([
+ container_path, 'exec', container_id, cmd,
+ '--version'
+ ], verbosity=CallVerbosity.SILENT)
+ if code == 0:
+ break
+ cmd = 'alertmanager' # reset cmd for version extraction
+ else:
+ _, err, code = call([
+ container_path, 'exec', container_id, cmd, '--version'
+ ])
+ if code == 0 and \
+ err.startswith('%s, version ' % cmd):
+ version = err.split(' ')[2]
+ return version
+
##################################
##################################
+class PortOccupiedError(Error):
+ pass
+
def attempt_bind(s, address, port):
# type: (socket.socket, str, int) -> None
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((address, port))
except (socket.error, OSError) as e: # py2 and py3
- msg = 'Cannot bind to IP %s port %d: %s' % (address, port, e)
- logger.warning(msg)
if e.errno == errno.EADDRINUSE:
- raise OSError(msg)
- elif e.errno == errno.EADDRNOTAVAIL:
- pass
+ msg = 'Cannot bind to IP %s port %d: %s' % (address, port, e)
+ logger.warning(msg)
+ raise PortOccupiedError(msg)
+ else:
+ raise e
finally:
s.close()
# type: (int) -> bool
"""Detect whether a port is in use on the local machine - IPv4 and IPv6"""
logger.info('Verifying port %d ...' % port_num)
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- attempt_bind(s, '0.0.0.0', port_num)
-
- s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
- attempt_bind(s, '::', port_num)
- except OSError:
- return True
- else:
+ def _port_in_use(af, address):
+ # type: (socket.AddressFamily, str) -> bool
+ try:
+ s = socket.socket(af, socket.SOCK_STREAM)
+ attempt_bind(s, address, port_num)
+ except PortOccupiedError:
+ return True
+ except OSError as e:
+ if e.errno in (errno.EAFNOSUPPORT, errno.EADDRNOTAVAIL):
+ # Ignore EAFNOSUPPORT and EADDRNOTAVAIL as two interfaces are
+ # being tested here and one might be intentionally be disabled.
+ # In that case no error should be raised.
+ return False
+ else:
+ raise e
return False
+ return any(_port_in_use(af, address) for af, address in (
+ (socket.AF_INET, '0.0.0.0'),
+ (socket.AF_INET6, '::')
+ ))
def check_ip_port(ip, port):
ip = unwrap_ipv6(ip)
else:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- try:
- attempt_bind(s, ip, port)
- except OSError as e:
- raise Error(e)
+ attempt_bind(s, ip, port)
##################################
return False
+def is_container_running(name: str) -> bool:
+ out, err, ret = call_throws([
+ container_path, 'ps',
+ '--format', '{{.Names}}'])
+ return name in out
+
+
def get_legacy_config_fsid(cluster, legacy_dir=None):
# type: (str, Optional[str]) -> Optional[str]
config_file = '/etc/ceph/%s.conf' % cluster
'--setgroup', 'ceph',
'--default-log-to-file=false',
'--default-log-to-stderr=true',
- '--default-log-stderr-prefix="debug "',
+ '--default-log-stderr-prefix=debug ',
]
if daemon_type == 'mon':
r += [
if daemon_type in Monitoring.components.keys():
config_json: Dict[str, Any] = get_parm(args.config_json)
- required_files = Monitoring.components[daemon_type].get('config-json-files', list())
# Set up directories specific to the monitoring component
config_dir = ''
makedirs(os.path.join(data_dir_root, config_dir, 'data'), uid, gid, 0o755)
# populate the config directory for the component from the config-json
- for fname in required_files:
- if 'files' in config_json: # type: ignore
+ if 'files' in config_json:
+ for fname in config_json['files']:
content = dict_get_join(config_json['files'], fname)
- with open(os.path.join(data_dir_root, config_dir, fname), 'w') as f:
+ if os.path.isabs(fname):
+ fpath = os.path.join(data_dir_root, fname.lstrip(os.path.sep))
+ else:
+ fpath = os.path.join(data_dir_root, config_dir, fname)
+ with open(fpath, 'w', encoding='utf-8') as f:
os.fchown(f.fileno(), uid, gid)
os.fchmod(f.fileno(), 0o600)
f.write(content)
envs=envs,
privileged=privileged,
ptrace=ptrace,
- init=args.container_init,
host_network=host_network,
)
file_obj.write('! '+ ' '.join(container.rm_cmd()) + ' 2> /dev/null\n')
# Sometimes, `podman rm` doesn't find the container. Then you'll have to add `--storage`
if 'podman' in container_path:
- file_obj.write('! '+ ' '.join(container.rm_cmd(storage=True)) + ' 2> /dev/null\n')
+ file_obj.write(
+ '! '
+ + ' '.join([shlex.quote(a) for a in container.rm_cmd(storage=True)])
+ + ' 2> /dev/null\n')
# container run command
- file_obj.write(' '.join(container.run_cmd()) + (' &' if background else '') + '\n')
+ file_obj.write(
+ ' '.join([shlex.quote(a) for a in container.run_cmd()])
+ + (' &' if background else '') + '\n')
def deploy_daemon_units(fsid, uid, gid, daemon_type, daemon_id, c,
else:
logger.debug('firewalld port %s is enabled in current zone' % tcp_port)
- out, err, ret = call([self.cmd, '--permanent', '--query-port', tcp_port], verbose_on_failure=False)
def apply_rules(self):
# type: () -> None
if not self.available:
'Type=forking\n'
'PIDFile=/%t/%n-pid\n')
+ docker = 'docker' in container_path
u = """# generated by cephadm
[Unit]
Description=Ceph %i for {fsid}
# http://www.freedesktop.org/wiki/Software/systemd/NetworkTarget
# these can be removed once ceph-mon will dynamically change network
# configuration.
-After=network-online.target local-fs.target time-sync.target
+After=network-online.target local-fs.target time-sync.target{docker_after}
Wants=network-online.target local-fs.target time-sync.target
+{docker_requires}
PartOf=ceph-{fsid}.target
Before=ceph-{fsid}.target
container_path=container_path,
fsid=fsid,
data_dir=args.data_dir,
- extra_args=extra_args)
+ extra_args=extra_args,
+ # if docker, we depend on docker.service
+ docker_after=' docker.service' if docker else '',
+ docker_requires='Requires=docker.service\n' if docker else '',
+)
return u
privileged: bool = False,
ptrace: bool = False,
bind_mounts: Optional[List[List[str]]] = None,
- init: bool = False,
+ init: Optional[bool] = None,
host_network: bool = True,
) -> None:
self.image = image
self.privileged = privileged
self.ptrace = ptrace
self.bind_mounts = bind_mounts if bind_mounts else []
- self.init = init
+ self.init = init if init else container_init
self.host_network = host_network
def run_cmd(self) -> List[str]:
cmd_args.append('--cap-add=SYS_PTRACE')
if self.init:
cmd_args.append('--init')
+ envs += ['-e', 'CEPH_USE_RANDOM_NONCE=1']
if self.cname:
cmd_args.extend(['--name', self.cname])
if self.envs:
# let OSD etc read block devs that haven't been chowned
'--group-add=disk',
])
+ if self.init:
+ cmd_args.append('--init')
+ envs += ['-e', 'CEPH_USE_RANDOM_NONCE=1']
if self.envs:
for env in self.envs:
envs.extend(['-e', env])
# type: () -> int
out, err, ret = call_throws([
container_path, 'inspect',
- '--format', '{{.ID}},{{json .RepoDigests}}',
+ '--format', '{{.ID}},{{.RepoDigests}}',
args.image])
if ret:
return errno.ENOENT
'image_id': normalize_container_id(image_id)
}
if digests:
- json_digests = json.loads(digests)
+ json_digests = digests[1:-1].split(' ')
if json_digests:
r['repo_digest'] = json_digests[0]
return r
if not cp.has_section('global'):
cp.add_section('global')
cp.set('global', 'fsid', fsid);
- cp.set('global', 'mon host', addr_arg)
+ cp.set('global', 'mon_host', addr_arg)
cp.set('global', 'container_image', args.image)
+ if not cp.has_section('mon'):
+ cp.add_section('mon')
+ if (
+ not cp.has_option('mon', 'auth_allow_insecure_global_id_reclaim')
+ and not cp.has_option('mon', 'auth allow insecure global id reclaim')
+ ):
+ cp.set('mon', 'auth_allow_insecure_global_id_reclaim', 'false')
cpf = StringIO()
cp.write(cpf)
config = cpf.getvalue()
cli(['config', 'set', 'mgr', 'mgr/cephadm/registry_username', args.registry_username, '--force'])
cli(['config', 'set', 'mgr', 'mgr/cephadm/registry_password', args.registry_password, '--force'])
- if args.container_init:
- cli(['config', 'set', 'mgr', 'mgr/cephadm/container_init', str(args.container_init), '--force'])
+ cli(['config', 'set', 'mgr', 'mgr/cephadm/container_init', str(container_init), '--force'])
if not args.skip_dashboard:
# Configure SSL port (cephadm only allows to configure dashboard SSL port)
logger.info('Creating initial admin user...')
password = args.initial_dashboard_password or generate_password()
- cmd = ['dashboard', 'ac-user-create', args.initial_dashboard_user, password, 'administrator', '--force-password']
+ tmp_password_file = write_tmp(password, uid, gid)
+ cmd = ['dashboard', 'ac-user-create', args.initial_dashboard_user, '-i', '/tmp/dashboard.pw', 'administrator', '--force-password']
if not args.dashboard_password_noupdate:
cmd.append('--pwd-update-required')
- cli(cmd)
+ cli(cmd, extra_mounts={pathify(tmp_password_file.name): '/tmp/dashboard.pw:z'})
logger.info('Fetching dashboard port number...')
out = cli(['config', 'get', 'mgr', 'mgr/dashboard/ssl_server_port'])
port = int(out)
ssh_key = '/etc/ceph/ceph.pub'
if args.ssh_public_key:
ssh_key = args.ssh_public_key.name
- out, err, code = call_throws(['ssh-copy-id', '-f', '-i', ssh_key, '%s@%s' % (args.ssh_user, split[1])])
+ out, err, code = call_throws(['sudo', '-u', args.ssh_user, 'ssh-copy-id', '-f', '-i', ssh_key, '-o StrictHostKeyChecking=no', '%s@%s' % (args.ssh_user, split[1])])
mounts = {}
mounts[pathify(args.apply_spec)] = '/tmp/spec.yml:z'
redeploy = False
unit_name = get_unit_name(args.fsid, daemon_type, daemon_id)
+ container_name = 'ceph-%s-%s.%s' % (args.fsid, daemon_type, daemon_id)
(_, state, _) = check_unit(unit_name)
- if state == 'running':
+ if state == 'running' or is_container_running(container_name):
redeploy = True
if args.reconfig:
elif daemon_type in ['prometheus',
'alertmanager',
'node-exporter']:
- cmd = daemon_type.replace('-', '_')
- out, err, code = call(
- [container_path, 'exec', container_id,
- cmd, '--version'])
- if not code and \
- err.startswith('%s, version ' % cmd):
- version = err.split(' ')[2]
- seen_versions[image_id] = version
+ version = Monitoring.get_version(container_path, container_id, daemon_type)
+ seen_versions[image_id] = version
elif daemon_type == CustomContainer.daemon_type:
# Because a custom container can contain
# everything, we do not know which command
action='append',
default=[],
help='set environment variable')
+ parser.add_argument(
+ '--no-container-init',
+ action='store_true',
+ default=not CONTAINER_INIT,
+ help='Do not run podman/docker with `--init`')
subparsers = parser.add_subparsers(help='sub-command')
parser_adopt.add_argument(
'--container-init',
action='store_true',
- help='Run podman/docker with `--init`')
+ default=CONTAINER_INIT,
+ help=argparse.SUPPRESS)
parser_rm_daemon = subparsers.add_parser(
'rm-daemon', help='remove daemon instance')
parser_bootstrap.add_argument(
'--container-init',
action='store_true',
- help='Run podman/docker with `--init`')
+ default=CONTAINER_INIT,
+ help=argparse.SUPPRESS)
parser_deploy = subparsers.add_parser(
'deploy', help='deploy a daemon')
parser_deploy.add_argument(
'--container-init',
action='store_true',
- help='Run podman/docker with `--init`')
+ default=CONTAINER_INIT,
+ help=argparse.SUPPRESS)
parser_check_host = subparsers.add_parser(
'check-host', help='check host configuration')
def _parse_args(av):
parser = _get_parser()
+
args = parser.parse_args(av)
if 'command' in args and args.command and args.command[0] == "--":
args.command.pop(0)
+
+ # workaround argparse to deprecate the subparser `--container-init` flag
+ # container_init and no_container_init must always be mutually exclusive
+ container_init_args = ('--container-init', '--no-container-init')
+ if set(container_init_args).issubset(av):
+ parser.error('argument %s: not allowed with argument %s' % (container_init_args))
+ elif '--container-init' in av:
+ args.no_container_init = not args.container_init
+ else:
+ args.container_init = not args.no_container_init
+ assert args.container_init is not args.no_container_init
+
return args
sys.stderr.write('Unable to locate any of %s\n' % CONTAINER_PREFERENCE)
sys.exit(1)
+ # container-init?
+ container_init = args.container_init
+ logger.debug('container_init=%s' % (container_init))
+
try:
r = args.func()
except Error as e: