def _rest_get_hostconfigs(self): try: response = self.odl_rest_client.get() response.raise_for_status() hostconfigs = response.json()['hostconfigs']['hostconfig'] except exceptions.ConnectionError: LOG.error(_LE("Cannot connect to the Opendaylight Controller"), exc_info=True) return None except exceptions.HTTPError as e: # restconf returns 404 on operation when there is no entry if e.response.status_code == codes.not_found: LOG.debug("Response code not_found (404)" " treated as an empty list") return [] else: LOG.warning(_LW("REST/GET odl hostconfig failed, "), exc_info=True) return None except KeyError: LOG.error(_LE("got invalid hostconfigs"), exc_info=True) return None except Exception: LOG.warning(_LW("REST/GET odl hostconfig failed, "), exc_info=True) return None else: if LOG.isEnabledFor(logging.DEBUG): _hconfig_str = jsonutils.dumps(response, sort_keys=True, indent=4, separators=(',', ': ')) LOG.debug("ODLPORTBINDING hostconfigs:\n%s", _hconfig_str) return hostconfigs
def _sync_pending_rows(self, session, exit_after_run): while True: LOG.debug("Thread walking database") row = db.get_oldest_pending_db_row_with_lock(session) if not row: LOG.debug("No rows to sync") break # Validate the operation validate_func = ( dependency_validations.VALIDATION_MAP[row.object_type]) valid = validate_func(session, row) if not valid: LOG.info( _LI("%(operation)s %(type)s %(uuid)s is not a " "valid operation yet, skipping for now"), { 'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid }) # Set row back to pending. db.update_db_row_state(session, row, odl_const.PENDING) if exit_after_run: break continue LOG.info( _LI("Syncing %(operation)s %(type)s %(uuid)s"), { 'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid }) # Add code to sync this to ODL method, urlpath, to_send = self._json_data(row) try: self.client.sendjson(method, urlpath, to_send) db.update_db_row_state(session, row, odl_const.COMPLETED) except exceptions.ConnectionError as e: # Don't raise the retry count, just log an error LOG.error(_LE("Cannot connect to the Opendaylight Controller")) # Set row back to pending db.update_db_row_state(session, row, odl_const.PENDING) # Break our of the loop and retry with the next # timer interval break except Exception as e: LOG.error( _LE("Error syncing %(type)s %(operation)s," " id %(uuid)s Error: %(error)s"), { 'type': row.object_type, 'uuid': row.object_uuid, 'operation': row.operation, 'error': e.message }) db.update_pending_db_row_retry(session, row, self._row_retry_count)
def _sync_pending_rows(self, session, exit_after_run): while True: LOG.debug("Thread walking database") row = db.get_oldest_pending_db_row_with_lock(session) if not row: LOG.debug("No rows to sync") break # Validate the operation validate_func = (dependency_validations. VALIDATION_MAP[row.object_type]) valid = validate_func(session, row) if not valid: LOG.info(_LI("%(operation)s %(type)s %(uuid)s is not a " "valid operation yet, skipping for now"), {'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid}) # Set row back to pending. db.update_db_row_state(session, row, odl_const.PENDING) if exit_after_run: break continue LOG.info(_LI("Syncing %(operation)s %(type)s %(uuid)s"), {'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid}) # Add code to sync this to ODL method, urlpath, to_send = self._json_data(row) try: self.client.sendjson(method, urlpath, to_send) db.update_db_row_state(session, row, odl_const.COMPLETED) except exceptions.ConnectionError as e: # Don't raise the retry count, just log an error LOG.error(_LE("Cannot connect to the Opendaylight Controller")) # Set row back to pending db.update_db_row_state(session, row, odl_const.PENDING) # Break our of the loop and retry with the next # timer interval break except Exception as e: LOG.error(_LE("Error syncing %(type)s %(operation)s," " id %(uuid)s Error: %(error)s"), {'type': row.object_type, 'uuid': row.object_uuid, 'operation': row.operation, 'error': e.message}) db.update_pending_db_row_retry(session, row, self._row_retry_count)
def sync_from_callback(self, context, operation, res_type, res_id, resource_dict): object_type = res_type.plural.replace('_', '-') try: if operation == odl_const.ODL_DELETE: self.out_of_sync |= not self.client.try_delete(object_type + '/' + res_id) else: if operation == odl_const.ODL_CREATE: urlpath = object_type method = 'post' elif operation == odl_const.ODL_UPDATE: urlpath = object_type + '/' + res_id method = 'put' self.client.sendjson(method, urlpath, resource_dict) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _LE("Unable to perform %(operation)s on " "%(object_type)s %(res_id)s " "%(resource_dict)s"), { 'operation': operation, 'object_type': object_type, 'res_id': res_id, 'resource_dict': resource_dict }) self.out_of_sync = True
def send_resource(self, operation, object_type, data): """Send over a single resource from Neutron to OpenDaylight. Prepare a rest call and send a single resource to ODL NB """ # Convert underscores to dashes in the URL for ODL object_type_url = object_type.replace('_', '-') try: obj_id = data['id'] if operation == odl_const.ODL_DELETE: self.client.try_delete(object_type_url + '/' + obj_id) else: if operation == odl_const.ODL_CREATE: urlpath = object_type_url method = 'post' elif operation == odl_const.ODL_UPDATE: urlpath = object_type_url + '/' + obj_id method = 'put' policy_data = self.convert_rules_format(data) self.client.sendjson(method, urlpath, {odl_const.ODL_POLICY: policy_data}) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _LE("Unable to perform %(operation)s on " "%(object_type)s %(object_id)s"), { 'operation': operation, 'object_type': object_type, 'object_id': obj_id })
def _vif_type_from_conf(conf, userspace_datapath_types): # take vif_type from datapath_type ------------------------------------ if conf.datapath_type: # take it from datapath_type if conf.datapath_type in USERSPACE_DATAPATH_TYPES: if conf.datapath_type not in userspace_datapath_types: LOG.warning( _LW("Using user space data path type '%s' even if no " "support was detected."), conf.datapath_type) return 'vhostuser' else: return 'ovs' # take vif_type from ovs_dpdk ----------------------------------------- if conf.ovs_dpdk is True: if userspace_datapath_types: return 'vhostuser' raise ValueError( _LE("--ovs_dpdk option was specified but the 'netdev' datapath_type " "was not enabled. " "To override use option --datapath_type=netdev")) elif conf.ovs_dpdk is False: return 'ovs' # take detected dtype ------------------------------------------------- if userspace_datapath_types: return 'vhostuser' else: return 'ovs'
def main(args=None): """Main.""" conf = setup_conf(args) if os.geteuid() != 0: LOG.error(_LE('Root permissions are required to configure ovsdb.')) return 1 try: set_ovs_extid_hostconfigs(conf=conf, ovs_vsctl=OvsVsctl()) except Exception as ex: # pylint: disable=broad-except LOG.error(_LE("Fatal error: %s"), ex, exc_info=conf.debug) return 1 else: return 0
def _create_parsers(self): for parser_name in self.network_topology_parsers: try: yield NetworkTopologyParser.create_parser(parser_name) except Exception: LOG.exception( _LE('Error initializing topology parser: %(parser_name)r'), {'parser_name': parser_name})
def _fetch_elements_by_host(self, host_name, cache_timeout=60.0): '''Yields all network topology elements referring to given host name ''' host_addresses = [host_name] try: # It uses both compute host name and known IP addresses to # recognize topology elements valid for given computed host ip_addresses = utils.get_addresses_by_name(host_name) except Exception: ip_addresses = [] LOG.exception( _LE('Unable to resolve IP addresses for host %(host_name)r'), {'host_name': host_name}) else: host_addresses.extend(ip_addresses) yield_elements = set() try: for __, element in self._elements_by_ip.fetch_all( host_addresses, cache_timeout): # yields every element only once if element not in yield_elements: yield_elements.add(element) yield element except cache.CacheFetchError as error: # This error is expected on most of the cases because typically not # all host_addresses maps to a network topology element. if yield_elements: # As we need only one element for every host we ignore the # case in which others host addresseses didn't map to any host LOG.debug( 'Host addresses not found in networking topology: %s', ', '.join(error.missing_keys)) else: LOG.exception( _LE('No such network topology elements for given host ' '%(host_name)r and given IPs: %(ip_addresses)s.'), { 'host_name': host_name, 'ip_addresses': ", ".join(ip_addresses) }) error.reraise_cause()
def _fetch_elements_by_host(self, host_name, cache_timeout=60.0): '''Yields all network topology elements referring to given host name ''' host_addresses = [host_name] try: # It uses both compute host name and known IP addresses to # recognize topology elements valid for given computed host ip_addresses = utils.get_addresses_by_name(host_name) except Exception: ip_addresses = [] LOG.exception( _LE('Unable to resolve IP addresses for host %(host_name)r'), {'host_name': host_name}) else: host_addresses.extend(ip_addresses) yield_elements = set() try: for __, element in self._elements_by_ip.fetch_all( host_addresses, cache_timeout): # yields every element only once if element not in yield_elements: yield_elements.add(element) yield element except cache.CacheFetchError as error: # This error is expected on most of the cases because typically not # all host_addresses maps to a network topology element. if yield_elements: # As we need only one element for every host we ignore the # case in which others host addresseses didn't map to any host LOG.debug( 'Host addresses not found in networking topology: %s', ', '.join(error.missing_keys)) else: LOG.exception( _LE('No such network topology elements for given host ' '%(host_name)r and given IPs: %(ip_addresses)s.'), {'host_name': host_name, 'ip_addresses': ", ".join(ip_addresses)}) error.reraise_cause()
def _hconfig_bind_port(self, port_context, hconfig): """bind port after validating odl host configuration.""" valid_segment = None for segment in port_context.segments_to_bind: if self._is_valid_segment(segment, hconfig['configurations']): valid_segment = segment break else: LOG.debug("No valid segments found!") return False confs = hconfig['configurations']['supported_vnic_types'] # nova provides vnic_type in port_context to neutron. # neutron provides supported vif_type for binding based on vnic_type # in this case ODL hostconfigs has the vif_type to bind for vnic_type vnic_type = port_context.current.get(portbindings.VNIC_TYPE) if vnic_type != portbindings.VNIC_NORMAL: LOG.error(_LE("Binding failed: unsupported VNIC %s"), vnic_type) return False for conf in confs: if conf["vnic_type"] == vnic_type: vif_type = conf.get('vif_type', portbindings.VIF_TYPE_OVS) LOG.debug("Binding vnic:'%s' to vif:'%s'", vnic_type, vif_type) break else: vif_type = portbindings.VIF_TYPE_OVS # default: OVS LOG.warning( _LW("No supported vif type found for host %s!, " "defaulting to OVS"), port_context.host) vif_details = conf.get('vif_details', {}) if not vif_details: # empty vif_details could be trouble, warn. LOG.warning(_LW("hostconfig:vif_details was empty!")) LOG.debug( "Bind port %(port)s on network %(network)s with valid " "segment %(segment)s and VIF type %(vif_type)r " "VIF details %(vif_details)r.", { 'port': port_context.current['id'], 'network': port_context.network.current['id'], 'segment': valid_segment, 'vif_type': vif_type, 'vif_details': vif_details }) port_context.set_binding(valid_segment[driver_api.ID], vif_type, vif_details, status=nl_const.PORT_STATUS_ACTIVE) return True
def delete_l2_gateway_postcommit(self, context, l2_gateway_id): LOG.info(_LI("ODL: Delete L2Gateway %(l2gatewayid)s"), {'l2gatewayid': l2_gateway_id}) url = L2GATEWAYS + '/' + l2_gateway_id try: self.client.try_delete(url) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE("ODL: L2Gateway delete" " failed for gateway_id %(l2gatewayid)s"), {'l2gatewayid': l2_gateway_id})
def create_l2_gateway_postcommit(self, context, l2_gateway): LOG.info(_LI("ODL: Create L2Gateway %(l2gateway)s"), {'l2gateway': l2_gateway}) request = {'l2_gateway': l2_gateway} try: self.client.sendjson('post', L2GATEWAYS, request) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE("ODL: L2Gateway create" " failed for gateway %(l2gatewayid)s"), {'l2gatewayid': l2_gateway['id']})
def delete_l2_gateway_connection_postcommit(self, context, l2_gateway_connection_id): LOG.info(_LI("ODL: Delete L2Gateway connection %(l2gwconnid)s"), {'l2gwconnid': l2_gateway_connection_id}) url = L2GATEWAY_CONNECTIONS + '/' + l2_gateway_connection_id try: self.client.try_delete(url) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE("ODL: L2Gateway connection delete" " failed for connection %(l2gwconnid)s"), {'l2gwconnid': l2_gateway_connection_id})
def journal_recovery(session): for row in db.get_all_db_rows_by_state(session, odl_const.FAILED): try: LOG.debug("Attempting recovery of journal entry %s.", row) odl_resource = _CLIENT.get_client().get_resource(row.object_type, row.object_uuid) if odl_resource is not None: _handle_existing_resource(session, row) else: _handle_non_existing_resource(session, row) except Exception: LOG.exception( _LE("Failure while recovering journal entry %s."), row)
def _execute_op(self, operation, session): op_details = operation.__name__ if operation.__doc__: op_details += " (%s)" % operation.func_doc try: LOG.info(_LI("Starting maintenance operation %s."), op_details) db.update_maintenance_operation(session, operation=operation) operation(session=session) LOG.info(_LI("Finished maintenance operation %s."), op_details) except Exception: LOG.exception(_LE("Failed during maintenance operation %s."), op_details)
def sendjson(self, method, urlpath, obj): """Send json to the OpenDaylight controller.""" data = jsonutils.dumps(obj, indent=2) if obj else None try: return self._check_rensponse( self.request(method, urlpath, data)) except Exception: with excutils.save_and_reraise_exception(): LOG.error(_LE("REST request ( %(method)s ) to " "url ( %(urlpath)s ) is failed." "Request body : [%(body)s] service"), {'method': method, 'urlpath': urlpath, 'body': obj})
def create_l2_gateway_connection_postcommit(self, context, l2_gateway_connection): LOG.info(_LI("ODL: Create L2Gateway connection %(l2gwconn)s"), {'l2gwconn': l2_gateway_connection}) odl_l2_gateway_connection = copy.deepcopy(l2_gateway_connection) odl_l2_gateway_connection['gateway_id'] = ( l2_gateway_connection['l2_gateway_id']) odl_l2_gateway_connection.pop('l2_gateway_id') request = {'l2gateway_connection': odl_l2_gateway_connection} try: self.client.sendjson('post', L2GATEWAY_CONNECTIONS, request) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE("ODL: L2Gateway connection create" " failed for gateway %(l2gwconnid)s"), {'l2gwconnid': l2_gateway_connection['l2_gateway_id']})
def run_sync_thread(self, exit_after_run=False): while True: try: self.event.wait() self.event.clear() session = neutron_db_api.get_session() self._sync_pending_rows(session, exit_after_run) LOG.debug("Clearing sync thread event") if exit_after_run: # Permanently waiting thread model breaks unit tests # Adding this arg to exit here only for unit tests break except Exception: # Catch exceptions to protect the thread while running LOG.exception(_LE("Error on run_sync_thread"))
def bind_port(self, port_context): controller_details = {'name': self.name, 'controller': self.controller} try: self.controller.bind_port(port_context) except Exception: with excutils.save_and_reraise_exception(): LOG.exception( _LE("Controller '%(name)s (%(controller)r)' had an error " "when binding port."), controller_details) else: if port_context._new_bound_segment: LOG.info( _LI("Controller '%(name)s (%(controller)r)' has bound " "port."), controller_details) else: LOG.debug( "Controller %(name)s (%(controller)r) hasn't bound " "port.", controller_details)
def _fetch_and_parse_network_topology(self, addresses): # The cache calls this method to fecth new elements when at least one # of the addresses is not in the cache or it has expired. # pylint: disable=unused-argument LOG.info(_LI('Fetch network topology from ODL.')) response = self._client.get() response.raise_for_status() network_topology = response.json() if LOG.isEnabledFor(logging.DEBUG): topology_str = jsonutils.dumps(network_topology, sort_keys=True, indent=4, separators=(',', ': ')) LOG.debug("Got network topology:\n%s", topology_str) at_least_one_element_for_asked_addresses = False for parser in self._parsers: try: for element in parser.parse_network_topology(network_topology): if not isinstance(element, NetworkTopologyElement): raise TypeError( _("Yield element doesn't implement interface " "'NetworkTopologyElement': {!r}").format( element)) # the same element can be known by more host addresses for host_address in element.host_addresses: if host_address in addresses: at_least_one_element_for_asked_addresses = True yield host_address, element except Exception: LOG.exception( _LE("Parser %(parser)r failed to parse network topology."), {'parser': parser}) if not at_least_one_element_for_asked_addresses: # this will mark entries for given addresses as failed to allow # calling this method again as soon it is requested and avoid # waiting for cache expiration raise ValueError( _('No such topology element for given host addresses: {}'). format(', '.join(addresses)))
def _fetch_and_parse_network_topology(self, addresses): # The cache calls this method to fecth new elements when at least one # of the addresses is not in the cache or it has expired. # pylint: disable=unused-argument LOG.info(_LI('Fetch network topology from ODL.')) response = self._client.get() response.raise_for_status() network_topology = response.json() if LOG.isEnabledFor(logging.DEBUG): topology_str = jsonutils.dumps( network_topology, sort_keys=True, indent=4, separators=(',', ': ')) LOG.debug("Got network topology:\n%s", topology_str) at_least_one_element_for_asked_addresses = False for parser in self._parsers: try: for element in parser.parse_network_topology(network_topology): if not isinstance(element, NetworkTopologyElement): raise TypeError( "Yield element doesn't implement interface " "'NetworkTopologyElement': {!r}".format(element)) # the same element can be known by more host addresses for host_address in element.host_addresses: if host_address in addresses: at_least_one_element_for_asked_addresses = True yield host_address, element except Exception: LOG.exception( _LE("Parser %(parser)r failed to parse network topology."), {'parser': parser}) if not at_least_one_element_for_asked_addresses: # this will mark entries for given addresses as failed to allow # calling this method again as soon it is requested and avoid # waiting for cache expiration raise ValueError( 'No such topology element for given host addresses: {}'.format( ', '.join(addresses)))
def sync_from_callback(self, context, operation, res_type, res_id, resource_dict, **kwrags): object_type = odl_utils.neutronify(res_type.plural) try: if operation == odl_const.ODL_DELETE: self.out_of_sync |= not self.client.try_delete(object_type + '/' + res_id) else: if operation == odl_const.ODL_CREATE: urlpath = object_type method = 'post' elif operation == odl_const.ODL_UPDATE: urlpath = object_type + '/' + res_id method = 'put' self.client.sendjson(method, urlpath, resource_dict) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _LE("Unable to perform %(operation)s on " "%(object_type)s %(res_id)s " "%(resource_dict)s"), { 'operation': operation, 'object_type': object_type, 'res_id': res_id, 'resource_dict': resource_dict }) self.out_of_sync = True # NOTE(yamahata) when security group is created, default rules # are also created. if (operation == odl_const.ODL_CREATE and res_type.singular == odl_const.ODL_SG): for rule in resource_dict[odl_const.ODL_SG][ odl_const.ODL_SG_RULES]: self.sync_from_callback( context, odl_const.ODL_CREATE, odl_call._RESOURCE_MAPPING[resources.SECURITY_GROUP_RULE], rule['id'], {odl_const.ODL_SG_RULE: rule})
def sync_single_resource(self, operation, object_type, context): """Sync over a single resource from Neutron to OpenDaylight. Handle syncing a single operation over to OpenDaylight, and correctly filter attributes out which are not required for the requisite operation (create or update) being handled. """ # Convert underscores to dashes in the URL for ODL object_type_url = odl_utils.neutronify(object_type) try: obj_id = context.current['id'] if operation == odl_const.ODL_DELETE: self.out_of_sync |= not self.client.try_delete( object_type_url + '/' + obj_id) else: filter_cls = self.FILTER_MAP[object_type] if operation == odl_const.ODL_CREATE: urlpath = object_type_url method = 'post' attr_filter = filter_cls.filter_create_attributes elif operation == odl_const.ODL_UPDATE: urlpath = object_type_url + '/' + obj_id method = 'put' attr_filter = filter_cls.filter_update_attributes resource = copy.deepcopy(context.current) attr_filter(resource, context) self.client.sendjson(method, urlpath, {object_type_url[:-1]: resource}) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _LE("Unable to perform %(operation)s on " "%(object_type)s %(object_id)s"), { 'operation': operation, 'object_type': object_type, 'object_id': obj_id }) self.out_of_sync = True
def _update_agents_db(self, hostconfigs): LOG.debug("ODLPORTBINDING Updating agents DB with ODL hostconfigs") agents_db = self._get_neutron_db_plugin() if not agents_db: # if ML2 is still initializing LOG.warning( _LW("ML2 still initializing, Will retry agentdb" " update on next poll")) return # Retry on next poll for host_config in hostconfigs: try: self.agentdb_row['host'] = host_config['host-id'] self.agentdb_row['agent_type'] = host_config['host-type'] self.agentdb_row['configurations'] = jsonutils.loads( host_config['config']) agents_db.create_or_update_agent(context.get_admin_context(), self.agentdb_row) except Exception: LOG.exception(_LE("Unable to update agentdb.")) continue # try next hostcofig
def sync_from_callback(self, operation, object_type, res_id, resource_dict): try: if operation == odl_const.ODL_DELETE: self.out_of_sync |= not self.client.try_delete( object_type + '/' + res_id) else: if operation == odl_const.ODL_CREATE: urlpath = object_type method = 'post' elif operation == odl_const.ODL_UPDATE: urlpath = object_type + '/' + res_id method = 'put' self.client.sendjson(method, urlpath, resource_dict) except Exception: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to perform %(operation)s on " "%(object_type)s %(res_id)s %(resource_dict)s"), {'operation': operation, 'object_type': object_type, 'res_id': res_id, 'resource_dict': resource_dict}) self.out_of_sync = True
def sync_single_resource(self, operation, object_type, context): """Sync over a single resource from Neutron to OpenDaylight. Handle syncing a single operation over to OpenDaylight, and correctly filter attributes out which are not required for the requisite operation (create or update) being handled. """ # Convert underscores to dashes in the URL for ODL object_type_url = object_type.replace('_', '-') try: obj_id = context.current['id'] if operation == odl_const.ODL_DELETE: self.out_of_sync |= not self.client.try_delete( object_type_url + '/' + obj_id) else: filter_cls = self.FILTER_MAP[object_type] if operation == odl_const.ODL_CREATE: urlpath = object_type_url method = 'post' attr_filter = filter_cls.filter_create_attributes elif operation == odl_const.ODL_UPDATE: urlpath = object_type_url + '/' + obj_id method = 'put' attr_filter = filter_cls.filter_update_attributes resource = copy.deepcopy(context.current) attr_filter(resource, context) self.client.sendjson(method, urlpath, {object_type_url[:-1]: resource}) except Exception: with excutils.save_and_reraise_exception(): LOG.error(_LE("Unable to perform %(operation)s on " "%(object_type)s %(object_id)s"), {'operation': operation, 'object_type': object_type, 'object_id': obj_id}) self.out_of_sync = True
def sync_from_callback_precommit(self, context, operation, res_type, res_id, resource_dict, **kwargs): object_type = res_type.singular if resource_dict is not None: resource_dict = resource_dict[object_type] if (operation == odl_const.ODL_CREATE and object_type == odl_const.ODL_SG): self._sync_security_group_create_precommit(context, operation, object_type, res_id, resource_dict) return # NOTE(yamahata): in security group/security gorup rule case, # orm object is passed. not resource dict. So we have to convert it # into resource_dict if not isinstance(resource_dict, dict) and resource_dict is not None: if object_type == odl_const.ODL_SG: resource_dict = self._make_security_group_dict(resource_dict) elif object_type == odl_const.ODL_SG_RULE: resource_dict = self._make_security_group_rule_dict( resource_dict) # NOTE(yamahata): bug work around # callback for update of security grouop doesn't pass complete # info. So we have to build it. Once the bug is fixed, remove # this bug work around. # https://launchpad.net/bugs/1546910 # https://review.openstack.org/#/c/281693/ elif (object_type == odl_const.ODL_SG and operation == odl_const.ODL_UPDATE): # NOTE(yamahata): precommit_update is called before updating # values. so context.session.{new, dirty} doesn't include sg # in question. a dictionary with new values needs to be build. core_plugin = directory.get_plugin() sg = core_plugin._get_security_group(context, res_id) tmp_dict = self._make_security_group_dict(sg) tmp_dict.update(resource_dict) resource_dict = tmp_dict object_uuid = (resource_dict.get('id') if operation == 'create' else res_id) if object_uuid is None: # NOTE(yamahata): bug work around bug/1546910 # TODO(yamahata): once the following patch is merged # remove this bug work around # https://review.openstack.org/#/c/281693/ assert object_type == odl_const.ODL_SG_RULE # NOTE(yamahata): bulk creation case # context.session.new accumulates all newly created orm object. # there is no easy way to pick up the lastly added orm object. rules = [ rule for rule in context.session.new if (isinstance(rule, securitygroup.SecurityGroupRule)) ] if len(rules) == 1: object_uuid = rules[0].id resource_dict['id'] = object_uuid else: LOG.error(_LE("bulk creation of sgrule isn't supported")) raise NotImplementedError( _("unsupporetd bulk creation of security group rule")) journal.record(context, object_type, object_uuid, operation, resource_dict) # NOTE(yamahata): DB auto deletion # Security Group Rule under this Security Group needs to # be deleted. At NeutronDB layer rules are auto deleted with # cascade='all,delete'. if (object_type == odl_const.ODL_SG and operation == odl_const.ODL_DELETE): for rule in kwargs['security_group'].rules: journal.record(context, odl_const.ODL_SG_RULE, rule.id, odl_const.ODL_DELETE, [object_uuid])
def bind_port(self, port_context): """Set binding for a valid segment """ host_name = port_context.host elements = list() try: # Append to empty list to add as much elements as possible # in the case it raises an exception elements.extend(self._fetch_elements_by_host(host_name)) except Exception: LOG.exception( _LE('Error fetching elements for host %(host_name)r.'), {'host_name': host_name}, exc_info=1) if not elements: # In case it wasn't able to find any network topology element # for given host then it uses the legacy OVS one keeping the old # behaviour LOG.warning( _LW('Using legacy OVS network topology element for port ' 'binding for host: %(host_name)r.'), {'host_name': host_name}) # Imported here to avoid cyclic module dependencies from networking_odl.ml2 import ovsdb_topology elements = [ovsdb_topology.OvsdbNetworkTopologyElement()] # TODO(Federico Ressi): in the case there are more candidate virtual # switches instances for the same host it choses one for binding # port. As there isn't any know way to perform this selection it # selects a VIF type that is valid for all switches that have # been found and a VIF type valid for all them. This has to be improved for vif_type in self.valid_vif_types: vif_type_is_valid_for_all = True for element in elements: if vif_type not in element.valid_vif_types: # it is invalid for at least one element: discard it vif_type_is_valid_for_all = False break if vif_type_is_valid_for_all: # This is the best VIF type valid for all elements LOG.debug( "Found VIF type %(vif_type)r valid for all network " "topology elements for host %(host_name)r.", {'vif_type': vif_type, 'host_name': host_name}) for element in elements: # It assumes that any element could be good for given host # In most of the cases I expect exactely one element for # every compute host try: return element.bind_port( port_context, vif_type, self._vif_details) except Exception: LOG.exception( _LE('Network topology element has failed binding ' 'port:\n%(element)s'), {'element': element.to_json()}) LOG.error( _LE('Unable to bind port element for given host and valid VIF ' 'types:\n' '\thostname: %(host_name)s\n' '\tvalid VIF types: %(valid_vif_types)s'), {'host_name': host_name, 'valid_vif_types': ', '.join(self.valid_vif_types)})
def sync_pending_row(self, exit_after_run=False): # Block until all pending rows are processed session = neutron_db_api.get_session() while not self.event.is_set(): self.event.wait() # Clear the event and go back to waiting after # the sync block exits self.event.clear() while True: LOG.debug("Thread walking database") row = db.get_oldest_pending_db_row_with_lock(session) if not row: LOG.debug("No rows to sync") break # Validate the operation validate_func = ( dependency_validations.VALIDATION_MAP[row.object_type]) valid = validate_func(session, row) if not valid: LOG.info( _LI("%(operation)s %(type)s %(uuid)s is not a " "valid operation yet, skipping for now"), { 'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid }) # Set row back to pending. db.update_db_row_state(session, row, odl_const.PENDING) if exit_after_run: break continue LOG.info( _LI("Syncing %(operation)s %(type)s %(uuid)s"), { 'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid }) # Add code to sync this to ODL method, urlpath, to_send = self._json_data(row) try: self.client.sendjson(method, urlpath, to_send) db.update_db_row_state(session, row, odl_const.COMPLETED) except exceptions.ConnectionError as e: # Don't raise the retry count, just log an error LOG.error( _LE("Cannot connect to the Opendaylight " "Controller")) # Set row back to pending db.update_db_row_state(session, row, odl_const.PENDING) # Break our of the loop and retry with the next # timer interval break except Exception as e: LOG.error( _LE("Error syncing %(type)s %(operation)s," " id %(uuid)s Error: %(error)s"), { 'type': row.object_type, 'uuid': row.object_uuid, 'operation': row.operation, 'error': e.message }) db.update_pending_db_row_retry(session, row, self._row_retry_count) LOG.debug("Clearing sync thread event") if exit_after_run: # Permanently waiting thread model breaks unit tests # Adding this arg to exit here only for unit tests break
def bind_port(self, port_context): """Set binding for a valid segment """ host_name = port_context.host elements = list() try: # Append to empty list to add as much elements as possible # in the case it raises an exception elements.extend(self._fetch_elements_by_host(host_name)) except Exception: LOG.exception( _LE('Error fetching elements for host %(host_name)r.'), {'host_name': host_name}, exc_info=1) if not elements: # In case it wasn't able to find any network topology element # for given host then it uses the legacy OVS one keeping the old # behaviour LOG.warning( _LW('Using legacy OVS network topology element for port ' 'binding for host: %(host_name)r.'), {'host_name': host_name}) # Imported here to avoid cyclic module dependencies from networking_odl.ml2 import ovsdb_topology elements = [ovsdb_topology.OvsdbNetworkTopologyElement()] # TODO(Federico Ressi): in the case there are more candidate virtual # switches instances for the same host it choses one for binding # port. As there isn't any know way to perform this selection it # selects a VIF type that is valid for all switches that have # been found and a VIF type valid for all them. This has to be improved for vif_type in self.valid_vif_types: vif_type_is_valid_for_all = True for element in elements: if vif_type not in element.valid_vif_types: # it is invalid for at least one element: discard it vif_type_is_valid_for_all = False break if vif_type_is_valid_for_all: # This is the best VIF type valid for all elements LOG.debug( "Found VIF type %(vif_type)r valid for all network " "topology elements for host %(host_name)r.", { 'vif_type': vif_type, 'host_name': host_name }) for element in elements: # It assumes that any element could be good for given host # In most of the cases I expect exactely one element for # every compute host try: return element.bind_port(port_context, vif_type, self._vif_details) except Exception: LOG.exception( _LE('Network topology element has failed binding ' 'port:\n%(element)s'), {'element': element.to_json()}) LOG.error( _LE('Unable to bind port element for given host and valid VIF ' 'types:\n' '\thostname: %(host_name)s\n' '\tvalid VIF types: %(valid_vif_types)s'), { 'host_name': host_name, 'valid_vif_types': ', '.join(self.valid_vif_types) })
def sync_pending_row(self, exit_after_run=False): # Block until all pending rows are processed session = neutron_db_api.get_session() while not self.event.is_set(): self.event.wait() # Clear the event and go back to waiting after # the sync block exits self.event.clear() while True: LOG.debug("Thread walking database") row = db.get_oldest_pending_db_row_with_lock(session) if not row: LOG.debug("No rows to sync") break # Validate the operation validate_func = (dependency_validations. VALIDATION_MAP[row.object_type]) valid = validate_func(session, row) if not valid: LOG.info(_LI("%(operation)s %(type)s %(uuid)s is not a " "valid operation yet, skipping for now"), {'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid}) # Set row back to pending. db.update_db_row_state(session, row, odl_const.PENDING) if exit_after_run: break continue LOG.info(_LI("Syncing %(operation)s %(type)s %(uuid)s"), {'operation': row.operation, 'type': row.object_type, 'uuid': row.object_uuid}) # Add code to sync this to ODL method, urlpath, to_send = self._json_data(row) try: self.client.sendjson(method, urlpath, to_send) db.update_db_row_state(session, row, odl_const.COMPLETED) except exceptions.ConnectionError as e: # Don't raise the retry count, just log an error LOG.error(_LE("Cannot connect to the Opendaylight " "Controller")) # Set row back to pending db.update_db_row_state(session, row, odl_const.PENDING) # Break our of the loop and retry with the next # timer interval break except Exception as e: LOG.error(_LE("Error syncing %(type)s %(operation)s," " id %(uuid)s Error: %(error)s"), {'type': row.object_type, 'uuid': row.object_uuid, 'operation': row.operation, 'error': e.message}) db.update_pending_db_row_retry(session, row, self._row_retry_count) LOG.debug("Clearing sync thread event") if exit_after_run: # Permanently waiting thread model breaks unit tests # Adding this arg to exit here only for unit tests break