return None
-# def describe_service(self, service_type=None, service_id=None,
-# node_name=None, refresh=False):
-# if service_type not in ("mds", "osd", "mgr", "mon", 'rgw', "nfs", None):
-# raise orchestrator.OrchestratorValidationError(
-# service_type + " unsupported")
-# result = self._get_daemons(service_type,
-# service_id=service_id,
-# node_name=node_name,
-# refresh=refresh)
-# return result
+ def describe_service(self, service_type=None, service_name=None,
+ refresh=False):
+ if refresh:
+ # ugly sync path, FIXME someday perhaps?
+ for host, hi in self.inventory.items():
+ self._refresh_host_daemons(host)
+ sm = {} # type: Dict[str, orchestrator.ServiceDescription]
+ for h, dm in self.daemon_cache.data.items():
+ for name, dd in dm.items():
+ if service_type and service_type != dd.daemon_type:
+ continue
+ n = dd.service_name()
+ if service_name and service_name != n:
+ continue
+ if n not in sm:
+ sm[n] = orchestrator.ServiceDescription(
+ service_name=n,
+ last_refresh=dd.last_refresh,
+ container_image_id=dd.container_image_id,
+ container_image_name=dd.container_image_name,
+ )
+ sm[n].size += 1
+ if dd.status == 1:
+ sm[n].running += 1
+ if not sm[n].last_refresh or not dd.last_refresh or dd.last_refresh < sm[n].last_refresh: # type: ignore
+ sm[n].last_refresh = dd.last_refresh
+ if sm[n].container_image_id != dd.container_image_id:
+ sm[n].container_image_id = 'mix'
+ if sm[n].container_image_name != dd.container_image_name:
+ sm[n].container_image_name = 'mix'
+ return trivial_result([s for n, s in sm.items()])
def list_daemons(self, daemon_type=None, daemon_id=None,
host=None, refresh=False):
"""
raise NotImplementedError()
- def describe_service(self, service_type=None, service_id=None, node_name=None, refresh=False):
- # type: (Optional[str], Optional[str], Optional[str], bool) -> Completion
+ def describe_service(self, service_type=None, service_name=None, refresh=False):
+ # type: (Optional[str], Optional[str], bool) -> Completion
"""
Describe a service (of any kind) that is already configured in
the orchestrator. For example, when viewing an OSD in the dashboard
return self.name().startswith(service_name + '.')
return False
+ def service_name(self):
+ if self.daemon_type == 'rgw':
+ v = self.daemon_id.split('.')
+ return 'rgw.%s' % ('.'.join(v[0:2]))
+ if self.daemon_type in ['mds', 'nfs']:
+ return 'mds.%s' % (self.daemon_id.split('.')[0])
+ return self.daemon_type
+
def __repr__(self):
return "<DaemonDescription>({type}.{id})".format(type=self.daemon_type,
id=self.daemon_id)
has decided the service should run.
"""
- def __init__(self, nodename=None,
- container_id=None, container_image_id=None,
+ def __init__(self,
+ container_image_id=None,
container_image_name=None,
- service=None, service_instance=None,
- service_type=None, version=None, rados_config_location=None,
- service_url=None, status=None, status_desc=None):
- # Node is at the same granularity as InventoryNode
- self.nodename = nodename # type: Optional[str]
-
+ service_name=None,
+ rados_config_location=None,
+ service_url=None,
+ last_refresh=None,
+ size=0,
+ running=0):
# Not everyone runs in containers, but enough people do to
- # justify having the container_id (runtime id) and container_image
+ # justify having the container_image_id (image hash) and container_image
# (image name)
- self.container_id = container_id # runtime id
self.container_image_id = container_image_id # image hash
self.container_image_name = container_image_name # image friendly name
- # Some services can be deployed in groups. For example, mds's can
- # have an active and standby daemons, and nfs-ganesha can run daemons
- # in parallel. This tag refers to a group of daemons as a whole.
- #
- # For instance, a cluster of mds' all service the same fs, and they
- # will all have the same service value (which may be the
- # Filesystem name in the FSMap).
- #
- # Single-instance services should leave this set to None
- self.service = service
-
- # The orchestrator will have picked some names for daemons,
- # typically either based on hostnames or on pod names.
- # This is the <foo> in mds.<foo>, the ID that will appear
- # in the FSMap/ServiceMap.
- self.service_instance = service_instance
-
- # The type of service (osd, mon, mgr, etc.)
- self.service_type = service_type
-
- # Service version that was deployed
- self.version = version
+ # The service_name is either a bare type (e.g., 'mgr') or
+ # type.id combination (e.g., 'mds.fsname' or 'rgw.realm.zone').
+ self.service_name = service_name
# Location of the service configuration when stored in rados
# object. Format: "rados://<pool>/[<namespace/>]<object>"
# the URL.
self.service_url = service_url
- # Service status: -1 error, 0 stopped, 1 running
- self.status = status
+ # Number of daemons
+ self.size = size
- # Service status description when status == -1.
- self.status_desc = status_desc
+ # Number of daemons up
+ self.running = running
# datetime when this info was last refreshed
- self.last_refresh = None # type: Optional[datetime.datetime]
+ self.last_refresh = last_refresh # type: Optional[datetime.datetime]
- def name(self):
- if self.service_instance:
- return '%s.%s' % (self.service_type, self.service_instance)
- return self.service_type
+ def service_type(self):
+ if self.service_name:
+ return self.service_name.split('.')[0]
+ return None
def __repr__(self):
- return "<ServiceDescription>({n_name}:{s_type})".format(n_name=self.nodename,
- s_type=self.name())
+ return "<ServiceDescription>({name})".format(name=self.service_name)
def to_json(self):
out = {
- 'nodename': self.nodename,
- 'container_id': self.container_id,
- 'service': self.service,
- 'service_instance': self.service_instance,
- 'service_type': self.service_type,
- 'version': self.version,
+ 'container_image_id': self.container_image_id,
+ 'container_image_name': self.container_image_name,
+ 'service_name': self.service_name,
'rados_config_location': self.rados_config_location,
'service_url': self.service_url,
- 'status': self.status,
- 'status_desc': self.status_desc,
+ 'size': self.size,
+ 'running': self.running,
}
+ if self.last_refresh:
+ out['last_refresh'] = self.last_refresh.strftime(DATEFMT)
return {k: v for (k, v) in out.items() if v is not None}
@classmethod
@handle_type_error
def from_json(cls, data):
+ if 'last_refresh' in data:
+ data['last_refresh'] = datetime.datetime.strptime(
+ data['last_refresh'],
+ DATEFMT)
return cls(**data)
out.append(table.get_string())
return HandleCommandResult(stdout='\n'.join(out))
+ @_cli_read_command(
+ 'orch ls',
+ "name=service_type,type=CephString,req=false "
+ "name=service_name,type=CephString,req=false "
+ "name=format,type=CephChoices,strings=json|plain,req=false "
+ "name=refresh,type=CephBool,req=false",
+ 'List services known to orchestrator')
+ def _list_services(self, host=None, service_type=None, service_name=None, format='plain', refresh=False):
+ completion = self.describe_service(service_type,
+ service_name,
+ refresh=refresh)
+ self._orchestrator_wait([completion])
+ raise_if_exception(completion)
+ services = completion.result
+
+ def ukn(s):
+ return '<unknown>' if s is None else s
+
+ # Sort the list for display
+ services.sort(key=lambda s: (ukn(s.service_name)))
+
+ if len(services) == 0:
+ return HandleCommandResult(stdout="No services reported")
+ elif format == 'json':
+ data = [s.to_json() for s in services]
+ return HandleCommandResult(stdout=json.dumps(data))
+ else:
+ now = datetime.datetime.utcnow()
+ table = PrettyTable(
+ ['NAME', 'RUNNING', 'REFRESHED', 'IMAGE NAME', 'IMAGE ID'],
+ border=False)
+ table.align['NAME'] = 'l'
+ table.align['RUNNING'] = 'r'
+ table.align['REFRESHED'] = 'l'
+ table.align['IMAGE NAME'] = 'l'
+ table.align['IMAGE ID'] = 'l'
+ table.left_padding_width = 0
+ table.right_padding_width = 1
+ for s in sorted(services, key=lambda s: s.service_name):
+ if s.last_refresh:
+ age = to_pretty_timedelta(now - s.last_refresh) + ' ago'
+ else:
+ age = '-'
+ table.add_row((
+ s.service_name,
+ '%d/%d' % (s.running, s.size),
+ age,
+ ukn(s.container_image_name),
+ ukn(s.container_image_id)[0:12]))
+
+ return HandleCommandResult(stdout=table.get_string())
+
@_cli_read_command(
'orch ps',
"name=host,type=CephString,req=false "