import enum
import errno
import json
-from typing import List, Set, Optional, Iterator, cast, Dict, Any, Union, Sequence, Mapping
+from typing import List, Set, Optional, Iterator, cast, Dict, Any, Union, Sequence, Mapping, Tuple
import re
import datetime
import math
from ceph.deployment.drive_group import DriveGroupSpec, DeviceSelection, OSDMethod
from ceph.deployment.service_spec import PlacementSpec, ServiceSpec, service_spec_allow_invalid_from_json, TracingSpec
from ceph.deployment.hostspec import SpecValidationError
+from ceph.deployment.utils import unwrap_ipv6
from ceph.utils import datetime_now
from mgr_util import to_pretty_timedelta, format_bytes
NoOrchestrator, OrchestratorValidationError, NFSServiceSpec, \
RGWSpec, InventoryFilter, InventoryHost, HostSpec, CLICommandMeta, \
ServiceDescription, DaemonDescription, IscsiServiceSpec, json_to_generic_spec, \
- GenericSpec, DaemonDescriptionStatus, SNMPGatewaySpec, MDSSpec, TunedProfileSpec
+ GenericSpec, DaemonDescriptionStatus, SNMPGatewaySpec, MDSSpec, TunedProfileSpec, \
+ NvmeofServiceSpec
def nice_delta(now: datetime.datetime, t: Optional[datetime.datetime], suffix: str = '') -> str:
return format_bytes(v, 5)
+class ArgumentError(Exception):
+ pass
+
+
class HostDetails:
def __init__(self,
host: Optional[HostSpec] = None,
yaml.add_representer(HostDetails, HostDetails.yaml_representer)
+class DaemonFields(enum.Enum):
+ service_name = 'service_name'
+ daemon_type = 'daemon_type'
+ name = 'name'
+ host = 'host'
+ status = 'status'
+ refreshed = 'refreshed'
+ age = 'age'
+ mem_use = 'mem_use'
+ mem_lim = 'mem_lim'
+ image = 'image'
+
+
class ServiceType(enum.Enum):
mon = 'mon'
mgr = 'mgr'
rgw = 'rgw'
nfs = 'nfs'
iscsi = 'iscsi'
+ nvmeof = 'nvmeof'
snmp_gateway = 'snmp-gateway'
elasticsearch = 'elasticsearch'
jaeger_agent = 'jaeger-agent'
class IngressType(enum.Enum):
default = 'default'
keepalive_only = 'keepalive-only'
+ haproxy_standard = 'haproxy-standard'
+ haproxy_protocol = 'haproxy-protocol'
+
+ def canonicalize(self) -> "IngressType":
+ if self == self.default:
+ return IngressType(self.haproxy_standard)
+ return IngressType(self)
def to_format(what: Any, format: Format, many: bool, cls: Any) -> Any:
if labels and len(labels) == 1:
labels = labels[0].split(',')
+ if addr is not None:
+ addr = unwrap_ipv6(addr)
+
s = HostSpec(hostname=hostname, addr=addr, labels=labels, status=_status)
return self._apply_misc([s], False, Format.plain)
return HandleCommandResult(stdout=completion.result_str())
@_cli_write_command('orch host drain')
- def _drain_host(self, hostname: str, force: bool = False) -> HandleCommandResult:
+ def _drain_host(self, hostname: str, force: bool = False, keep_conf_keyring: bool = False, zap_osd_devices: bool = False) -> HandleCommandResult:
"""drain all daemons from a host"""
- completion = self.drain_host(hostname, force)
+ completion = self.drain_host(hostname, force, keep_conf_keyring, zap_osd_devices)
raise_if_exception(completion)
return HandleCommandResult(stdout=completion.result_str())
service_name: Optional[str] = None,
daemon_type: Optional[str] = None,
daemon_id: Optional[str] = None,
+ sort_by: Optional[DaemonFields] = DaemonFields.name,
format: Format = Format.plain,
refresh: bool = False) -> HandleCommandResult:
"""
def ukn(s: Optional[str]) -> str:
return '<unknown>' if s is None else s
+
+ def sort_by_field(d: DaemonDescription) -> Any:
+ if sort_by == DaemonFields.name:
+ return d.name()
+ elif sort_by == DaemonFields.host:
+ return d.hostname
+ elif sort_by == DaemonFields.status:
+ return d.status.name if d.status else None
+ elif sort_by == DaemonFields.refreshed:
+ return d.last_refresh
+ elif sort_by == DaemonFields.age:
+ return d.created
+ elif sort_by == DaemonFields.mem_use:
+ return d.memory_usage
+ elif sort_by == DaemonFields.mem_lim:
+ return d.memory_request
+ elif sort_by == DaemonFields.image:
+ return d.container_image_id
+ elif sort_by == DaemonFields.daemon_type:
+ return d.daemon_type
+ elif sort_by == DaemonFields.service_name:
+ return d.service_name()
+ else:
+ return None
+
# Sort the list for display
daemons.sort(key=lambda s: (ukn(s.daemon_type), ukn(s.hostname), ukn(s.daemon_id)))
table._align['MEM LIM'] = 'r'
table.left_padding_width = 0
table.right_padding_width = 2
- for s in natsorted(daemons, key=lambda d: d.name()):
+ for s in natsorted(daemons, key=lambda d: sort_by_field(d)):
if s.status_desc:
status = s.status_desc
else:
return HandleCommandResult(stdout=table.get_string())
- @_cli_write_command('orch prometheus access info')
+ def _get_credentials(self, username: Optional[str] = None, password: Optional[str] = None, inbuf: Optional[str] = None) -> Tuple[str, str]:
+
+ _username = username
+ _password = password
+ if inbuf:
+ try:
+ credentials = json.loads(inbuf)
+ _username = credentials['username'].strip()
+ _password = credentials['password'].strip()
+ except (KeyError, json.JSONDecodeError):
+ raise ArgumentError("""
+ json provided for credentials did not include all necessary fields. Please setup json file as:
+
+ {
+ "username": "USERNAME",
+ "password": "PASSWORD"
+ }
+ """)
+
+ if not _username or not _password:
+ raise ArgumentError("Invalid arguments. Please provide arguments <username> <password> or -i <credentials_json_file>")
+
+ return _username, _password
+
+ @_cli_write_command('orch prometheus set-credentials')
+ def _set_prometheus_access_info(self, username: Optional[str] = None, password: Optional[str] = None, inbuf: Optional[str] = None) -> HandleCommandResult:
+ try:
+ username, password = self._get_credentials(username, password, inbuf)
+ completion = self.set_prometheus_access_info(username, password)
+ result = raise_if_exception(completion)
+ return HandleCommandResult(stdout=json.dumps(result))
+ except ArgumentError as e:
+ return HandleCommandResult(-errno.EINVAL, "", (str(e)))
+
+ @_cli_write_command('orch alertmanager set-credentials')
+ def _set_alertmanager_access_info(self, username: Optional[str] = None, password: Optional[str] = None, inbuf: Optional[str] = None) -> HandleCommandResult:
+ try:
+ username, password = self._get_credentials(username, password, inbuf)
+ completion = self.set_alertmanager_access_info(username, password)
+ result = raise_if_exception(completion)
+ return HandleCommandResult(stdout=json.dumps(result))
+ except ArgumentError as e:
+ return HandleCommandResult(-errno.EINVAL, "", (str(e)))
+
+ @_cli_write_command('orch prometheus get-credentials')
def _get_prometheus_access_info(self) -> HandleCommandResult:
completion = self.get_prometheus_access_info()
access_info = raise_if_exception(completion)
return HandleCommandResult(stdout=json.dumps(access_info))
- @_cli_write_command('orch alertmanager access info')
+ @_cli_write_command('orch alertmanager get-credentials')
def _get_alertmanager_access_info(self) -> HandleCommandResult:
completion = self.get_alertmanager_access_info()
access_info = raise_if_exception(completion)
)
return self._daemon_add_misc(spec)
+ @_cli_write_command('orch daemon add nvmeof')
+ def _nvmeof_add(self,
+ pool: str,
+ placement: Optional[str] = None,
+ inbuf: Optional[str] = None) -> HandleCommandResult:
+ """Start nvmeof daemon(s)"""
+ if inbuf:
+ raise OrchestratorValidationError('unrecognized command -i; -h or --help for usage')
+
+ spec = NvmeofServiceSpec(
+ service_id='nvmeof',
+ pool=pool,
+ placement=PlacementSpec.from_string(placement),
+ )
+ return self._daemon_add_misc(spec)
+
@_cli_write_command('orch')
def _service_action(self, action: ServiceAction, service_name: str) -> HandleCommandResult:
"""Start, stop, restart, redeploy, or reconfig an entire service (i.e. all daemons)"""
realm: Optional[str] = None,
zonegroup: Optional[str] = None,
zone: Optional[str] = None,
+ networks: Optional[List[str]] = None,
port: Optional[int] = None,
ssl: bool = False,
dry_run: bool = False,
rgw_realm=realm,
rgw_zonegroup=zonegroup,
rgw_zone=zone,
+ networks=networks,
rgw_frontend_port=port,
ssl=ssl,
placement=PlacementSpec.from_string(placement),
return self._apply_misc([spec], dry_run, format, no_overwrite)
+ @_cli_write_command('orch apply nvmeof')
+ def _apply_nvmeof(self,
+ pool: str,
+ placement: Optional[str] = None,
+ unmanaged: bool = False,
+ dry_run: bool = False,
+ format: Format = Format.plain,
+ no_overwrite: bool = False,
+ inbuf: Optional[str] = None) -> HandleCommandResult:
+ """Scale an nvmeof service"""
+ if inbuf:
+ raise OrchestratorValidationError('unrecognized command -i; -h or --help for usage')
+
+ spec = NvmeofServiceSpec(
+ service_id=pool,
+ pool=pool,
+ placement=PlacementSpec.from_string(placement),
+ unmanaged=unmanaged,
+ preview_only=dry_run
+ )
+
+ spec.validate() # force any validation exceptions to be caught correctly
+
+ return self._apply_misc([spec], dry_run, format, no_overwrite)
+
@_cli_write_command('orch apply snmp-gateway')
def _apply_snmp_gateway(self,
snmp_version: SNMPGatewaySpec.SNMPVersion,