text: # Copyright 2015 Infoblox Inc.
  # All Rights Reserved.
  #
  #    Licensed under the Apache License, Version 2.0 (the "License"); you may
  #    not use this file except in compliance with the License. You may obtain
  #    a copy of the License at
  #
  #         http://www.apache.org/licenses/LICENSE-2.0
  #
  #    Unless required by applicable law or agreed to in writing, software
  #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  #    License for the specific language governing permissions and limitations
  #    under the License.
  
  import six
  
  try:
      from oslo_log import log as logging
  except ImportError:  # pragma: no cover
      import logging
  
  from infoblox_client import exceptions as ib_ex
  from infoblox_client import utils as ib_utils
  
  LOG = logging.getLogger(__name__)
  
  
  class BaseObject(object):
      """Base class that provides minimal new object model interface
  
      This class add next features to objects:
      - initialize public instance variables with None for fields
       defined in '_fields' and '_shadow_fields'
      - accept fields from '_fields' and '_shadow_fields' as a parameter on init
      - dynamically remap one fields into another using _remap dict,
       mapping is in effect on all stages (on init, getter and setter)
      - provides nice object representation that contains class
       and not None object fields (useful in python interpreter)
      """
      _fields = []
      _shadow_fields = []
      _remap = {}
      _infoblox_type = None
  
      def __init__(self, **kwargs):
          mapped_args = self._remap_fields(kwargs)
          for field in self._fields + self._shadow_fields:
              if field in mapped_args:
                  setattr(self, field, mapped_args[field])
              else:
                  # Init all not initialized fields with None
                  if not hasattr(self, field):
                      setattr(self, field, None)
  
      def __getattr__(self, name):
          # Map aliases into real fields
          if name in self._remap:
              return getattr(self, self._remap[name])
          else:
              # Default behaviour
              raise AttributeError
  
      def __setattr__(self, name, value):
          if name in self._remap:
              return setattr(self, self._remap[name], value)
          else:
              super(BaseObject, self).__setattr__(name, value)
  
      def __eq__(self, other):
          if isinstance(other, self.__class__):
              for field in self._fields:
                  if getattr(self, field) != getattr(other, field):
                      return False
              return True
          return False
  
      def __repr__(self):
          data = {field: getattr(self, field)
                  for field in self._fields + self._shadow_fields
                  if hasattr(self, field) and getattr(self, field) is not None}
          data_str = ', '.join(
              "{0}=\"{1}\"".format(key, data[key]) for key in data)
          return "{0}: {1}".format(self.__class__.__name__, data_str)
  
      @classmethod
      def _remap_fields(cls, kwargs):
          """Map fields from kwargs into dict acceptable by NIOS"""
          mapped = {}
          for key in kwargs:
              if key in cls._remap:
                  mapped[cls._remap[key]] = kwargs[key]
              else:
                  mapped[key] = kwargs[key]
          return mapped
  
      @classmethod
      def from_dict(cls, ip_dict):
          return cls(**ip_dict)
  
      def to_dict(self):
          return {field: getattr(self, field) for field in self._fields
                  if getattr(self, field, None) is not None}
  
      @property
      def ref(self):
          if hasattr(self, '_ref'):
              return self._ref
  
  
  class EA(object):
      """Extensible Attributes
  
      This class represents extensible attributes (EA).
      Converts EAs into format suitable for NIOS (to_dict)
      and builds EA class from NIOS reply (from_dict).
      """
  
      def __init__(self, ea_dict=None):
          """Optionally accept EAs as a dict on init.
  
          Expected EA format is {ea_name: ea_value}
          """
          if ea_dict is None:
              ea_dict = {}
          self._ea_dict = ea_dict
  
      def __repr__(self):
          eas = ()
          if self._ea_dict:
              eas = ("{0}={1}".format(name, self._ea_dict[name])
                     for name in self._ea_dict)
          return "EAs:{0}".format(','.join(eas))
  
      @property
      def ea_dict(self):
          """Returns dict with EAs in {ea_name: ea_value} format."""
          return self._ea_dict.copy()
  
      @classmethod
      def from_dict(cls, eas_from_nios):
          """Converts extensible attributes from the NIOS reply."""
          if not eas_from_nios:
              return
          return cls({name: cls._process_value(ib_utils.try_value_to_bool,
                                               eas_from_nios[name]['value'])
                      for name in eas_from_nios})
  
      def to_dict(self):
          """Converts extensible attributes into the format suitable for NIOS."""
          return {name: {'value': self._process_value(str, value)}
                  for name, value in self._ea_dict.items()
                  if not (value is None or value == "" or value == [])}
  
      @staticmethod
      def _process_value(func, value):
          """Applies processing method for value or each element in it.
  
          :param func: method to be called with value
          :param value: value to process
          :return: if 'value' is list/tupe, returns iterable with func results,
                   else func result is returned
          """
          if isinstance(value, (list, tuple)):
              return [func(item) for item in value]
          return func(value)
  
      def get(self, name, default=None):
          """Return value of requested EA."""
          return self._ea_dict.get(name, default)
  
      def set(self, name, value):
          """Set value of requested EA."""
          self._ea_dict[name] = value
  
  
  class InfobloxObject(BaseObject):
      """Base class for all Infoblox related objects
  
      _fields - fields that represents NIOS object (WAPI fields) and
          are sent to NIOS on object creation
      _search_for_update_fields - field/fields used to find an object during an
          update operation. this should be the smallest number of fields that
          uniquely identify an object
      _all_searchable_fields - all fields that can be used to find object on NIOS
          side
      _updateable_search_fields - fields that can be used to find object on
          NIOS side, but also can be changed, so has to be sent on update.
      _shadow_fields - fields that object usually has but they should not
          be sent to NIOS. These fields can be received from NIOS. Examples:
          [_ref, is_default]
      _return_fields - fields requested to be returned from NIOS side
           if object is found/created
      _infoblox_type - string representing wapi type of described object
      _remap - dict that maps user faced names into internal
           representation (_fields)
      _custom_field_processing - dict that define rules (lambda) for building
           objects from data returned by NIOS side.
           Expected to be redefined in child class as needed,
           _custom_field_processing has priority over _global_field_processing,
           so can redefine for child class global rules
           defined in _global_field_processing.
      _global_field_processing - almost the same as _custom_field_processing,
           but defines rules for building field on global level.
           Fields defined in this dict will be processed in the same way in all
           child classes. Is not expected to be redefined in child classes.
      _ip_version - ip version of the object, used to mark version
          specific classes. Value other than None indicates that
          no versioned class lookup needed.
      """
      _fields = []
      _search_for_update_fields = []
      _all_searchable_fields = []
      _updateable_search_fields = []
      _shadow_fields = []
      _infoblox_type = None
      _remap = {}
  
      _return_fields = []
      _custom_field_processing = {}
      _global_field_processing = {'extattrs': EA.from_dict}
      _ip_version = None
  
      def __new__(cls, connector, **kwargs):
          return super(InfobloxObject,
                       cls).__new__(cls.get_class_from_args(kwargs))
  
      def __init__(self, connector, **kwargs):
          self.connector = connector
          super(InfobloxObject, self).__init__(**kwargs)
  
      def update_from_dict(self, ip_dict, only_ref=False):
          if only_ref:
              self._ref = ip_dict['_ref']
              return
  
          mapped_args = self._remap_fields(ip_dict)
          for field in self._fields + self._shadow_fields:
              if field in ip_dict:
                  setattr(self, field, mapped_args[field])
  
      @classmethod
      def from_dict(cls, connector, ip_dict):
          """Build dict fields as SubObjects if needed.
  
          Checks if lambda for building object from dict exists.
          _global_field_processing and _custom_field_processing rules
          are checked.
          """
          mapping = cls._global_field_processing.copy()
          mapping.update(cls._custom_field_processing)
          # Process fields that require building themselves as objects
          for field in mapping:
              if field in ip_dict:
                  ip_dict[field] = mapping[field](ip_dict[field])
          return cls(connector, **ip_dict)
  
      @staticmethod
      def value_to_dict(value):
          return value.to_dict() if hasattr(value, 'to_dict') else value
  
      def field_to_dict(self, field):
          """Read field value and converts to dict if possible"""
          value = getattr(self, field)
          if isinstance(value, (list, tuple)):
              return [self.value_to_dict(val) for val in value]
          return self.value_to_dict(value)
  
      def to_dict(self, search_fields=None):
          """Builds dict without None object fields"""
          fields = self._fields
          if search_fields == 'update':
              fields = self._search_for_update_fields
          elif search_fields == 'all':
              fields = self._all_searchable_fields
          elif search_fields == 'exclude':
              # exclude search fields for update actions,
              # but include updateable_search_fields
              fields = [field for field in self._fields
                        if field in self._updateable_search_fields or
                        field not in self._search_for_update_fields]
  
          return {field: self.field_to_dict(field) for field in fields
                  if getattr(self, field, None) is not None}
  
      @staticmethod
      def _object_from_reply(parse_class, connector, reply):
          if not reply:
              return None
          if isinstance(reply, dict):
              return parse_class.from_dict(connector, reply)
  
          # If no return fields were requested reply contains only string
          # with reference to object
          return_dict = {'_ref': reply}
          return parse_class.from_dict(connector, return_dict)
  
      @classmethod
      def create_check_exists(cls, connector, check_if_exists=True,
                              update_if_exists=False, **kwargs):
          # obj_created is used to check if object is being created or
          # pre-exists. obj_created is True if object is not pre-exists
          # and getting created with this function call
          obj_created = False
          local_obj = cls(connector, **kwargs)
          if check_if_exists:
              if local_obj.fetch(only_ref=True):
                  LOG.info(("Infoblox %(obj_type)s already exists: "
                            "%(ib_obj)s"),
                           {'obj_type': local_obj.infoblox_type,
                            'ib_obj': local_obj})
                  if not update_if_exists:
                      return local_obj, obj_created
          reply = None
          if not local_obj.ref:
              reply = connector.create_object(local_obj.infoblox_type,
                                              local_obj.to_dict(),
                                              local_obj.return_fields)
              obj_created = True
              LOG.info("Infoblox %(obj_type)s was created: %(ib_obj)s",
                       {'obj_type': local_obj.infoblox_type,
                        'ib_obj': local_obj})
          elif update_if_exists:
              update_fields = local_obj.to_dict(search_fields='exclude')
              reply = connector.update_object(local_obj.ref,
                                              update_fields,
                                              local_obj.return_fields)
              LOG.info('Infoblox object was updated: %s', local_obj.ref)
          return cls._object_from_reply(local_obj, connector, reply), obj_created
  
      @classmethod
      def create(cls, connector, check_if_exists=True,
                 update_if_exists=False, **kwargs):
          ib_object, _ = (
              cls.create_check_exists(connector,
                                      check_if_exists=check_if_exists,
                                      update_if_exists=update_if_exists,
                                      **kwargs))
          return ib_object
  
      @classmethod
      def _search(cls, connector, return_fields=None,
                  search_extattrs=None, force_proxy=False,
                  max_results=None, **kwargs):
          ib_obj_for_search = cls(connector, **kwargs)
          search_dict = ib_obj_for_search.to_dict(search_fields='all')
          if return_fields is None and ib_obj_for_search.return_fields:
              return_fields = ib_obj_for_search.return_fields
          # allow search_extattrs to be instance of EA class
          # or dict in NIOS format
          extattrs = search_extattrs
          if hasattr(search_extattrs, 'to_dict'):
              extattrs = search_extattrs.to_dict()
          reply = connector.get_object(ib_obj_for_search.infoblox_type,
                                       search_dict,
                                       return_fields=return_fields,
                                       extattrs=extattrs,
                                       force_proxy=force_proxy,
                                       max_results=max_results)
          return reply, ib_obj_for_search
  
      @classmethod
      def search(cls, connector, **kwargs):
          ib_obj, parse_class = cls._search(
              connector, **kwargs)
          if ib_obj:
              return parse_class.from_dict(connector, ib_obj[0])
  
      @classmethod
      def search_all(cls, connector, **kwargs):
          ib_objects, parsing_class = cls._search(
              connector, **kwargs)
          if ib_objects:
              return [parsing_class.from_dict(connector, obj)
                      for obj in ib_objects]
          return []
  
      def fetch(self, only_ref=False):
          """Fetch object from NIOS by _ref or searchfields
  
          Update existent object with fields returned from NIOS
          Return True on successful object fetch
          """
          if self.ref:
              reply = self.connector.get_object(
                  self.ref, return_fields=self.return_fields)
              if reply:
                  self.update_from_dict(reply)
                  return True
  
          search_dict = self.to_dict(search_fields='update')
          return_fields = [] if only_ref else self.return_fields
          reply = self.connector.get_object(self.infoblox_type,
                                            search_dict,
                                            return_fields=return_fields)
          if reply:
              self.update_from_dict(reply[0], only_ref=only_ref)
              return True
          return False
  
      def update(self):
          update_fields = self.to_dict(search_fields='exclude')
          ib_obj = self.connector.update_object(self.ref,
                                                update_fields,
                                                self.return_fields)
          LOG.info('Infoblox object was updated: %s', self.ref)
          return self._object_from_reply(self, self.connector, ib_obj)
  
      def delete(self):
          try:
              self.connector.delete_object(self.ref)
          except ib_ex.InfobloxCannotDeleteObject as e:
              LOG.info("Failed to delete an object: %s", e)
  
      @property
      def infoblox_type(self):
          return self._infoblox_type
  
      @property
      def return_fields(self):
          return self._return_fields
  
      @property
      def ip_version(self):
          return self._ip_version
  
      @classmethod
      def get_class_from_args(cls, kwargs):
          # skip processing if cls already versioned class
          if cls._ip_version:
              return cls
  
          for field in ['ip', 'cidr', 'start_ip', 'ip_address', 'network',
                        'start_addr', 'end_addr']:
              if field in kwargs:
                  if ib_utils.determine_ip_version(kwargs[field]) == 6:
                      return cls.get_v6_class()
                  else:
                      return cls.get_v4_class()
          # fallback to IPv4 object if find nothing
          return cls.get_v4_class()
  
      @classmethod
      def get_v4_class(cls):
          return cls
  
      @classmethod
      def get_v6_class(cls):
          return cls
  
  
  class Network(InfobloxObject):
      _fields = ['network_view', 'network', 'template',
                 'options', 'members', 'extattrs', 'comment',
                 'zone_associations']
      _search_for_update_fields = ['network_view', 'network']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref']
      _return_fields = ['network_view', 'network', 'options', 'members',
                        'extattrs', 'comment']
      _remap = {'cidr': 'network'}
  
      @classmethod
      def get_v4_class(cls):
          return NetworkV4
  
      @classmethod
      def get_v6_class(cls):
          return NetworkV6
  
      @staticmethod
      def _build_member(members):
          if not members:
              return None
          return [AnyMember.from_dict(m) for m in members]
  
      # TODO(pbondar): Rework SubObject to correctly handle arrays
      # passed into from_dict, so all _build_options and _build_member
      # would be no longer needed
      @staticmethod
      def _build_options(members):
          if not members:
              return None
          return [DhcpOption.from_dict(m) for m in members]
  
      _custom_field_processing = {'members': _build_member.__func__,
                                  'options': _build_options.__func__}
  
  
  class NetworkV4(Network):
      _infoblox_type = 'network'
      _ip_version = 4
  
  
  class NetworkV6(Network):
      _infoblox_type = 'ipv6network'
      _ip_version = 6
  
  
  class HostRecord(InfobloxObject):
      """Base class for HostRecords
  
      HostRecord uses ipvXaddr for search and ipvXaddrs for object creation.
      ipvXaddr and ipvXaddrs are quite different:
      ipvXaddr is single ip as a string
      ipvXaddrs is list of dicts with ipvXaddr, mac, configure_for_dhcp
      and host keys.
      In 'ipvXaddr' 'X' stands for 4 or 6 depending on ip version of the class.
  
      To find HostRecord use next syntax:
      hr = HostRecord.search(connector, ip='192.168.1.25', view='some-view')
  
      To create host record create IP object first:
      ip = IP(ip='192.168.1.25', mac='aa:ab;ce:12:23:34')
      hr = HostRecord.create(connector, ip=ip, view='some-view')
  
      """
      _infoblox_type = 'record:host'
  
      @classmethod
      def get_v4_class(cls):
          return HostRecordV4
  
      @classmethod
      def get_v6_class(cls):
          return HostRecordV6
  
      def _ip_setter(self, ipaddr_name, ipaddrs_name, ips):
          """Setter for ip fields
  
          Accept as input string or list of IP instances.
          String case:
              only ipvXaddr is going to be filled, that is enough to perform
              host record search using ip
          List of IP instances case:
              ipvXaddrs is going to be filled with ips content,
              so create can be issues, since fully prepared IP objects in place.
              ipXaddr is also filled to be able perform search on NIOS
              and verify that no such host record exists yet.
          """
          if isinstance(ips, six.string_types):
              setattr(self, ipaddr_name, ips)
          elif isinstance(ips, (list, tuple)) and isinstance(ips[0], IP):
              setattr(self, ipaddr_name, ips[0].ip)
              setattr(self, ipaddrs_name, ips)
          elif isinstance(ips, IP):
              setattr(self, ipaddr_name, ips.ip)
              setattr(self, ipaddrs_name, [ips])
          elif ips is None:
              setattr(self, ipaddr_name, None)
              setattr(self, ipaddrs_name, None)
          else:
              raise ValueError(
                  "Invalid format of ip passed in: %s."
                  "Should be string or list of NIOS IP objects." % ips)
  
  
  class HostRecordV4(HostRecord):
      """HostRecord for IPv4"""
      _fields = ['ipv4addrs', 'view', 'extattrs', 'name', 'zone',
                 'configure_for_dns', 'network_view', 'mac', 'ttl',
                 'comment', 'aliases']
      _search_for_update_fields = ['view', 'ipv4addr', 'name',
                                   'zone', 'network_view', 'mac']
      _all_searchable_fields = _search_for_update_fields
      _updateable_search_fields = ['name']
      _shadow_fields = ['_ref', 'ipv4addr']
      _return_fields = ['ipv4addrs', 'extattrs', 'aliases']
      _remap = {'ip': 'ipv4addrs',
                'ips': 'ipv4addrs'}
      _ip_version = 4
  
      @property
      def ipv4addrs(self):
          return self._ipv4addrs
  
      @ipv4addrs.setter
      def ipv4addrs(self, ips):
          """Setter for ipv4addrs/ipv4addr"""
          self._ip_setter('ipv4addr', '_ipv4addrs', ips)
  
      @staticmethod
      def _build_ipv4(ips_v4):
          if not ips_v4:
              raise ib_ex.HostRecordNotPresent()
          ip = ips_v4[0]['ipv4addr']
          if not ib_utils.is_valid_ip(ip):
              raise ib_ex.InfobloxInvalidIp(ip=ip)
          return [IPv4.from_dict(ip_addr) for ip_addr in ips_v4]
  
      _custom_field_processing = {'ipv4addrs': _build_ipv4.__func__}
  
  
  class HostRecordV6(HostRecord):
      """HostRecord for IPv6"""
      _fields = ['ipv6addrs', 'view', 'extattrs', 'name', 'zone',
                 'configure_for_dns', 'network_view', 'ttl', 'comment',
                 'aliases']
      _search_for_update_fields = ['ipv6addr', 'view', 'name',
                                   'zone', 'network_view']
      _all_searchable_fields = _search_for_update_fields
      _updateable_search_fields = ['name']
      _shadow_fields = ['_ref', 'ipv6addr']
      _return_fields = ['ipv6addrs', 'extattrs', 'aliases']
      _remap = {'ip': 'ipv6addrs',
                'ips': 'ipv6addrs'}
  
      _ip_version = 6
  
      @property
      def ipv6addrs(self):
          return self._ipv6addrs
  
      @ipv6addrs.setter
      def ipv6addrs(self, ips):
          """Setter for ipv6addrs/ipv6addr"""
          self._ip_setter('ipv6addr', '_ipv6addrs', ips)
  
      @staticmethod
      def _build_ipv6(ips_v6):
          if not ips_v6:
              raise ib_ex.HostRecordNotPresent()
          ip = ips_v6[0]['ipv6addr']
          if not ib_utils.is_valid_ip(ip):
              raise ib_ex.InfobloxInvalidIp(ip=ip)
          return [IPv6.from_dict(ip_addr) for ip_addr in ips_v6]
  
      _custom_field_processing = {'ipv6addrs': _build_ipv6.__func__}
  
  
  class IPv6HostAddress(InfobloxObject):
      _infoblox_type = 'record:host_ipv6addr'
      _fields = ['duid', 'network_view', 'host']
      _search_for_update_fields = ['duid', 'network_view']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref']
      _return_fields = ['host']
      _ip_version = 6
  
  
  class SubObjects(BaseObject):
      """Base class for objects that do not require all InfobloxObject power"""
      @classmethod
      def from_dict(cls, ip_dict):
          return cls(**ip_dict)
  
      def to_dict(self):
          return {field: getattr(self, field) for field in self._fields
                  if getattr(self, field, None) is not None}
  
  
  class IP(SubObjects):
      _fields = []
      _shadow_fields = ['_ref', 'ip', 'host']
      _remap = {}
      ip_version = None
  
      # better way for mac processing?
      @classmethod
      def create(cls, ip=None, mac=None, **kwargs):
          if ip is None:
              raise ValueError
          if ib_utils.determine_ip_version(ip) == 6:
              return IPv6(ip=ip, duid=ib_utils.generate_duid(mac),
                          **kwargs)
          else:
              return IPv4(ip=ip, mac=mac, **kwargs)
  
      def __eq__(self, other):
          if isinstance(other, six.string_types):
              return self.ip == other
          elif isinstance(other, self.__class__):
              return self.ip == other.ip
          return False
  
      @property
      def zone_auth(self):
          if self.host is not None:
              return self.host.partition('.')[2]
  
      @property
      def hostname(self):
          if self.host is not None:
              return self.host.partition('.')[0]
  
      @property
      def ip(self):
          # Convert IPAllocation objects to string
          if hasattr(self, '_ip'):
              return str(self._ip)
  
      @ip.setter
      def ip(self, ip):
          self._ip = ip
  
  
  class IPv4(IP):
      _fields = ['ipv4addr', 'configure_for_dhcp', 'mac']
      _remap = {'ipv4addr': 'ip'}
      ip_version = 4
  
  
  class IPv6(IP):
      _fields = ['ipv6addr', 'configure_for_dhcp', 'duid']
      _remap = {'ipv6addr': 'ip'}
      ip_version = 6
  
  
  class AnyMember(SubObjects):
      _fields = ['_struct', 'name', 'ipv4addr', 'ipv6addr']
      _shadow_fields = ['ip']
  
      @property
      def ip(self):
          if hasattr(self, '_ip'):
              return str(self._ip)
  
      @ip.setter
      def ip(self, ip):
          # AnyMember represents both ipv4 and ipv6 objects, so don't need
          # versioned object for that. Just set v4 or v6 field additionally
          # to setting shadow 'ip' field itself.
          # So once dict is generated by to_dict only versioned ip field
          # to be shown.
          if ib_utils.determine_ip_version(ip) == 6:
              self.ipv6addr = ip
          else:
              self.ipv4addr = ip
  
  
  class DhcpOption(SubObjects):
      _fields = ['name', 'num', 'use_option', 'value', 'vendor_class']
  
  
  class IPRange(InfobloxObject):
      _fields = ['start_addr', 'end_addr', 'network_view',
                 'network', 'extattrs', 'disable', 'template', 'comment',
                 'server_association_type', 'failover_association']
      _remap = {'cidr': 'network'}
      _search_for_update_fields = ['network_view', 'start_addr',
                                   'end_addr', 'network']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref']
      _return_fields = ['start_addr', 'end_addr', 'network_view', 'extattrs']
  
      @classmethod
      def get_v4_class(cls):
          return IPRangeV4
  
      @classmethod
      def get_v6_class(cls):
          return IPRangeV6
  
  
  class IPRangeV4(IPRange):
      _infoblox_type = 'range'
      _ip_version = 4
  
  
  class IPRangeV6(IPRange):
      _infoblox_type = 'ipv6range'
      _ip_version = 6
  
  
  class FixedAddress(InfobloxObject):
      @classmethod
      def get_v4_class(cls):
          return FixedAddressV4
  
      @classmethod
      def get_v6_class(cls):
          return FixedAddressV6
  
      @property
      def ip(self):
          if hasattr(self, '_ip') and self._ip:
              return str(self._ip)
  
      @ip.setter
      def ip(self, ip):
          self._ip = ip
  
  
  class FixedAddressV4(FixedAddress):
      _infoblox_type = 'fixedaddress'
      _fields = ['ipv4addr', 'mac', 'network_view', 'extattrs', 'network',
                 'options', 'comment', 'name', 'ms_server']
      _search_for_update_fields = ['ipv4addr', 'mac', 'network_view', 'network']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref', 'ip']
      _return_fields = ['ipv4addr', 'mac', 'network_view', 'extattrs']
      _remap = {'ipv4addr': 'ip'}
      _ip_version = 4
  
      @staticmethod
      def _build_options(members):
          if not members:
              return None
          return [DhcpOption.from_dict(m) for m in members]
  
      _custom_field_processing = {'options': _build_options.__func__}
  
  
  class FixedAddressV6(FixedAddress):
      """FixedAddress for IPv6"""
      _infoblox_type = 'ipv6fixedaddress'
      _fields = ['ipv6addr', 'duid', 'network_view', 'extattrs', 'network',
                 'comment', 'name', 'ms_server']
      _search_for_update_fields = ['ipv6addr', 'duid', 'network_view', 'network']
      _all_searchable_fields = _search_for_update_fields
      _return_fields = ['ipv6addr', 'duid', 'network_view', 'extattrs']
      _shadow_fields = ['_ref', 'mac', 'ip']
      _remap = {'ipv6addr': 'ip'}
      _ip_version = 6
  
      @property
      def mac(self):
          return self._mac
  
      @mac.setter
      def mac(self, mac):
          """Set mac and duid fields
  
          To have common interface with FixedAddress accept mac address
          and set duid as a side effect.
          'mac' was added to _shadow_fields to prevent sending it out over wapi.
          """
          self._mac = mac
          if mac:
              self.duid = ib_utils.generate_duid(mac)
          elif not hasattr(self, 'duid'):
              self.duid = None
  
  
  class ARecordBase(InfobloxObject):
  
      @classmethod
      def get_v4_class(cls):
          return ARecord
  
      @classmethod
      def get_v6_class(cls):
          return AAAARecord
  
  
  class ARecord(ARecordBase):
      _infoblox_type = 'record:a'
      _fields = ['ipv4addr', 'name', 'view', 'comment', 'extattrs',
                 'ttl']
      _search_for_update_fields = ['ipv4addr', 'view']
      _all_searchable_fields = _search_for_update_fields + ['name']
      _updateable_search_fields = ['ipv4addr']
      _return_fields = ['ipv4addr', 'name']
      _shadow_fields = ['_ref']
      _remap = {'ip': 'ipv4addr'}
      _ip_version = 4
  
      @property
      def ipv4addr(self):
          # Convert IPAllocation objects to string
          if hasattr(self, '_ipv4addr'):
              return str(self._ipv4addr)
  
      @ipv4addr.setter
      def ipv4addr(self, ipv4addr):
          self._ipv4addr = ipv4addr
  
  
  class AAAARecord(ARecordBase):
      _infoblox_type = 'record:aaaa'
      _fields = ['ipv6addr', 'name', 'view', 'comment', 'extattrs',
                 'ttl']
      _search_for_update_fields = ['ipv6addr', 'view']
      _all_searchable_fields = _search_for_update_fields + ['name']
      _return_fields = ['ipv6addr', 'name']
      _shadow_fields = ['_ref']
      _remap = {'ip': 'ipv6addr'}
      _ip_version = 6
  
      @property
      def ipv6addr(self):
          # Convert IPAllocation objects to string
          if hasattr(self, '_ipv6addr'):
              return str(self._ipv6addr)
  
      @ipv6addr.setter
      def ipv6addr(self, ipv6addr):
          self._ipv6addr = ipv6addr
  
  
  class PtrRecord(InfobloxObject):
      _infoblox_type = 'record:ptr'
  
      @classmethod
      def get_v4_class(cls):
          return PtrRecordV4
  
      @classmethod
      def get_v6_class(cls):
          return PtrRecordV6
  
  
  class PtrRecordV4(PtrRecord):
      _fields = ['view', 'ipv4addr', 'ptrdname', 'extattrs', 'ttl']
      _search_for_update_fields = ['view', 'ipv4addr']
      _all_searchable_fields = _search_for_update_fields + ['ptrdname']
      _shadow_fields = ['_ref']
      _remap = {'ip': 'ipv4addr'}
      _ip_version = 4
  
  
  class PtrRecordV6(PtrRecord):
      _fields = ['view', 'ipv6addr', 'ptrdname', 'extattrs', 'ttl']
      _search_for_update_fields = ['view', 'ipv6addr']
      _all_searchable_fields = _search_for_update_fields + ['ptrdname']
      _shadow_fields = ['_ref']
      _remap = {'ip': 'ipv6addr'}
      _ip_version = 6
  
  
  class SRVRecord(InfobloxObject):
      _infoblox_type = 'record:srv'
      _fields = ['name', 'port', 'priority', 'target', 'weight',
                 'aws_rte53_record_info', 'cloud_info', 'comment',
                 'creation_time', 'creator', 'ddns_principal',
                 'ddns_protected', 'disable', 'dns_name', 'dns_target',
                 'extattrs', 'forbid_reclamation', 'reclaimable',
                 'ttl', 'use_ttl', 'view', 'zone']
      _search_for_update_fields = ['comment', 'creator', 'ddns_principal',
                                   'name', 'port', 'priority', 'reclaimable',
                                   'target', 'view', 'weight', 'zone']
      _all_searchable_fields = _search_for_update_fields
      _return_fields = ['name', 'port', 'priority', 'weight', 'target', 'view']
      _shadow_fields = ['_ref']
  
  
  class NetworkView(InfobloxObject):
      _infoblox_type = 'networkview'
      _fields = ['name', 'extattrs']
      _return_fields = ['name', 'extattrs', 'is_default']
      _search_for_update_fields = ['name']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref', 'is_default']
      _ip_version = 'any'
  
  
  class DNSView(InfobloxObject):
      _infoblox_type = 'view'
      _fields = ['name', 'network_view', 'extattrs']
      _return_fields = ['name', 'network_view', 'extattrs']
      _search_for_update_fields = ['name', 'network_view']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref', 'is_default']
      _ip_version = 'any'
  
  
  class DNSZone(InfobloxObject):
      _infoblox_type = 'zone_auth'
      _fields = ['fqdn', 'view', 'extattrs', 'zone_format',
                 'prefix', 'grid_primary', 'grid_secondaries']
      _return_fields = ['fqdn', 'view', 'extattrs', 'zone_format', 'ns_group',
                        'prefix', 'grid_primary', 'grid_secondaries']
      _search_for_update_fields = ['fqdn', 'view', 'zone_format']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref', 'ns_group']
      _ip_version = 'any'
  
      @staticmethod
      def _build_member(members):
          if not members:
              return None
          return [AnyMember.from_dict(m) for m in members]
  
      _custom_field_processing = {
          'primary_dns_members': _build_member.__func__,
          'secondary_dns_members': _build_member.__func__}
  
  
  class Member(InfobloxObject):
      _infoblox_type = 'member'
      _fields = ['host_name', 'ipv6_setting', 'vip_setting',
                 'extattrs', 'ipv4_address', 'ipv6_address', 'platform',
                 'config_addr_type', 'service_type_configuration']
      _return_fields = ['host_name', 'ipv6_setting', 'node_info',
                        'vip_setting', 'extattrs']
      _search_for_update_fields = ['host_name', 'ipv4_address', 'ipv6_address']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref', 'ip', 'node_info']
      _ip_version = 'any'
      _remap = {'name': 'host_name'}
  
  
  class EADefinition(InfobloxObject):
      """Extensible Attribute Definition"""
      _infoblox_type = 'extensibleattributedef'
      _fields = ['comment', 'default_value', 'flags', 'list_values',
                 'max', 'min', 'name', 'namespace', 'type',
                 'allowed_object_types']
      _search_for_update_fields = ['name']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref']
      _return_fields = ['comment', 'default_value', 'flags', 'list_values',
                        'max', 'min', 'name', 'namespace', 'type',
                        'allowed_object_types']
  
  
  class IPAddress(InfobloxObject):
      _fields = ['network_view', 'ip_address', 'objects', 'network', 'status',
                 'is_conflict', 'types', 'usage', 'names']
      _search_for_update_fields = ['network_view', 'ip_address',
                                   'network', 'status']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref']
      _return_fields = ['objects']
  
      @classmethod
      def get_v4_class(cls):
          return IPv4Address
  
      @classmethod
      def get_v6_class(cls):
          return IPv6Address
  
  
  class IPv4Address(IPAddress):
      _infoblox_type = 'ipv4address'
      _ip_version = 4
      _fields = IPAddress._fields + ['mac_address']
  
  
  class IPv6Address(IPAddress):
      _infoblox_type = 'ipv6address'
      _ip_version = 6
      _fields = IPAddress._fields + ['duid', 'lease_state']
  
  
  class IPAllocation(object):
  
      def __init__(self, address, next_available_ip):
          self.ip_version = ib_utils.determine_ip_version(address)
          self.next_available_ip = next_available_ip
  
      def __repr__(self):
          return "IPAllocation: {0}".format(self.next_available_ip)
  
      def __str__(self):
          return str(self.next_available_ip)
  
      @classmethod
      def next_available_ip_from_cidr(cls, net_view_name, cidr):
          return cls(cidr, 'func:nextavailableip:'
                           '{cidr:s},{net_view_name:s}'.format(**locals()))
  
      @classmethod
      def next_available_ip_from_range(cls, net_view_name, first_ip, last_ip):
          return cls(first_ip, 'func:nextavailableip:{first_ip}-{last_ip},'
                               '{net_view_name}'.format(**locals()))
  
  
  class Tenant(InfobloxObject):
      _infoblox_type = 'grid:cloudapi:tenant'
      _fields = ['id', 'name', 'comment']
      _search_for_update_fields = ['id']
      _all_searchable_fields = _search_for_update_fields
      _shadow_fields = ['_ref']
  
  
  class CNAMERecord(InfobloxObject):
      _infoblox_type = 'record:cname'
      _fields = ['name', 'canonical', 'view', 'extattrs', 'comment',
                 'creator', 'ddns_principal', 'ddns_protected', 'disable',
                 'forbid_reclamation', 'ttl', 'use_ttl']
      _search_for_update_fields = ['name', 'view']
      _updateable_search_fields = ['name']
      _all_searchable_fields = _search_for_update_fields + ['reclaimable',
                                                            'zone']
      _return_fields = ['canonical', 'name', 'view', 'extattrs']
      _shadow_fields = ['_ref']
  
  
  class MXRecord(InfobloxObject):
      _infoblox_type = 'record:mx'
      _fields = ['name', 'mail_exchanger', 'preference', 'view',
                 'aws_rte53_record_info', 'cloud_info', 'comment',
                 'creation_time', 'creator', 'ddns_principal',
                 'ddns_protected', 'disable', 'dns_mail_exchanger',
                 'dns_name', 'extattrs', 'forbid_reclamation', 'last_queried',
                 'reclaimable', 'shared_record_group', 'ttl', 'use_ttl', 'zone']
      _search_for_update_fields = ['name', 'mail_exchanger']
      _all_searchable_fields = ['name', 'mail_exchanger', 'comment', 'creator',
                                'ddns_principal', 'preference', 'view', 'zone']
      _updateable_search_fields = ['name', 'mail_exchanger', 'preference']
      _return_fields = ['name', 'mail_exchanger', 'preference']
      _shadow_fields = ['_ref']
  