text: import logging
  import re
  from functools import lru_cache
  
  from django.conf import settings
  
  from ralph.lib import network
  from ralph.settings import DEFAULT_OPENSTACK_PROVIDER_NAME
  
  try:
      from keystoneclient.v2_0 import client as ks_v2_client
      from keystoneclient.v3 import client as ks_v3_client
      from keystoneauth1 import session as ks_session
      from keystoneauth1.identity import v3 as ks_v3_identity
      keystone_client_exists = True
  except ImportError:
      keystone_client_exists = False
  
  try:
      from novaclient import client as novac
      from novaclient.exceptions import NotFound
      nova_client_exists = True
  except ImportError:
      nova_client_exists = False
  
  
  try:
      from ironicclient.client import get_client as get_ironic_client
      ironic_client_exists = True
  except ImportError:
      ironic_client_exists = False
  
  
  logger = logging.getLogger(__name__)
  
  
  class EmptyListError(Exception):
      def __init___(self, value):
          self.value = value
  
      def __str__(self):
          repr(self.value)
  
  
  class RalphIronicClient:
  
      def __init__(self, os_conf):
          if not ironic_client_exists:
              logger.error("ironicclient module is not installed")
              raise ImportError("No module named ironicclient")
          self.ironic_client = get_ironic_client(
              api_version=os_conf.get('ironic-api-version', '1'),
              os_username=os_conf['username'],
              os_password=os_conf['password'],
              os_tenant_name=os_conf['tenant_name'],
              os_auth_url=os_conf['auth_url']
          )
  
      def get_nodes_list(self, associated=True, fields=None):
          if not fields:
              fields = ['extra', 'instance_uuid']
          return self.ironic_client.node.list(
              associated=associated,
              fields=fields
          )
  
  
  class RalphOpenstackClient:
      """
      This is a single OpenStack instance client. It handles keystone
      authentication. It has methods to fetch OpenStack data useful from
      Ralph's perspective from openstack compute and openstack keystone.
      """
      def __init__(self, site):
          if not nova_client_exists:
              logger.error("novaclient module is not installed")
              raise ImportError("No module named novaclient")
          if not keystone_client_exists:
              logger.error("keystoneclient module is not installed")
              raise ImportError("No module named keystoneclient")
  
          self.session = self._get_keystone_session(site)
          self.nova_client = self._get_nova_client_connection(site)
          self.keystone_client = self._get_keystone_client(site)
          self.site = site
  
      def _get_nova_client_connection(self, site):
          if (
              site.get('keystone_auth_url') and
              site.get('keystone_version', '').startswith('3')
          ):
              nt = novac.Client(site['version'], session=self.session)
          else:
              nt = novac.Client(
                  site['version'],
                  site['username'],
                  site['password'],
                  site['tenant_name'],
                  site['auth_url']
              )
          return nt
  
      def _get_keystone_client(self, site):
          if site.get('keystone_version', '').startswith('3'):
              client = ks_v3_client.Client(session=self.session, version=(3,))
          else:
              client = ks_v2_client.Client(
                  username=site['username'],
                  password=site['password'],
                  tenant_name=site['tenant_name'],
                  auth_url=site['auth_url'],
              )
          return client
  
      @staticmethod
      def _get_keystone_session(site):
          auth = ks_v3_identity.Password(
              auth_url=site.get('keystone_auth_url', site['auth_url']),
              username=site['username'],
              password=site['password'],
              user_domain_name=site.get('user_domain_name', 'default'),
              project_name=site['tenant_name'],
              project_domain_name=site.get('project_domain_name', 'default'),
          )
          return ks_session.Session(auth=auth)
  
      @lru_cache()
      def _get_images(self):
          logger.info('Fetching images')
          return {img.id: img.__dict__ for img in self.nova_client.images.list()}
  
      def get_image_name(self, image_id):
          try:
              return self._get_images()[image_id]['name']
          except KeyError:
              try:
                  return self.nova_client.images.get(image_id).name
              except NotFound:
                  return ''
  
      def get_flavors_list(self):
          """
          Return list of flavours
          """
          logger.info('Fetching flavors list from {}'.format(self.site['tag']))
          flavors = []
          for is_public in (True, False):
              flavors.extend(list(map(
                  lambda fl: fl.__dict__, self.nova_client.flavors.list(
                      is_public=is_public
                  )
              )))
          if len(flavors) == 0:
              raise EmptyListError(
                  'Got an empty list of flavors from {}'.format(
                      self.site['auth_url']
                  )
              )
          return flavors
  
      def get_servers_list(self, search_opts=None):
          """
          Returns list of servers for a project.
          """
          if not search_opts:
              search_opts = {}
          servers = []
          marker = None
          limit = 1000
          logger.info('Fetching servers list from {}'.format(self.site['tag']))
          while True:
              try:
                  logger.debug(
                      'Fetching servers with marker {}'.format(marker)
                  )
                  servers_part = self.nova_client.servers.list(
                      search_opts=search_opts,
                      limit=limit,
                      marker=marker,
                  )
                  marker = servers_part[-1].id
                  servers.extend(servers_part)
                  if len(servers_part) < limit:
                      break
              except IndexError:
                  break
  
          servers = list(map(lambda server: server.__dict__, servers))
          # If only an incremental update is being performed, it is possible
          # that no servers are returned. Otherwise, zero servers is an error.
          if len(servers) == 0 and not search_opts.get('changes-since'):
              raise EmptyListError(
                  'Got an empty list of instances from {}'.format(
                      self.site['auth_url']
                  )
              )
          logger.info('Fetched {} servers from {}'.format(
              len(servers), self.site['tag']
          ))
          return servers
  
      def get_keystone_projects(self):
          if self.keystone_client.version == 'v3':
              projects_resource = self.keystone_client.projects
          else:
              projects_resource = self.keystone_client.tenants
          yield from projects_resource.list()
  
  
  class RalphOpenStackInfrastructureClient:
      """
      This is an OpenStack client designed to connect with multiple (or all)
      OpenStack instances that may exist in your local infrastructure.
      It instantiates a separate RalphOpenStackClient instance to communicate
      with each OpenStack instance. It has methods to extract
      and accumulate data from all instances in one place.
      """
      def __init__(self, openstack_provider_name):
          super().__init__()
          self.DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
          self.openstack_provider_name = openstack_provider_name
          self.clients = self._get_instances_from_settings()
  
      def _get_instances_from_settings(self):
          """
          Filter instances from OPENSTACK_INSTANCES (from settings) and
          initialize clients only for the ones matching current
          openstack provider.
          """
          clients = []
          for os_instance in settings.OPENSTACK_INSTANCES:
              if os_instance.get(
                  'provider', DEFAULT_OPENSTACK_PROVIDER_NAME
              ) == self.openstack_provider_name:
                  clients.append(RalphOpenstackClient(os_instance))
          return clients
  
      @classmethod
      def _get_flavor_data(cls, client, flavor):
          return {
              'name': flavor['name'],
              'cores': flavor['vcpus'],
              'memory': flavor['ram'],
              'disk': flavor['disk'] * 1024,
              'tag': client.site['tag'],
          }
  
      def get_openstack_flavors(self):
          openstack_flavors = {}
          for client in self.clients:
              logger.info('Processing {} ({})'.format(
                  client.site['auth_url'], client.site['tag']
              ))
              for flavor in client.get_flavors_list():
                  openstack_flavors[flavor['id']] = self._get_flavor_data(
                      client, flavor
                  )
          return openstack_flavors
  
      def get_openstack_projects(self):
          openstack_projects = {}
          for client in self.clients:
              logger.info('Processing {} ({})'.format(
                  client.site['auth_url'], client.site['tag']
              ))
              for project in client.get_keystone_projects():
                  if project.id not in openstack_projects:
                      openstack_projects[project.id] = {
                          'name': project.name,
                          'servers': {},
                          'tags': []
                      }
                  openstack_projects[project.id]['tags'].append(
                      client.site['tag']
                  )
          return openstack_projects
  
      def get_openstack_instances_data(
          self, openstack_projects, openstack_flavors, search_opts=None
      ):
          """
          Take openstack_projects dictionary and populate it with instances
          (servers). If any flavor is missing, add it to the openstack_flavors
          dictionary.
  
          :param openstack_flavors: dictionary of openstack flavors
          :param openstack_projects: dictionary of openstack projects
          :return: updated openstack_projects and openstack_flavors
          :param search_opts: dictionary of search options
          """
          default_search_opts = {'all_tenants': True}
          if not search_opts:
              search_opts = {}
          search_opts.update(default_search_opts)
  
          for client in self.clients:
              logger.info('Processing {} ({})'.format(
                  client.site['auth_url'], client.site['tag']
              ))
              for server in client.get_servers_list(search_opts=search_opts):
                  project_id = server['tenant_id']
                  host_id = server['id']
                  image_name = client.get_image_name(
                       server['image']['id']
                  ) if server['image'] else None
                  flavor_id = server['flavor']['id']
                  new_server = {
                      'hostname': None,
                      'id': server['id'],
                      'flavor_id': flavor_id,
                      'tag': client.site['tag'],
                      'ips': {},
                      'created': server['created'],
                      'hypervisor': server['OS-EXT-SRV-ATTR:hypervisor_hostname'],  # noqa: E501
                      'image': image_name,
                      'status': server['status']
                  }
                  for zone in server['addresses']:
                      if (
                          'network_regex' in client.site and
                          not re.match(client.site['network_regex'], zone)
                      ):
                          continue
                      for ip in server['addresses'][zone]:
                          addr = ip['addr']
                          # fetch FQDN from DNS by IP address
                          hostname = network.hostname(addr)
                          logger.debug('Get IP {} ({}) for {}'.format(
                              addr, hostname, server['id']
                          ))
                          new_server['ips'][addr] = hostname
                          if not new_server['hostname']:
                              new_server['hostname'] = hostname
                  # fallback to default behavior if FQDN could not be fetched
                  # from DNS
                  new_server['hostname'] = (
                      new_server['hostname'] or server['name']
                  )
                  try:
                      openstack_projects[project_id]['servers'][host_id] = (
                          new_server
                      )
                  except KeyError:
                      logger.warning(
                          'Project %s not found for server %s',
                          project_id, host_id
                      )
  
                  if flavor_id not in openstack_flavors:
                      logger.warning(
                          'Flavor %s (found in host %s) not in flavors list.'
                          ' Fetching it', flavor_id, host_id)
                      flavor = client.nova_client.flavors.get(flavor_id).__dict__
                      openstack_flavors[flavor['id']] = self._get_flavor_data(
                          client,
                          flavor,
                      )
          return openstack_projects, openstack_flavors
  