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])
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)
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])
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