def resolve_config_files(plugin, release): ''' Resolve configuration files and contexts :param plugin: shortname of plugin e.g. ovs :param release: openstack release codename :returns: dict of configuration files, contexts and associated services ''' config_files = deepcopy(CONFIG_FILES) if plugin == OVS: # NOTE: deal with switch to ML2 plugin for >= icehouse drop_config = [NEUTRON_OVS_AGENT_CONF] if release >= 'mitaka': # ml2 -> ovs_agent drop_config = [NEUTRON_ML2_PLUGIN_CONF] for _config in drop_config: if _config in config_files[plugin]: config_files[plugin].pop(_config) if is_relation_made('amqp-nova'): amqp_nova_ctxt = context.AMQPContext(ssl_dir=NOVA_CONF_DIR, rel_name='amqp-nova', relation_prefix='nova') else: amqp_nova_ctxt = context.AMQPContext(ssl_dir=NOVA_CONF_DIR, rel_name='amqp') config_files[plugin][NOVA_CONF]['hook_contexts'].append(amqp_nova_ctxt) return config_files
def register_configs(): ''' Register config files with their respective contexts. ''' release = get_os_codename_install_source(config('openstack-origin')) configs = templating.OSConfigRenderer(templates_dir=TEMPLATES, openstack_release=release) plugin = remap_plugin(config('plugin')) name = networking_name() if plugin == 'ovs': # NOTE: deal with switch to ML2 plugin for >= icehouse drop_config = NEUTRON_ML2_PLUGIN_CONF if release >= 'icehouse': drop_config = NEUTRON_OVS_PLUGIN_CONF if drop_config in CONFIG_FILES[name][plugin]: CONFIG_FILES[name][plugin].pop(drop_config) if is_relation_made('amqp-nova'): amqp_nova_ctxt = context.AMQPContext( ssl_dir=NOVA_CONF_DIR, rel_name='amqp-nova', relation_prefix='nova') else: amqp_nova_ctxt = context.AMQPContext( ssl_dir=NOVA_CONF_DIR, rel_name='amqp') CONFIG_FILES[name][plugin][NOVA_CONF][ 'hook_contexts'].append(amqp_nova_ctxt) for conf in CONFIG_FILES[name][plugin]: configs.register(conf, CONFIG_FILES[name][plugin][conf]['hook_contexts']) return configs
def resolve_config_files(plugin, release): ''' Resolve configuration files and contexts :param plugin: shortname of plugin e.g. ovs :param release: openstack release codename :returns: dict of configuration files, contexts and associated services ''' config_files = deepcopy(get_config_files()) drop_config = [] cmp_os_release = CompareOpenStackReleases(release) if plugin == OVS: # NOTE: deal with switch to ML2 plugin for >= icehouse drop_config = [NEUTRON_OVS_AGENT_CONF] if cmp_os_release >= 'mitaka': # ml2 -> ovs_agent drop_config = [NEUTRON_ML2_PLUGIN_CONF] # Use MAAS1.9 for MTU and external port config on xenial and above if CompareHostReleases(lsb_release()['DISTRIB_CODENAME']) >= 'xenial': drop_config.extend([EXT_PORT_CONF, PHY_NIC_MTU_CONF]) # Rename to lbaasv2 in newton if cmp_os_release < 'newton': drop_config.extend([NEUTRON_LBAASV2_AA_PROFILE_PATH]) else: drop_config.extend([NEUTRON_LBAAS_AA_PROFILE_PATH]) # Drop lbaasv2 at train # or drop if disable-lbaas option is true if disable_neutron_lbaas(): if cmp_os_release >= 'newton': drop_config.extend([ NEUTRON_LBAASV2_AA_PROFILE_PATH, NEUTRON_LBAAS_AGENT_CONF, ]) else: drop_config.extend([ NEUTRON_LBAAS_AA_PROFILE_PATH, NEUTRON_LBAAS_AGENT_CONF, ]) if disable_nova_metadata(cmp_os_release): drop_config.extend(get_nova_config_files().keys()) else: if is_relation_made('amqp-nova'): amqp_nova_ctxt = context.AMQPContext(ssl_dir=NOVA_CONF_DIR, rel_name='amqp-nova', relation_prefix='nova') else: amqp_nova_ctxt = context.AMQPContext(ssl_dir=NOVA_CONF_DIR, rel_name='amqp') config_files[plugin][NOVA_CONF]['hook_contexts'].append(amqp_nova_ctxt) for _config in drop_config: if _config in config_files[plugin]: config_files[plugin].pop(_config) return config_files
def is_cellv2_init_ready(): """Determine if we're ready to initialize the cell v2 databases Cells v2 init requires transport_url and database connections to be set in nova.conf. """ amqp = context.AMQPContext() shared_db = nova_cc_context.NovaCellV2SharedDBContext() if (CompareOpenStackReleases(os_release('nova-common')) >= 'ocata' and amqp() and shared_db()): return True log("OpenStack release, database, or rabbitmq not ready for Cells V2", level=DEBUG) return False
def resource_map(): rm = OrderedDict([ (ASTARA_CONFIG, { 'services': ['astara-orchestrator'], 'contexts': [ astara_context.AstaraOrchestratorContext(), context.AMQPContext(), context.SharedDBContext(), context.IdentityServiceContext( service='astara', service_user='******'), ], }) ]) return rm
context.IdentityServiceContext(service='glance', service_user='******'), context.SyslogContext(), glance_contexts.LoggingConfigContext(), glance_contexts.GlanceIPv6Context(), context.WorkerConfigContext(), context.OSConfigFlagContext(charm_flag='registry-config-flags', template_flag='registry_config_flags'), context.MemcacheContext() ], 'services': ['glance-registry'] }), (GLANCE_API_CONF, { 'hook_contexts': [ context.SharedDBContext(ssl_dir=GLANCE_CONF_DIR), context.AMQPContext(ssl_dir=GLANCE_CONF_DIR), context.IdentityServiceContext(service='glance', service_user='******'), glance_contexts.GlanceContext(), glance_contexts.CephGlanceContext(), glance_contexts.ObjectStoreContext(), glance_contexts.CinderStoreContext(), glance_contexts.HAProxyContext(), context.SyslogContext(), glance_contexts.LoggingConfigContext(), glance_contexts.GlanceIPv6Context(), context.WorkerConfigContext(), glance_contexts.MultiStoreContext(), context.OSConfigFlagContext(charm_flag='api-config-flags', template_flag='api_config_flags'), context.InternalEndpointContext('glance-common'),
"""Provide the required charm interfaces based on configured roles.""" _interfaces = copy(REQUIRED_INTERFACES) if not service_enabled('api'): # drop requirement for identity interface _interfaces.pop('identity') return _interfaces # Map config files to hook contexts and services that will be associated # with file in restart_on_changes()'s service map. BASE_RESOURCE_MAP = OrderedDict([ (CINDER_CONF, { 'contexts': [ context.SharedDBContext(ssl_dir=CINDER_CONF_DIR), context.AMQPContext(ssl_dir=CINDER_CONF_DIR), context.ImageServiceContext(), context.OSConfigFlagContext(), context.SyslogContext(), cinder_contexts.CephContext(), cinder_contexts.HAProxyContext(), cinder_contexts.ImageServiceContext(), cinder_contexts.CinderSubordinateConfigContext( interface=['storage-backend', 'backup-backend'], service='cinder', config_file=CINDER_CONF), cinder_contexts.StorageBackendContext(), cinder_contexts.LoggingConfigContext(), context.IdentityServiceContext(service='cinder', service_user='******'), context.BindHostContext(),
:returns: True or False :rtype: bool """ db = kv() return db.get(USE_FQDN_KEY, False) BASE_RESOURCE_MAP = OrderedDict([ (NEUTRON_CONF, { 'services': ['neutron-plugin-openvswitch-agent'], 'contexts': [ neutron_ovs_context.OVSPluginContext(), neutron_ovs_context.RemoteRestartContext( ['neutron-plugin', 'neutron-control']), context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR), context.ZeroMQContext(), context.NotificationDriverContext(), context.HostInfoContext(use_fqdn_hint_cb=use_fqdn_hint), neutron_ovs_context.ZoneContext(), ], }), (ML2_CONF, { 'services': ['neutron-plugin-openvswitch-agent'], 'contexts': [neutron_ovs_context.OVSPluginContext()], }), (OVS_CONF, { 'services': ['neutron-openvswitch-agent'], 'contexts': [neutron_ovs_context.OVSPluginContext()], }), (OVS_DEFAULT, {
os_rel = os_release('nova-common') for release in SERVICE_BLACKLIST: if os_rel >= release or config('disable-aws-compat'): [ _services.remove(service) for service in SERVICE_BLACKLIST[release] ] return _services BASE_RESOURCE_MAP = OrderedDict([ (NOVA_CONF, { 'services': resolve_services(), 'contexts': [ context.AMQPContext(ssl_dir=NOVA_CONF_DIR), context.SharedDBContext(relation_prefix='nova', ssl_dir=NOVA_CONF_DIR), context.OSConfigFlagContext(charm_flag='nova-alchemy-flags', template_flag='nova_alchemy_flags'), context.ImageServiceContext(), context.OSConfigFlagContext(), context.SubordinateConfigContext(interface='nova-vmware', service='nova', config_file=NOVA_CONF), nova_cc_context.NovaCellContext(), context.SyslogContext(), context.LogLevelContext(), nova_cc_context.HAProxyContext(), nova_cc_context.IdentityServiceContext(service='nova', service_user='******'),
FELIX_CONF = FELIX_CONF_DIR + '/felix.cfg' DHCP_CONF = "%s/dhcp_agent.ini" % NEUTRON_CONF_DIR BIRD_CONF_DIR = "/etc/bird" BIRD_CONF = "%s/bird.conf" % BIRD_CONF_DIR BIRD6_CONF = "%s/bird6.conf" % BIRD_CONF_DIR DHCP_AGENT = 'neutron-dhcp-agent' if get_os_codename_install_source(config('openstack-origin')) >= 'liberty': DHCP_AGENT = 'calico-dhcp-agent' BASE_RESOURCE_MAP = OrderedDict([ (NEUTRON_CONF, { 'services': ['calico-felix', DHCP_AGENT, 'nova-api-metadata'], 'contexts': [neutron_calico_context.CalicoPluginContext(), context.AMQPContext()], }), (DHCP_CONF, { 'services': [DHCP_AGENT], 'contexts': [neutron_calico_context.CalicoPluginContext()], }) ]) BIRD_RESOURCE_MAP = { 'services': ['bird'], 'contexts': [neutron_calico_context.CalicoPluginContext()], } BIRD6_RESOURCE_MAP = { 'services': ['bird6'], 'contexts': [neutron_calico_context.CalicoPluginContext()], } TEMPLATES = 'templates/'
NOVA_API_PASTE = '%s/api-paste.ini' % NOVA_CONF_DIR QUANTUM_CONF = '%s/quantum.conf' % QUANTUM_CONF_DIR QUANTUM_API_PASTE = '%s/api-paste.ini' % QUANTUM_CONF_DIR NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend' APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf' NEUTRON_DEFAULT = '/etc/default/neutron-server' QUANTUM_DEFAULT = '/etc/default/quantum-server' BASE_RESOURCE_MAP = OrderedDict([ (NOVA_CONF, { 'services': BASE_SERVICES, 'contexts': [ context.AMQPContext(ssl_dir=NOVA_CONF_DIR), context.SharedDBContext(relation_prefix='nova', ssl_dir=NOVA_CONF_DIR), context.OSConfigFlagContext(charm_flag='nova-alchemy-flags', template_flag='nova_alchemy_flags'), nova_cc_context.NovaPostgresqlDBContext(), context.ImageServiceContext(), context.OSConfigFlagContext(), context.SubordinateConfigContext(interface='nova-vmware', service='nova', config_file=NOVA_CONF), nova_cc_context.NovaCellContext(), context.SyslogContext(), context.LogLevelContext(), nova_cc_context.HAProxyContext(), nova_cc_context.IdentityServiceContext(service='nova',
def get_config_files(): global __CONFIG_FILES if __CONFIG_FILES is not None: return __CONFIG_FILES NOVA_CONFIG_FILES = get_nova_config_files() NEUTRON_SHARED_CONFIG_FILES = { NEUTRON_DHCP_AGENT_CONF: { 'hook_contexts': [DHCPAgentContext()], 'services': ['neutron-dhcp-agent'] }, NEUTRON_DNSMASQ_CONF: { 'hook_contexts': [DHCPAgentContext()], 'services': ['neutron-dhcp-agent'] }, NEUTRON_METADATA_AGENT_CONF: { 'hook_contexts': [ NetworkServiceContext(), DHCPAgentContext(), context.WorkerConfigContext(), NeutronGatewayContext(), NovaMetadataContext() ], 'services': ['neutron-metadata-agent'] }, NEUTRON_DHCP_AA_PROFILE_PATH: { 'services': ['neutron-dhcp-agent'], 'hook_contexts': [context.AppArmorContext(NEUTRON_DHCP_AA_PROFILE)], }, NEUTRON_LBAAS_AA_PROFILE_PATH: { 'services': ['neutron-lbaas-agent'], 'hook_contexts': [context.AppArmorContext(NEUTRON_LBAAS_AA_PROFILE)], }, NEUTRON_LBAASV2_AA_PROFILE_PATH: { 'services': ['neutron-lbaasv2-agent'], 'hook_contexts': [context.AppArmorContext(NEUTRON_LBAASV2_AA_PROFILE)], }, NEUTRON_METADATA_AA_PROFILE_PATH: { 'services': ['neutron-metadata-agent'], 'hook_contexts': [context.AppArmorContext(NEUTRON_METADATA_AA_PROFILE)], }, NEUTRON_METERING_AA_PROFILE_PATH: { 'services': ['neutron-metering-agent'], 'hook_contexts': [context.AppArmorContext(NEUTRON_METERING_AA_PROFILE)], }, } NEUTRON_SHARED_CONFIG_FILES.update(NOVA_CONFIG_FILES) NEUTRON_OVS_CONFIG_FILES = { NEUTRON_CONF: { 'hook_contexts': [ context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR), NeutronGatewayContext(), SyslogContext(), context.ZeroMQContext(), context.WorkerConfigContext(), context.NotificationDriverContext() ], 'services': [ 'neutron-l3-agent', 'neutron-dhcp-agent', 'neutron-metadata-agent', 'neutron-plugin-openvswitch-agent', 'neutron-plugin-metering-agent', 'neutron-metering-agent', 'neutron-lbaas-agent', 'neutron-vpn-agent' ] }, NEUTRON_L3_AGENT_CONF: { 'hook_contexts': [ NetworkServiceContext(), L3AgentContext(), NeutronGatewayContext() ], 'services': ['neutron-l3-agent', 'neutron-vpn-agent'] }, NEUTRON_METERING_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-plugin-metering-agent', 'neutron-metering-agent'] }, NEUTRON_LBAAS_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-lbaas-agent'] }, NEUTRON_VPNAAS_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-vpn-agent'] }, NEUTRON_FWAAS_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-l3-agent', 'neutron-vpn-agent'] }, NEUTRON_ML2_PLUGIN_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-plugin-openvswitch-agent'] }, NEUTRON_OVS_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-plugin-openvswitch-agent'] }, NEUTRON_OVS_AA_PROFILE_PATH: { 'services': ['neutron-plugin-openvswitch-agent'], 'hook_contexts': [context.AppArmorContext(NEUTRON_OVS_AA_PROFILE)], }, NEUTRON_L3_AA_PROFILE_PATH: { 'services': ['neutron-l3-agent', 'neutron-vpn-agent'], 'hook_contexts': [context.AppArmorContext(NEUTRON_L3_AA_PROFILE)], }, EXT_PORT_CONF: { 'hook_contexts': [ExternalPortContext()], 'services': ['ext-port'] }, PHY_NIC_MTU_CONF: { 'hook_contexts': [PhyNICMTUContext()], 'services': ['os-charm-phy-nic-mtu'] } } NEUTRON_OVS_CONFIG_FILES.update(NEUTRON_SHARED_CONFIG_FILES) NEUTRON_OVS_ODL_CONFIG_FILES = { NEUTRON_CONF: { 'hook_contexts': [ context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR), NeutronGatewayContext(), SyslogContext(), context.ZeroMQContext(), context.WorkerConfigContext(), context.NotificationDriverContext() ], 'services': [ 'neutron-l3-agent', 'neutron-dhcp-agent', 'neutron-metadata-agent', 'neutron-plugin-metering-agent', 'neutron-metering-agent', 'neutron-lbaas-agent', 'neutron-vpn-agent' ] }, NEUTRON_L3_AGENT_CONF: { 'hook_contexts': [ NetworkServiceContext(), L3AgentContext(), NeutronGatewayContext() ], 'services': ['neutron-l3-agent', 'neutron-vpn-agent'] }, NEUTRON_METERING_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-plugin-metering-agent', 'neutron-metering-agent'] }, NEUTRON_LBAAS_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-lbaas-agent'] }, NEUTRON_VPNAAS_AGENT_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-vpn-agent'] }, NEUTRON_FWAAS_CONF: { 'hook_contexts': [NeutronGatewayContext()], 'services': ['neutron-l3-agent', 'neutron-vpn-agent'] }, EXT_PORT_CONF: { 'hook_contexts': [ExternalPortContext()], 'services': ['ext-port'] }, PHY_NIC_MTU_CONF: { 'hook_contexts': [PhyNICMTUContext()], 'services': ['os-charm-phy-nic-mtu'] } } NEUTRON_OVS_ODL_CONFIG_FILES.update(NEUTRON_SHARED_CONFIG_FILES) NEUTRON_NSX_CONFIG_FILES = { NEUTRON_CONF: { 'hook_contexts': [ context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR), NeutronGatewayContext(), context.WorkerConfigContext(), SyslogContext() ], 'services': ['neutron-dhcp-agent', 'neutron-metadata-agent'] }, } NEUTRON_NSX_CONFIG_FILES.update(NEUTRON_SHARED_CONFIG_FILES) NEUTRON_N1KV_CONFIG_FILES = { NEUTRON_CONF: { 'hook_contexts': [ context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR), NeutronGatewayContext(), context.WorkerConfigContext(), SyslogContext() ], 'services': [ 'neutron-l3-agent', 'neutron-dhcp-agent', 'neutron-metadata-agent' ] }, NEUTRON_L3_AGENT_CONF: { 'hook_contexts': [ NetworkServiceContext(), L3AgentContext(), NeutronGatewayContext() ], 'services': ['neutron-l3-agent'] }, } NEUTRON_N1KV_CONFIG_FILES.update(NEUTRON_SHARED_CONFIG_FILES) __CONFIG_FILES = { NSX: NEUTRON_NSX_CONFIG_FILES, OVS: NEUTRON_OVS_CONFIG_FILES, N1KV: NEUTRON_N1KV_CONFIG_FILES, OVS_ODL: NEUTRON_OVS_ODL_CONFIG_FILES } return __CONFIG_FILES
'database': ['mongodb'], 'messaging': ['amqp'], 'identity': ['identity-service'], } CEILOMETER_ROLE = "ResellerAdmin" SVC = 'ceilometer' WSGI_CEILOMETER_API_CONF = '/etc/apache2/sites-enabled/wsgi-openstack-api.conf' PACKAGE_CEILOMETER_API_CONF = '/etc/apache2/sites-enabled/ceilometer-api.conf' QUEENS_CONFIG_FILES = OrderedDict([ (CEILOMETER_CONF, { 'hook_contexts': [ context.IdentityCredentialsContext(service=SVC, service_user=SVC), context.AMQPContext(ssl_dir=CEILOMETER_CONF_DIR), LoggingConfigContext(), MongoDBContext(), CeilometerContext(), context.SyslogContext(), context.MemcacheContext(), MetricServiceContext(), context.WorkerConfigContext()], 'services': QUEENS_SERVICES }), ]) CONFIG_FILES = OrderedDict([ (CEILOMETER_CONF, { 'hook_contexts': [context.IdentityServiceContext(service=SVC, service_user=SVC),
'hook_contexts': [ context.AppArmorContext(NEUTRON_METADATA_AA_PROFILE) ], }, NEUTRON_METERING_AA_PROFILE_PATH: { 'services': ['neutron-metering-agent'], 'hook_contexts': [ context.AppArmorContext(NEUTRON_METERING_AA_PROFILE) ], }, } NEUTRON_SHARED_CONFIG_FILES.update(NOVA_CONFIG_FILES) NEUTRON_OVS_CONFIG_FILES = { NEUTRON_CONF: { 'hook_contexts': [context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR), NeutronGatewayContext(), SyslogContext(), context.ZeroMQContext(), context.WorkerConfigContext(), context.NotificationDriverContext()], 'services': ['neutron-l3-agent', 'neutron-dhcp-agent', 'neutron-metadata-agent', 'neutron-plugin-openvswitch-agent', 'neutron-plugin-metering-agent', 'neutron-metering-agent', 'neutron-lbaas-agent', 'neutron-vpn-agent'] }, NEUTRON_L3_AGENT_CONF: {
MITAKA_PACKAGES = FOLSOM_PACKAGES + ['python-ceilometermiddleware'] SWIFT_HA_RES = 'grp_swift_vips' TEMPLATES = 'templates/' # Map config files to hook contexts and services that will be associated # with file in restart_on_changes()'s service map. CONFIG_FILES = OrderedDict([ (SWIFT_CONF, { 'hook_contexts': [SwiftHashContext()], 'services': ['swift-proxy'], }), (SWIFT_PROXY_CONF, { 'hook_contexts': [SwiftIdentityContext(), context.BindHostContext(), context.AMQPContext(ssl_dir=SWIFT_CONF_DIR)], 'services': ['swift-proxy'], }), (HAPROXY_CONF, { 'hook_contexts': [context.HAProxyContext(singlenode_mode=True), HAProxyContext()], 'services': ['haproxy'], }), (SWIFT_RINGS_CONF, { 'hook_contexts': [SwiftRingContext()], 'services': ['apache2'], }), (SWIFT_RINGS_24_CONF, { 'hook_contexts': [SwiftRingContext()], 'services': ['apache2'], }),
# Cluster resource used to determine leadership when hacluster'd CLUSTER_RES = 'grp_heat_vips' SVC = 'heat' HEAT_DIR = '/etc/heat' HEAT_CONF = '/etc/heat/heat.conf' HEAT_API_PASTE = '/etc/heat/api-paste.ini' HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' HTTPS_APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend' HTTPS_APACHE_24_CONF = os.path.join('/etc/apache2/sites-available', 'openstack_https_frontend.conf') ADMIN_OPENRC = '/root/admin-openrc-v3' CONFIG_FILES = OrderedDict([ (HEAT_CONF, { 'services': BASE_SERVICES, 'contexts': [context.AMQPContext(ssl_dir=HEAT_DIR), context.SharedDBContext(relation_prefix='heat', ssl_dir=HEAT_DIR), context.OSConfigFlagContext(), HeatIdentityServiceContext(service=SVC, service_user=SVC), HeatHAProxyContext(), HeatSecurityContext(), InstanceUserContext(), context.SyslogContext(), context.LogLevelContext(), context.WorkerConfigContext(), context.BindHostContext(), ContrailAPIContext()] }), (HEAT_API_PASTE, { 'services': [s for s in BASE_SERVICES if 'api' in s],