class NetMRIJob(resource.Resource, mri.NetMRIResourceMixin):
    '''A resource which represents a job executed in NetMRI.'''

    PROPERTIES = mri.NetMRIResourceMixin.PROPERTIES
    ATTRIBUTES = mri.NetMRIResourceMixin.ATTRIBUTES

    support_status = support.SupportStatus(
        support.UNSUPPORTED, _('See support.infoblox.com for support.'))

    properties_schema = {
        constants.CONNECTION:
        resource_utils.connection_schema(constants.NETMRI),
    }
    properties_schema.update(mri.NetMRIResourceMixin.job_schema)

    attributes_schema = mri.NetMRIResourceMixin.job_attributes_schema

    def handle_create(self):
        r = self._execute_job(self.properties)
        self.resource_id_set(r['JobID'])

    def check_create_complete(self, handler_data):
        if not self.properties[self.WAIT]:
            return True

        job_id = int(self.resource_id)
        return self._check_job_complete(job_id)

    def handle_delete(self):
        pass

    def _resolve_attribute(self, name):
        return self._resolve_job_attribute(name)
class AnycastLoopback(resource.Resource):
    """A resource which represents an anycast loopback interface.

    This is used to assign anycast address to Grid Member loopback interface.
    """

    PROPERTIES = (
        IP,
        GRID_MEMBERS,
        ENABLE_BGP,
        ENABLE_OSPF,
        ENABLE_DNS,
    ) = (
        'ip',
        'grid_members',
        'enable_bgp',
        'enable_ospf',
        'enable_dns',
    )

    support_status = support.SupportStatus(
        support.UNSUPPORTED, _('See support.infoblox.com for support.'))

    properties_schema = {
        constants.CONNECTION:
        resource_utils.connection_schema(constants.DDI),
        IP:
        properties.Schema(properties.Schema.STRING,
                          _('The Anycast Loopback IP address.'),
                          update_allowed=True,
                          required=True),
        GRID_MEMBERS:
        properties.Schema(
            properties.Schema.LIST,
            _('List of Grid Member Names for Anycast IP address'),
            schema=properties.Schema(properties.Schema.STRING),
            update_allowed=True,
            required=True),
        ENABLE_BGP:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('Determines if the BGP advertisement setting is enabled '
              'for this interface or not.'),
            update_allowed=True,
            required=False),
        ENABLE_OSPF:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('Determines if the OSPF advertisement setting is enabled '
              'for this interface or not.'),
            update_allowed=True,
            required=False),
        ENABLE_DNS:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('Determines if the Anycast IP will be used to serve DNS.'),
            update_allowed=True,
            required=False),
    }

    @property
    def infoblox(self):
        if not getattr(self, 'infoblox_object', None):
            conn = self.properties[constants.CONNECTION]
            self.infoblox_object = resource_utils.connect_to_infoblox(conn)
        return self.infoblox_object

    def handle_create(self):
        ip = self.properties[self.IP]
        for member_name in self.properties[self.GRID_MEMBERS]:
            with lockutils.lock(member_name,
                                external=True,
                                lock_file_prefix='infoblox-anycast'):
                self.infoblox.create_anycast_loopback(
                    member_name, ip, self.properties[self.ENABLE_BGP],
                    self.properties[self.ENABLE_OSPF])
            if self.properties[self.ENABLE_DNS]:
                with lockutils.lock(member_name,
                                    external=True,
                                    lock_file_prefix='infoblox-dns-ips'):
                    self.infoblox.add_member_dns_additional_ip(member_name, ip)

    def _delete_ip_from_dns(self, member_name, ip):
        if self.properties[self.ENABLE_DNS]:
            with lockutils.lock(member_name,
                                external=True,
                                lock_file_prefix='infoblox-dns-ips'):
                self.infoblox.remove_member_dns_additional_ip(member_name, ip)

    def _delete_anycast_ip_from_member(self, member_name):
        ip = self.properties[self.IP]
        self._delete_ip_from_dns(member_name, ip)
        with lockutils.lock(member_name,
                            external=True,
                            lock_file_prefix='infoblox-anycast'):
            self.infoblox.delete_anycast_loopback(ip, member_name)

    def handle_delete(self):
        for member_name in self.properties[self.GRID_MEMBERS]:
            self._delete_anycast_ip_from_member(member_name)

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if not prop_diff:
            return
        new_members = set(tmpl_diff['Properties'][self.GRID_MEMBERS])
        old_members = set(self.properties.get(self.GRID_MEMBERS))
        to_remove = old_members - new_members
        if self.GRID_MEMBERS in prop_diff:
            for member in to_remove:
                self._delete_anycast_ip_from_member(member)

            if len(prop_diff) > 1:
                # Anycast settings were changed, need to update all members
                to_update = new_members
            else:
                # Anycast settings unchanged, so add it to new members
                to_update = new_members - old_members
        else:
            # Anycast settings were changed, so need to update all members
            to_update = new_members

        # Enable_dns field complicates update because it refers to
        # member:dns additional_ip_list which depends on the
        # additional_ip_list field from member.
        # To update ip for anycast loopback update has to be executed in
        # next order:
        # - delete old ip address from member:dns
        # - update anycast ip
        # - add updated ip address to member:dns

        # if ip changed or dns disabled - delete dns ip from existing members
        if (self.IP in prop_diff or
            (self.ENABLE_DNS in prop_diff and not prop_diff[self.ENABLE_DNS])):
            for member in old_members - to_remove:
                self._delete_ip_from_dns(member, self.properties[self.IP])
        # now create/update anycast loopback and dns ip
        for member in to_update:

            with lockutils.lock(member,
                                external=True,
                                lock_file_prefix='infoblox-anycast'):
                self.infoblox.create_anycast_loopback(
                    member,
                    tmpl_diff['Properties'][self.IP],
                    tmpl_diff['Properties'][self.ENABLE_BGP],
                    tmpl_diff['Properties'][self.ENABLE_OSPF],
                    old_ip=self.properties[self.IP])

            if tmpl_diff['Properties'][self.ENABLE_DNS]:
                with lockutils.lock(member,
                                    external=True,
                                    lock_file_prefix='infoblox-dns-ips'):
                    self.infoblox.add_member_dns_additional_ip(
                        member, tmpl_diff['Properties'][self.IP])
Esempio n. 3
0
class NameServerGroupMember(resource.Resource):
    '''A resource which represents a name server group.

    Use this resource to create, modify, and delete name server groups in the
    grid.
    '''

    PROPERTIES = (GROUP_NAME, MEMBER_ROLE, MEMBER_SERVER, EXTERNAL_SERVER,
                  MEMBER_NAME, GRID_REPLICATE, LEAD,
                  ENABLE_PREFERRED_PRIMARIES,
                  PREFERRED_PRIMARIES) = ('group_name', 'member_role',
                                          'member_server', 'external_server',
                                          'name', 'grid_replicate', 'lead',
                                          'enable_preferred_primaries',
                                          'preferred_primaries')

    # for now, only support grid members
    # not 'external_primary', 'external_secondary'
    MEMBER_ROLES = ['grid_primary', 'grid_secondary']

    ATTRIBUTES = (NS_GROUP, ) = ('name_server_group', )

    support_status = support.SupportStatus(
        support.UNSUPPORTED, _('See support.infoblox.com for support.'))

    properties_schema = {
        constants.CONNECTION:
        resource_utils.connection_schema(constants.DDI),
        GROUP_NAME:
        properties.Schema(properties.Schema.STRING,
                          _('Name server group name.')),
        MEMBER_ROLE:
        properties.Schema(
            properties.Schema.STRING,
            _('The role the member plays in the group.'),
            constraints=[constraints.AllowedValues(MEMBER_ROLES)]),
        MEMBER_SERVER:
        properties.Schema(
            properties.Schema.MAP,
            _('A grid member settings in this group.'),
            schema={
                MEMBER_NAME:
                properties.Schema(properties.Schema.STRING,
                                  _('The member name.'),
                                  required=True),
                GRID_REPLICATE:
                properties.Schema(
                    properties.Schema.BOOLEAN,
                    _('Determines if grid replication or zone transfer will '
                      'be used to this server.'),
                    default=True),
                LEAD:
                properties.Schema(
                    properties.Schema.BOOLEAN,
                    _('Determines if this member should serve as the lead '
                      'secondary for the group.'),
                    default=False),
            }),
    }

    attributes_schema = {
        NS_GROUP:
        attributes.Schema(_('The name server group details.'),
                          attributes.Schema.MAP)
    }

    def infoblox(self):
        if not getattr(self, 'infoblox_object', None):
            conn = self.properties[constants.CONNECTION]
            self.infoblox_object = resource_utils.connect_to_infoblox(conn)
        return self.infoblox_object

    def _remove_member(self, member_list, member):
        i = 0
        for m in member_list:
            if m['name'] == member['name']:
                del member_list[i]
            i += 1

    def _add_member(self, member_list, member):
        # remove it if it is already there, so we get any updates
        self._remove_member(member_list, member)
        member_list.append(member)

    def _get_ns_group(self, group_name):
        LOG.debug("LOADING NSGROUP: %s" % group_name)
        groups = self.infoblox().get_ns_group(
            group_name,
            return_fields=['name', 'grid_primary', 'grid_secondaries'])
        if len(groups) == 0:
            raise exception.EntityNotFound(entity='Name Server Group',
                                           name=group_name)
        return groups[0]

    def handle_create(self):
        with lockutils.lock(
                self.properties[self.MEMBER_SERVER][self.MEMBER_NAME],
                external=True,
                lock_file_prefix='infoblox-ns_group-update'):
            group_name = self.properties[self.GROUP_NAME]
            group = self._get_ns_group(group_name)
            LOG.debug("NSGROUP: %s" % group)

            member_role = self.properties[self.MEMBER_ROLE]
            member = self.properties[self.MEMBER_SERVER]
            if member_role == 'grid_primary':
                self._remove_member(group['grid_secondaries'], member)
                self._add_member(group['grid_primary'], member)
            elif member_role == 'grid_secondary':
                self._remove_member(group['grid_primary'], member)
                self._add_member(group['grid_secondaries'], member)

            self.infoblox().update_ns_group(group_name, group)
        self.resource_id_set("%s/%s/%s" %
                             (group_name, member_role, member['name']))

    def handle_delete(self):
        LOG.debug("NSGROUP %s DELETE" % self.resource_id)
        if self.resource_id is None:
            return None

        group_name, member_role, member_name = self.resource_id.split('/')
        member = {'name': member_name}
        field_name = 'grid_primary'
        if member_role == 'grid_secondary':
            field_name = 'grid_secondaries'

        with lockutils.lock(
                self.properties[self.MEMBER_SERVER][self.MEMBER_NAME],
                external=True,
                lock_file_prefix='infoblox-ns_group-update'):
            group = self._get_ns_group(group_name)

            LOG.debug("NSGROUP for DELETE: %s" % group)
            self._remove_member(group[field_name], member)
            LOG.debug("NSGROUP update DELETE: %s" % group)
            self.infoblox().update_ns_group(group_name, group)

    def _resolve_attribute(self, name):
        LOG.debug("RESOLVE ATTRIBUTE: %s" % name)
        group_name = self.properties[self.GROUP_NAME]
        group = self._get_ns_group(group_name)
        if name == self.NS_GROUP:
            return group
        return None
class NetMRIManagedResource(resource.Resource, mri.NetMRIResourceMixin):
    '''A resource managed via NetMRI jobs.

       The user may specify separate scripts for create and delete.
    '''

    ATTRIBUTES = (JOB, JOB_DETAILS) = ('job', 'job_details')

    CREATE_JOB = 'create_job'
    DELETE_JOB = 'delete_job'
    DELETE_JOB_ID = 'delete_job_id'

    support_status = support.SupportStatus(
        support.UNSUPPORTED, _('See support.infoblox.com for support.'))

    properties_schema = {
        constants.CONNECTION:
        resource_utils.connection_schema(constants.NETMRI),
        CREATE_JOB:
        properties.Schema(properties.Schema.MAP,
                          _("The job to execute on resource creation."),
                          required=True,
                          schema=mri.NetMRIResourceMixin.job_schema),
        DELETE_JOB:
        properties.Schema(properties.Schema.MAP,
                          _("The job to execute on resource deletion."),
                          required=True,
                          schema=mri.NetMRIResourceMixin.job_schema)
    }

    attributes_schema = mri.NetMRIResourceMixin.job_attributes_schema

    def handle_create(self):
        r = self._execute_job(self.properties[self.CREATE_JOB])
        self.resource_id_set(r['JobID'])

    def check_create_complete(self, handler_data):
        if not self.properties[self.CREATE_JOB][self.WAIT]:
            return True

        job_id = int(self.resource_id)
        return self._check_job_complete(job_id)

    def _check_job_complete(self, job_id):
        job = self.netmri.show('job', job_id)['job']
        LOG.debug("job = %s", job)
        if job['completed_at']:
            return True

        return False

    def handle_delete(self):
        r = self._execute_job(self.properties[self.DELETE_JOB])
        self.metadata_set({self.DELETE_JOB_ID: r['JobID']})

    def check_delete_complete(self, handler_data):
        if not self.properties[self.DELETE_JOB][self.WAIT]:
            return True

        md = self.metadata_get()
        job_id = int(md[self.DELETE_JOB_ID])
        return self._check_job_complete(job_id)

    def _resolve_attribute(self, name):
        return self._resolve_job_attribute(name)
Esempio n. 5
0
class Bgp(resource.Resource):
    """A resource which represents a BGP AS configuration.

    This resource is used configure a BGP Autonomous System on grid member.
    """

    PROPERTIES = (GRID_MEMBER, AS, HOLDDOWN, KEEPALIVE, LINK_DETECT,
                  AUTHENTICATION_MODE, BGP_NEIGHBOR_PASS, COMMENT, INTERFACE,
                  NEIGHBOR_IP,
                  REMOTE_AS) = ('grid_member', 'as', 'holddown', 'keepalive',
                                'link_detect', 'authentication_mode',
                                'bgp_neighbor_pass', 'comment', 'interface',
                                'neighbor_ip', 'remote_as')

    AUTHENTICATION_MODES = ('MD5', 'NONE')
    INTERFACES = ('LAN_HA', )

    support_status = support.SupportStatus(
        support.UNSUPPORTED, _('See support.infoblox.com for support.'))

    properties_schema = {
        constants.CONNECTION:
        resource_utils.connection_schema(constants.DDI),
        GRID_MEMBER:
        properties.Schema(properties.Schema.STRING,
                          _('Grid Member Name for BGP Neigbor configuration'),
                          required=True),
        AS:
        properties.Schema(properties.Schema.INTEGER,
                          _('The number of this autonomous system.'),
                          update_allowed=True,
                          required=True),
        HOLDDOWN:
        properties.Schema(properties.Schema.INTEGER,
                          _('The AS holddown timer (in seconds). '
                            'Valid values are from 3 to 65535.'),
                          update_allowed=True),
        KEEPALIVE:
        properties.Schema(
            properties.Schema.INTEGER,
            _('The AS keepalive timer (in seconds). Valid values are '
              'from 1 to 21845.'),
            update_allowed=True),
        LINK_DETECT:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('Determines if link detection on the interface is enabled.'),
            update_allowed=True),
        AUTHENTICATION_MODE:
        properties.Schema(
            properties.Schema.STRING,
            _('Determines the BGP authentication mode.'),
            constraints=[constraints.AllowedValues(AUTHENTICATION_MODES)],
            update_allowed=True,
            required=True),
        BGP_NEIGHBOR_PASS:
        properties.Schema(
            properties.Schema.STRING,
            _('The password for the BGP neighbor. This is required only if '
              'authentication_mode is set to "MD5". '),
            update_allowed=True),
        COMMENT:
        properties.Schema(properties.Schema.STRING,
                          _('User comments for this BGP neighbor.'),
                          update_allowed=True),
        INTERFACE:
        properties.Schema(
            properties.Schema.STRING,
            _('The interface that sends BGP advertisement information.'),
            update_allowed=True,
            constraints=[constraints.AllowedValues(INTERFACES)]),
        NEIGHBOR_IP:
        properties.Schema(properties.Schema.STRING,
                          _('The IP address of the BGP neighbor.'),
                          update_allowed=True,
                          required=True),
        REMOTE_AS:
        properties.Schema(properties.Schema.INTEGER,
                          _('The remote AS number of the BGP neighbor.'),
                          update_allowed=True,
                          required=True),
    }

    @property
    def infoblox(self):
        if not getattr(self, 'infoblox_object', None):
            conn = self.properties[constants.CONNECTION]
            self.infoblox_object = resource_utils.connect_to_infoblox(conn)
        return self.infoblox_object

    def handle_create(self):
        bgp_options_dict = {
            name: self.properties.get(name)
            for name in self.PROPERTIES
        }
        member_name = self.properties[self.GRID_MEMBER]
        # Create/update/delete actions are all doing read-change-update on bgp
        # configuration for particular member.Concurrent modifications leads to
        # missed data at scale and on bulk operations like deleting multiple
        # bgp neighbors.
        # Introduced semaphore to allow only one process to modify
        # particular grid member bgp configuration.
        with lockutils.lock(member_name,
                            external=True,
                            lock_file_prefix='infoblox-bgp-update'):
            self.infoblox.create_bgp_as(member_name, bgp_options_dict)

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            member_name = tmpl_diff['Properties']['grid_member']
            with lockutils.lock(member_name,
                                external=True,
                                lock_file_prefix='infoblox-bgp-update'):
                self.infoblox.create_bgp_as(
                    member_name,
                    tmpl_diff['Properties'],
                    old_neighbor_ip=self.properties[self.NEIGHBOR_IP])

    def handle_delete(self):
        with lockutils.lock(self.properties[self.GRID_MEMBER],
                            external=True,
                            lock_file_prefix='infoblox-bgp-update'):
            self.infoblox.delete_bgp_as(self.properties[self.GRID_MEMBER])
Esempio n. 6
0
class Ospf(resource.Resource):
    """A resource which represents an OSPF settings.

    This is used to configure OSPF parameters for the member.
    """

    PROPERTIES = (
        GRID_MEMBERS, ADVERTISE_INTERFACE_VLAN, AREA_ID, AREA_TYPE,
        AUTHENTICATION_KEY, AUTHENTICATION_TYPE, AUTO_CALC_COST_ENABLED,
        COMMENT, COST, DEAD_INTERVAL, HELLO_INTERVAL, INTERFACE,
        IS_IPV4, KEY_ID, RETRANSMIT_INTERVAL, TRANSMIT_DELAY
    ) = (
        'grid_members', 'advertise_interface_vlan', 'area_id', 'area_type',
        'authentication_key', 'authentication_type', 'auto_calc_cost_enabled',
        'comment', 'cost', 'dead_interval', 'hello_interval', 'interface',
        'is_ipv4', 'key_id', 'retransmit_interval', 'transmit_delay'
    )

    AREA_TYPES = (
        'NSSA',
        'STANDARD',
        'STUB'
    )

    AUTHENTICATION_TYPES = (
        'MESSAGE_DIGEST',
        'NONE',
        'SIMPLE'
    )

    INTERFACES = (
        'IP',
        'LAN_HA'
    )

    DELIM = '/'

    properties_schema = {
        constants.CONNECTION:
            resource_utils.connection_schema(constants.DDI),
        GRID_MEMBERS: properties.Schema(
            properties.Schema.LIST,
            _('List of Grid Member Names for Anycast IP address'),
            schema=properties.Schema(
                properties.Schema.STRING
            ),
            update_allowed=True,
            required=True),
        ADVERTISE_INTERFACE_VLAN: properties.Schema(
            properties.Schema.STRING,
            _('The VLAN used as the advertising interface '
              'for sending OSPF announcements.'),
            update_allowed=True),
        AREA_ID: properties.Schema(
            properties.Schema.STRING,
            _('The area ID value of the OSPF settings.'),
            update_allowed=True,
            required=True),
        AREA_TYPE: properties.Schema(
            properties.Schema.STRING,
            _('The OSPF area type.'),
            update_allowed=True,
            constraints=[
                constraints.AllowedValues(AREA_TYPES)
            ]),
        AUTHENTICATION_KEY: properties.Schema(
            properties.Schema.STRING,
            _('The authentication password to use for OSPF.'),
            update_allowed=True),
        AUTHENTICATION_TYPE: properties.Schema(
            properties.Schema.STRING,
            _('The authentication type used for the OSPF advertisement.'),
            update_allowed=True,
            constraints=[
                constraints.AllowedValues(AUTHENTICATION_TYPES)
            ]),
        AUTO_CALC_COST_ENABLED: properties.Schema(
            properties.Schema.BOOLEAN,
            _('Determines if auto calculate cost is enabled or not.'),
            update_allowed=True,
            required=True),
        COMMENT: properties.Schema(
            properties.Schema.STRING,
            _('A descriptive comment of the OSPF configuration.'),
            update_allowed=True),
        COST: properties.Schema(
            properties.Schema.INTEGER,
            _('The cost metric associated with the OSPF advertisement.'),
            update_allowed=True),
        DEAD_INTERVAL: properties.Schema(
            properties.Schema.INTEGER,
            _('The dead interval value of OSPF (in seconds).'),
            update_allowed=True),
        HELLO_INTERVAL: properties.Schema(
            properties.Schema.INTEGER,
            _('The hello interval value of OSPF.'),
            update_allowed=True,),
        INTERFACE: properties.Schema(
            properties.Schema.STRING,
            _('The interface that sends out OSPF advertisement information.'),
            update_allowed=True,
            constraints=[
                constraints.AllowedValues(INTERFACES)
            ]),
        IS_IPV4: properties.Schema(
            properties.Schema.BOOLEAN,
            _('The OSPF protocol version. '),
            update_allowed=True,
            required=True),
        KEY_ID: properties.Schema(
            properties.Schema.INTEGER,
            _('The hash key identifier to use for'
              ' "MESSAGE_DIGEST" authentication.'),
            update_allowed=True),
        RETRANSMIT_INTERVAL: properties.Schema(
            properties.Schema.INTEGER,
            _('The retransmit interval time of OSPF (in seconds).'),
            update_allowed=True),
        TRANSMIT_DELAY: properties.Schema(
            properties.Schema.INTEGER,
            _('The transmit delay value of OSPF (in seconds).'),
            update_allowed=True),
    }

    @property
    def infoblox(self):
        if not getattr(self, 'infoblox_object', None):
            conn = self.properties[constants.CONNECTION]
            self.infoblox_object = resource_utils.connect_to_infoblox(conn)
        return self.infoblox_object

    def handle_create(self):
        ospf_options_dict = {
            name: self.properties.get(name) for name in self.PROPERTIES}
        for member_name in self.properties[self.GRID_MEMBERS]:
            self.infoblox.create_ospf(member_name,
                                      ospf_options_dict)
        self.resource_id_set(self.properties[self.AREA_ID])

    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
        if prop_diff:
            new_members = set(tmpl_diff['Properties']['grid_members'])
            if 'grid_members' in prop_diff:
                old_members = set(self.properties.get('grid_members'))
                to_remove = old_members - new_members
                for member in to_remove:
                    self.infoblox.delete_ospf(self.properties[self.AREA_ID],
                                              member)

                if len(prop_diff) > 1:
                    # OSPF setting was changed, so need to update all members
                    to_add = new_members
                else:
                    # OSPF setting is not changes, so it add to new members
                    to_add = new_members - old_members
            else:
                # OSPF setting was changed, so need to update all members
                to_add = new_members

            for member in to_add:
                self.infoblox.create_ospf(
                    member,
                    tmpl_diff['Properties'],
                    old_area_id=self.properties[self.AREA_ID])

    def handle_delete(self):
        for member in self.properties[self.GRID_MEMBERS]:
            self.infoblox.delete_ospf(self.properties[self.AREA_ID], member)
class GridMember(resource.Resource):
    '''A resource which represents an Infoblox Grid Member.

    This is used to provision new grid members on an existing grid. See the
    Grid Master resource to create a new grid.
    '''

    PROPERTIES = (
        NAME,
        MODEL,
        LICENSES,
        TEMP_LICENSES,
        REMOTE_CONSOLE,
        ADMIN_PASSWORD,
        MGMT_PORT,
        LAN1_PORT,
        LAN2_PORT,
        HA_PORT,
        CONFIG_ADDR_TYPE,
        GM_IP,
        GM_CERTIFICATE,
        NAT_IP,
        # only 'enable' supported for now
        DNS_SETTINGS,
        DNS_ENABLE,
        DNS_RECURSIVE_RESOLVER,
        DNS_PORTS,
        DNS_ENABLE_FIXED_RRSET_ORDER_FQDNS,
        DNS_FIXED_RRSET_ORDER_FQDNS,
        DNS_USE_FIXED_RRSET_ORDER_FQDNS,
        DNS_DTC_HEALTH_SOURCE,
        DNS_DTC_HEALTH_SOURCE_ADDRESS,
        DNS_RPZ_QNAME_WAIT_RECURSE,
        DNS_USE_RPZ_QNAME_WAIT_RECURSE,
        DNS_LOG_DTC_GSLB,
        DNS_LOG_DTC_HEALTH,
        DNS_UNBOUND_LOGGING_LEVEL,
        HA_PAIR,
        VIP_PORT,
        USE_IPV4_VIP,
        VIRTUAL_ROUTER_ID,
        LAN2_VIRTUAL_ROUTER_ID,
        NODE2_MGMT_PORT,
        NODE2_LAN1_PORT,
        NODE2_LAN2_PORT,
        NODE2_HA_PORT,
        VIP_VLAN_ID,
        VIP6_VLAN_ID,
        UPDATE_ALLOWED_ADDRESS_PAIRS) = (
            'name', 'model', 'licenses', 'temp_licenses',
            'remote_console_enabled', 'admin_password', 'MGMT', 'LAN1', 'LAN2',
            'HA', 'config_addr_type', 'gm_ip', 'gm_certificate', 'nat_ip',
            'dns', 'enable', 'recursive_resolver', 'ports',
            'enable_fixed_rrset_order_fqdns', 'fixed_rrset_order_fqdns',
            'use_fixed_rrset_order_fqdns', 'dtc_health_source',
            'dtc_health_source_address', 'rpz_qname_wait_recurse',
            'use_rpz_qname_wait_recurse', 'log_dtc_glsb', 'log_dtc_health',
            'unbound_logging_level', 'ha_pair', 'VIP', 'use_ipv4_vip',
            'virtual_router_id', 'lan2_virtual_router_id', 'node2_MGMT',
            'node2_LAN1', 'node2_LAN2', 'node2_HA', 'vip_vlan_id',
            'vip6_vlan_id', 'update_allowed_address_pairs')

    ATTRIBUTES = (USER_DATA, NODE2_USER_DATA, NAME_ATTR,
                  DNS_UNBOUND_CAPABLE) = ('user_data', 'node2_user_data',
                                          'name', 'is_unbound_capable')

    ALLOWED_MODELS = ('CP-V1400', 'CP-V2200', 'CP-V800', 'IB-VM-100',
                      'IB-VM-1410', 'IB-VM-1420', 'IB-VM-2210', 'IB-VM-2220',
                      'IB-VM-4010', 'IB-VM-810', 'IB-VM-820', 'IB-VM-RSP',
                      'Rev1', 'Rev2')

    ALLOWED_LICENSES_PRE_PROVISION = ('cloud_api', 'dhcp', 'dns', 'dtc',
                                      'enterprise', 'fireeye', 'ms_management',
                                      'rpz', 'vnios')

    ALLOWED_LICENSES_TEMP = ('dns', 'rpz', 'cloud', 'cloud_api', 'enterprise',
                             'ipam', 'vnios', 'reporting')

    ALLOWED_CONFIG_ADDR_TYPES = ('IPV4', 'IPV6', 'BOTH')

    support_status = support.SupportStatus(
        support.UNSUPPORTED, _('See support.infoblox.com for support.'))

    properties_schema = {
        constants.CONNECTION:
        resource_utils.connection_schema(constants.DDI),
        NAME:
        properties.Schema(properties.Schema.STRING, _('Member name.')),
        MODEL:
        properties.Schema(
            properties.Schema.STRING,
            _('Infoblox model name.'),
            constraints=[constraints.AllowedValues(ALLOWED_MODELS)]),
        LICENSES:
        properties.Schema(
            properties.Schema.LIST,
            _('List of licenses to pre-provision.'),
            schema=properties.Schema(properties.Schema.STRING),
            constraints=[
                constraints.AllowedValues(ALLOWED_LICENSES_PRE_PROVISION)
            ]),
        TEMP_LICENSES:
        properties.Schema(
            properties.Schema.LIST,
            _('List of temporary licenses to apply to the member.'),
            schema=properties.Schema(properties.Schema.STRING),
            constraints=[constraints.AllowedValues(ALLOWED_LICENSES_TEMP)]),
        REMOTE_CONSOLE:
        properties.Schema(properties.Schema.BOOLEAN,
                          _('Enable the remote console.')),
        ADMIN_PASSWORD:
        properties.Schema(properties.Schema.STRING,
                          _('The password to use for the admin user.')),
        GM_IP:
        properties.Schema(properties.Schema.STRING,
                          _('The Gridmaster IP address.'),
                          required=True),
        GM_CERTIFICATE:
        properties.Schema(
            properties.Schema.STRING,
            _('The Gridmaster SSL certificate for verification.'),
            required=False),
        NAT_IP:
        properties.Schema(
            properties.Schema.STRING,
            _('If the GM will see this member as a NATed address, enter that '
              'address here.'),
            required=False),
        MGMT_PORT:
        resource_utils.port_schema(MGMT_PORT, False),
        LAN1_PORT:
        resource_utils.port_schema(LAN1_PORT, True),
        LAN2_PORT:
        resource_utils.port_schema(LAN2_PORT, False),
        HA_PORT:
        resource_utils.port_schema(HA_PORT, False),
        CONFIG_ADDR_TYPE:
        properties.Schema(
            properties.Schema.STRING,
            _('Address configuration types.'),
            constraints=[constraints.AllowedValues(ALLOWED_CONFIG_ADDR_TYPES)],
            default='IPV4'),
        DNS_SETTINGS:
        properties.Schema(properties.Schema.MAP,
                          _('The DNS settings for this member.'),
                          required=False,
                          schema={
                              DNS_ENABLE:
                              properties.Schema(
                                  properties.Schema.BOOLEAN,
                                  _('If true, enable DNS on this member.'),
                                  default=False),
                          }),
        HA_PAIR:
        properties.Schema(
            properties.Schema.BOOLEAN,
            _('"True" if member should be configured as HA pair.'),
            required=False,
            default=False),
        VIP_PORT:
        resource_utils.port_schema(VIP_PORT, False),
        USE_IPV4_VIP:
        properties.Schema(properties.Schema.BOOLEAN,
                          required=False,
                          default=True),
        VIRTUAL_ROUTER_ID:
        properties.Schema(
            properties.Schema.INTEGER,
            _('Virtual Router ID. '
              'Warning: Must be unique on the local network.'),
            required=False,
        ),
        LAN2_VIRTUAL_ROUTER_ID:
        properties.Schema(
            properties.Schema.INTEGER,
            _('LAN2 Virtual Router ID. '
              'Should set if configured a LAN2 address.'),
            required=False,
        ),
        NODE2_MGMT_PORT:
        resource_utils.port_schema(NODE2_MGMT_PORT, False),
        NODE2_LAN1_PORT:
        resource_utils.port_schema(NODE2_LAN1_PORT, False),
        NODE2_LAN2_PORT:
        resource_utils.port_schema(NODE2_LAN2_PORT, False),
        NODE2_HA_PORT:
        resource_utils.port_schema(NODE2_HA_PORT, False),
        VIP_VLAN_ID:
        properties.Schema(
            properties.Schema.INTEGER,
            required=False,
        ),
        VIP6_VLAN_ID:
        properties.Schema(
            properties.Schema.INTEGER,
            required=False,
        ),
        UPDATE_ALLOWED_ADDRESS_PAIRS:
        properties.Schema(properties.Schema.BOOLEAN,
                          required=False,
                          default=True),
    }

    attributes_schema = {
        USER_DATA:
        attributes.Schema(_('User data for the Nova boot process.'),
                          attributes.Schema.STRING),
        NODE2_USER_DATA:
        attributes.Schema(_('Node 2 user data for the Nova boot process.'),
                          attributes.Schema.STRING),
        NAME_ATTR:
        attributes.Schema(_('The member name.'), attributes.Schema.STRING)
    }

    def _make_network_settings(self, ip):
        subnet = self.client('neutron').show_subnet(ip['subnet_id'])['subnet']
        ipnet = netaddr.IPNetwork(subnet['cidr'])
        vip = {
            'address': ip['ip_address'],
            'subnet_mask': str(ipnet.netmask),
            'gateway': subnet['gateway_ip']
        }, subnet
        if self.properties[self.VIP_VLAN_ID]:
            vip['vlan_id'] = self.properties[self.VIP_VLAN_ID]
        return vip

    def _make_ipv6_settings(self, ip):
        subnet = self.client('neutron').show_subnet(ip['subnet_id'])['subnet']
        prefix = netaddr.IPNetwork(subnet['cidr'])
        autocfg = subnet['ipv6_ra_mode'] == "slaac"
        vip6 = {
            'virtual_ip': ip['ip_address'],
            'cidr_prefix': int(prefix.prefixlen),
            'gateway': subnet['gateway_ip'],
            'enabled': True,
            'auto_router_config_enabled': autocfg
        }, subnet
        if self.properties[self.VIP6_VLAN_ID]:
            vip6['vlan_id'] = self.properties[self.VIP6_VLAN_ID]
        return vip6

    def infoblox(self):
        if not getattr(self, 'infoblox_object', None):
            conn = self.properties[constants.CONNECTION]
            self.infoblox_object = resource_utils.connect_to_infoblox(conn)
        return self.infoblox_object

    def _make_port_network_settings(self, port_name, return_subnets=False):
        if self.properties[port_name] is None:
            return None

        port = self.client('neutron').show_port(
            self.properties[port_name])['port']

        if port is None:
            return None

        ipv4 = None
        ipv6 = None
        ipv4_subnet = None
        ipv6_subnet = None
        for ip in port['fixed_ips']:
            if ':' in ip['ip_address'] and ipv6 is None:
                ipv6, ipv6_subnet = self._make_ipv6_settings(ip)
            else:
                if ipv4 is None:
                    ipv4, ipv4_subnet = self._make_network_settings(ip)
        result = {'ipv4': ipv4, 'ipv6': ipv6}
        if return_subnets:
            result['ipv4_subnet'] = ipv4_subnet
            result['ipv6_subnet'] = ipv6_subnet
        return result

    def handle_create(self):
        mgmt = self._make_port_network_settings(self.MGMT_PORT)
        lan1 = self._make_port_network_settings(self.LAN1_PORT)
        lan2 = self._make_port_network_settings(self.LAN2_PORT)

        name = self.properties[self.NAME]
        nat = self.properties[self.NAT_IP]

        ha_pair = self.properties[self.HA_PAIR]
        if ha_pair:
            vrid = self.properties[self.VIRTUAL_ROUTER_ID]
            lan2_vrid = self.properties[self.LAN2_VIRTUAL_ROUTER_ID]
            vip = self._make_port_network_settings(self.VIP_PORT)
            node1_ha = self._make_port_network_settings(self.HA_PORT)
            node2_ha = self._make_port_network_settings(self.NODE2_HA_PORT)
            node2_lan1 = self._make_port_network_settings(self.NODE2_LAN1_PORT)
            node2_mgmt = self._make_port_network_settings(self.NODE2_MGMT_PORT)
            use_ipv4_vip = self.properties[self.USE_IPV4_VIP]
            config_addr_type = self.properties[self.CONFIG_ADDR_TYPE]
            if self.properties[self.UPDATE_ALLOWED_ADDRESS_PAIRS]:
                # Add 'allowed_address_pairs' to HA ports.
                resource_utils.fix_ha_ports_mac(
                    self.client('neutron'), vip, vrid, use_ipv4_vip,
                    (self.properties[self.HA_PORT],
                     self.properties[self.NODE2_HA_PORT]))
            # Create infoblox HA pair member
            self.infoblox().create_member(name=name,
                                          config_addr_type=config_addr_type,
                                          mgmt=mgmt,
                                          vip=vip,
                                          lan2=lan2,
                                          nat_ip=nat,
                                          ha_pair=ha_pair,
                                          use_v4_vrrp=use_ipv4_vip,
                                          node1_ha=node1_ha,
                                          node2_ha=node2_ha,
                                          node1_lan1=lan1,
                                          node2_lan1=node2_lan1,
                                          node2_mgmt=node2_mgmt,
                                          vrid=vrid,
                                          lan2_vrid=lan2_vrid)
        else:
            self.infoblox().create_member(name=name,
                                          mgmt=mgmt,
                                          vip=lan1,
                                          lan2=lan2,
                                          nat_ip=nat)

        self.infoblox().pre_provision_member(
            name,
            hwmodel=self.properties[self.MODEL],
            hwtype='IB-VNIOS',
            licenses=self.properties[self.LICENSES],
            ha_pair=ha_pair)

        dns = self.properties[self.DNS_SETTINGS]
        if dns:
            self.infoblox().configure_member_dns(name,
                                                 enable_dns=dns['enable'])

        self.resource_id_set(name)

    def _remove_from_all_ns_groups(self):
        # This is a workaround needed because Juno Heat does not honor
        # dependencies in nested autoscale group stacks.
        fields = {'name', 'grid_primary', 'grid_secondaries'}
        with lockutils.lock(self.resource_id,
                            external=True,
                            lock_file_prefix='infoblox-ns_group-update'):
            groups = self.infoblox().get_all_ns_groups(return_fields=fields)
            for group in groups:
                new_list = {}
                changed = False
                for field in ('grid_primary', 'grid_secondaries'):
                    new_list[field] = []
                    for member in group[field]:
                        if member['name'] != self.resource_id:
                            new_list[field].append(member)
                        else:
                            changed = True
                if changed:
                    self.infoblox().update_ns_group(group['name'], new_list)

    def handle_delete(self):
        if self.resource_id is not None:
            self._remove_from_all_ns_groups()
            self.infoblox().delete_member(self.resource_id)

    def _get_dhcp_status_for_port(self, port_settings):
        status = {'ipv4': False, 'ipv6': False}

        if port_settings['ipv4'] and port_settings['ipv4_subnet']:
            status['ipv4'] = port_settings['ipv4_subnet']['enable_dhcp']

        if port_settings['ipv6'] and port_settings['ipv6_subnet']:
            status['ipv6'] = port_settings['ipv6_subnet']['enable_dhcp']
        return status

    def _make_user_data(self, member, token, node=0):
        user_data = '#infoblox-config\n\n'

        temp_licenses = self.properties[self.TEMP_LICENSES]
        if temp_licenses and len(temp_licenses) > 0:
            user_data += 'temp_license: %s\n' % ','.join(temp_licenses)

        remote_console = self.properties[self.REMOTE_CONSOLE]
        if remote_console is not None:
            user_data += 'remote_console_enabled: %s\n' % remote_console

        admin_password = self.properties[self.ADMIN_PASSWORD]
        if admin_password is not None:
            user_data += 'default_admin_password: %s\n' % admin_password

        vip = member.get('vip_setting', None)
        ipv6 = member.get('ipv6_setting', None)
        enable_ha = member.get('enable_ha', False)
        if ipv6 and not ipv6.get('enabled', False):
            ipv6 = None

        lan1 = self._make_port_network_settings(self.LAN1_PORT,
                                                return_subnets=True)
        dhcp_status = self._get_dhcp_status_for_port(lan1)
        # Do not generate userdata for port if dhcp is enabled in subnet
        need_vip = vip and not dhcp_status.get('ipv4')
        need_ipv6 = ipv6 and not dhcp_status.get('ipv6')

        if need_vip or need_ipv6:
            user_data += 'lan1:\n'

        if need_vip:
            if enable_ha:
                node_info = member.get('node_info')
                user_data += '  v4_addr: %s\n' % node_info[node][
                    'lan_ha_port_setting']['mgmt_lan']
            else:
                user_data += '  v4_addr: %s\n' % vip['address']
            user_data += '  v4_netmask: %s\n' % vip['subnet_mask']
            user_data += '  v4_gw: %s\n' % vip['gateway']

        if need_ipv6:
            user_data += '  v6_addr: %s\n' % ipv6['virtual_ip']
            user_data += '  v6_cidr: %s\n' % ipv6['cidr_prefix']
            if not ipv6['auto_router_config_enabled']:
                user_data += '  v6_gw: %s\n' % ipv6['gateway']

        if token and len(token) > 0:
            user_data += 'gridmaster:\n'
            user_data += '  token: %s\n' % token[node]['token']
            user_data += '  ip_addr: %s\n' % self.properties[self.GM_IP]
            user_data += '  certificate: |\n    %s\n' % self.properties[
                self.GM_CERTIFICATE].replace('\n', '\n    ')

        LOG.debug('user_data: %s' % user_data)

        return user_data

    def _get_member_tokens(self, member):
        token = self.infoblox().connector.call_func('read_token',
                                                    member['_ref'],
                                                    {})['pnode_tokens']
        if len(token) == 0:
            self.infoblox().connector.call_func('create_token', member['_ref'],
                                                {})['pnode_tokens']
            token = self.infoblox().connector.call_func(
                'read_token', member['_ref'], {})['pnode_tokens']
        return token

    def _resolve_attribute(self, name):
        member_name = self.resource_id
        member = self.infoblox().get_member_obj(member_name,
                                                fail_if_no_member=True,
                                                return_fields=[
                                                    'host_name', 'vip_setting',
                                                    'ipv6_setting',
                                                    'enable_ha', 'node_info'
                                                ])
        LOG.debug("MEMBER for %s = %s" % (name, member))
        if name == self.USER_DATA:
            token = self._get_member_tokens(member)
            return self._make_user_data(member, token, 0)
        if name == self.NODE2_USER_DATA:
            token = self._get_member_tokens(member)
            return self._make_user_data(member, token, 1)
        if name == self.NAME_ATTR:
            return member['host_name']
        return None