)
if spec.service_type == 'ingress':
# ingress has 2 daemons running per host
- sm[nm].size *= 2
+ # but only if it's the full ingress service, not for keepalive-only
+ if not cast(IngressSpec, spec).keepalive_only:
+ sm[nm].size *= 2
# factor daemons into status
for h, dm in self.cache.get_daemons_with_volatile_status():
else None
),
allow_colo=svc.allow_colo(),
- primary_daemon_type=svc.primary_daemon_type(),
- per_host_daemon_type=svc.per_host_daemon_type(),
+ primary_daemon_type=svc.primary_daemon_type(spec),
+ per_host_daemon_type=svc.per_host_daemon_type(spec),
rank_map=rank_map,
)
"""
return False
- def primary_daemon_type(self) -> str:
+ def primary_daemon_type(self, spec: Optional[ServiceSpec] = None) -> str:
"""
This is the type of the primary (usually only) daemon to be deployed.
"""
return self.TYPE
- def per_host_daemon_type(self) -> Optional[str]:
+ def per_host_daemon_type(self, spec: Optional[ServiceSpec] = None) -> Optional[str]:
"""
If defined, this type of daemon will be deployed once for each host
containing one or more daemons of the primary type.
import string
from typing import List, Dict, Any, Tuple, cast, Optional
-from ceph.deployment.service_spec import IngressSpec
+from ceph.deployment.service_spec import ServiceSpec, IngressSpec
from mgr_util import build_url
from cephadm import utils
from orchestrator import OrchestratorError, DaemonDescription
TYPE = 'ingress'
MAX_KEEPALIVED_PASS_LEN = 8
- def primary_daemon_type(self) -> str:
+ def primary_daemon_type(self, spec: Optional[ServiceSpec] = None) -> str:
+ if spec:
+ ispec = cast(IngressSpec, spec)
+ # in keepalive only setups, we are only deploying keepalived,
+ # so that should be marked as the primary daemon type. Otherwise,
+ # we consider haproxy to be the primary.
+ if hasattr(spec, 'keepalive_only') and ispec.keepalive_only:
+ return 'keepalived'
return 'haproxy'
- def per_host_daemon_type(self) -> Optional[str]:
+ def per_host_daemon_type(self, spec: Optional[ServiceSpec] = None) -> Optional[str]:
+ if spec:
+ ispec = cast(IngressSpec, spec)
+ # if we are using "keepalive_only" mode on this ingress service
+ # we are only deploying keepalived daemons, so there should
+ # only be a primary daemon type and the per host daemon type
+ # should be empty
+ if hasattr(spec, 'keepalive_only') and ispec.keepalive_only:
+ return None
return 'keepalived'
def prepare_create(
daemons = self.mgr.cache.get_daemons_by_service(spec.service_name())
- if not daemons:
+ if not daemons and not spec.keepalive_only:
raise OrchestratorError(
f'Failed to generate keepalived.conf: No daemons deployed for {spec.service_name()}')
# create the RGW keyring
rgw_user = f'{rados_user}-rgw'
rgw_keyring = self.create_rgw_keyring(daemon_spec)
+ bind_addr = spec.virtual_ip if spec.virtual_ip else (daemon_spec.ip if daemon_spec.ip else '')
+ if not bind_addr:
+ logger.warning(f'Bind address in {daemon_type}.{daemon_id}\'s ganesha conf is defaulting to empty')
# generate the ganesha config
def get_ganesha_conf() -> str:
"url": f'rados://{POOL_NAME}/{spec.service_id}/{spec.rados_config_name()}',
# fall back to default NFS port if not present in daemon_spec
"port": daemon_spec.ports[0] if daemon_spec.ports else 2049,
- "bind_addr": daemon_spec.ip if daemon_spec.ip else '',
+ "bind_addr": bind_addr,
}
return self.mgr.template.render('services/nfs/ganesha.conf.j2', context)
from ceph.deployment.service_spec import NFSServiceSpec, PlacementSpec, IngressSpec
import orchestrator
+from orchestrator.module import IngressType
from .exception import NFSInvalidOperation, ClusterNotFound
from .utils import (available_clusters, restart_nfs_service, conf_obj_name,
def _call_orch_apply_nfs(
self,
cluster_id: str,
- placement: Optional[str],
+ placement: Optional[str] = None,
virtual_ip: Optional[str] = None,
+ ingress_mode: Optional[IngressType] = None,
port: Optional[int] = None,
) -> None:
if not port:
if virtual_ip:
# nfs + ingress
# run NFS on non-standard port
+ if not ingress_mode:
+ ingress_mode = IngressType.default
+ pspec = PlacementSpec.from_string(placement)
+ if ingress_mode == IngressType.keepalive_only:
+ # enforce count=1 for nfs over keepalive only
+ pspec.count = 1
spec = NFSServiceSpec(service_type='nfs', service_id=cluster_id,
- placement=PlacementSpec.from_string(placement),
+ placement=pspec,
# use non-default port so we don't conflict with ingress
- port=10000 + port) # semi-arbitrary, fix me someday
+ port=10000 + port if ingress_mode != IngressType.keepalive_only else port, # semi-arbitrary, fix me someday
+ virtual_ip=virtual_ip.split('/')[0] if ingress_mode == IngressType.keepalive_only else None)
completion = self.mgr.apply_nfs(spec)
orchestrator.raise_if_exception(completion)
ispec = IngressSpec(service_type='ingress',
service_id='nfs.' + cluster_id,
backend_service='nfs.' + cluster_id,
- frontend_port=port,
+ placement=pspec,
+ frontend_port=port if ingress_mode != IngressType.keepalive_only else None,
monitor_port=7000 + port, # semi-arbitrary, fix me someday
- virtual_ip=virtual_ip)
+ virtual_ip=virtual_ip,
+ keepalive_only=(ingress_mode == IngressType.keepalive_only))
completion = self.mgr.apply_ingress(ispec)
orchestrator.raise_if_exception(completion)
else:
placement: Optional[str],
virtual_ip: Optional[str],
ingress: Optional[bool] = None,
+ ingress_mode: Optional[IngressType] = None,
port: Optional[int] = None,
) -> Tuple[int, str, str]:
raise NFSInvalidOperation('virtual_ip can only be provided with ingress enabled')
if not virtual_ip and ingress:
raise NFSInvalidOperation('ingress currently requires a virtual_ip')
+ if ingress_mode and not ingress:
+ raise NFSInvalidOperation('--ingress-mode must be passed along with --ingress')
invalid_str = re.search('[^A-Za-z0-9-_.]', cluster_id)
if invalid_str:
raise NFSInvalidOperation(f"cluster id {cluster_id} is invalid. "
self.create_empty_rados_obj(cluster_id)
if cluster_id not in available_clusters(self.mgr):
- self._call_orch_apply_nfs(cluster_id, placement, virtual_ip, port)
+ self._call_orch_apply_nfs(cluster_id, placement, virtual_ip, ingress_mode, port)
return 0, "NFS Cluster Created Successfully", ""
return 0, "", f"{cluster_id} cluster already exists"
except Exception as e:
from mgr_module import MgrModule, CLICommand, Option, CLICheckNonemptyFileInput
import orchestrator
+from orchestrator.module import IngressType
from .export import ExportMgr
from .cluster import NFSCluster
placement: Optional[str] = None,
ingress: Optional[bool] = None,
virtual_ip: Optional[str] = None,
+ ingress_mode: Optional[IngressType] = None,
port: Optional[int] = None) -> Tuple[int, str, str]:
"""Create an NFS Cluster"""
return self.nfs.create_nfs_cluster(cluster_id=cluster_id, placement=placement,
virtual_ip=virtual_ip, ingress=ingress,
- port=port)
+ ingress_mode=ingress_mode, port=port)
@CLICommand('nfs cluster rm', perm='rw')
def _cmd_nfs_cluster_rm(self, cluster_id: str) -> Tuple[int, str, str]:
rotate_key = 'rotate-key'
+class IngressType(enum.Enum):
+ default = 'default'
+ keepalive_only = 'keepalive-only'
+
+
def to_format(what: Any, format: Format, many: bool, cls: Any) -> Any:
def to_json_1(obj: Any) -> Any:
if hasattr(obj, 'to_json'):
config: Optional[Dict[str, str]] = None,
networks: Optional[List[str]] = None,
port: Optional[int] = None,
+ virtual_ip: Optional[str] = None,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
custom_configs: Optional[List[CustomConfig]] = None,
extra_entrypoint_args=extra_entrypoint_args, custom_configs=custom_configs)
self.port = port
+ self.virtual_ip = virtual_ip
def get_port_start(self) -> List[int]:
if self.port:
virtual_interface_networks: Optional[List[str]] = [],
unmanaged: bool = False,
ssl: bool = False,
+ keepalive_only: bool = False,
extra_container_args: Optional[List[str]] = None,
extra_entrypoint_args: Optional[List[str]] = None,
custom_configs: Optional[List[CustomConfig]] = None,
self.virtual_interface_networks = virtual_interface_networks or []
self.unmanaged = unmanaged
self.ssl = ssl
+ self.keepalive_only = keepalive_only
def get_port_start(self) -> List[int]:
- return [cast(int, self.frontend_port),
- cast(int, self.monitor_port)]
+ ports = []
+ if self.frontend_port is not None:
+ ports.append(cast(int, self.frontend_port))
+ if self.monitor_port is not None:
+ ports.append(cast(int, self.monitor_port))
+ return ports
def get_virtual_ip(self) -> Optional[str]:
return self.virtual_ip
if not self.backend_service:
raise SpecValidationError(
'Cannot add ingress: No backend_service specified')
- if not self.frontend_port:
+ if not self.keepalive_only and not self.frontend_port:
raise SpecValidationError(
'Cannot add ingress: No frontend_port specified')
if not self.monitor_port: