def _assert_bond_config_is_legal(iface_state, original_iface_state): """ When MAC address defined in original_iface_state and bond is MAC address restricted mode(cannot define MAC), raise NmstateValueError. When both miimon > 0 and arp_interval > 0 defined in merged state, raise NmstateValueError """ mac = original_iface_state.get(Interface.MAC) bond_options = iface_state[Bond.CONFIG_SUBTREE].get( Bond.OPTIONS_SUBTREE, {} ) bond_options[Bond.MODE] = iface_state[Bond.CONFIG_SUBTREE].get(Bond.MODE) if mac and is_in_mac_restricted_mode(bond_options): raise NmstateValueError( "MAC address cannot be specified in bond interface along with " "specified bond options" ) if "miimon" in bond_options and "arp_interval" in bond_options: try: miimon = int(bond_options["miimon"]) arp_interval = int(bond_options["arp_interval"]) except ValueError as e: raise NmstateValueError(f"Invalid bond option: {e}") if miimon > 0 and arp_interval > 0: raise NmstateValueError( "Bond option arp_interval is conflicting with miimon, " "please disable one of them by setting to 0" )
def validate_link_aggregation_state(desired_state, current_state): available_ifaces = { ifname for ifname, ifstate in desired_state.interfaces.items() if ifstate.get("state") != "absent" } available_ifaces |= set(current_state.interfaces) specified_slaves = set() for iface_state in desired_state.interfaces.values(): if iface_state.get("state") != "absent": link_aggregation = iface_state.get("link-aggregation") if link_aggregation: slaves = set(link_aggregation.get("slaves", [])) if not (slaves <= available_ifaces): raise NmstateValueError( "Link aggregation has missing slave: {}".format( iface_state ) ) if slaves & specified_slaves: raise NmstateValueError( "Link aggregation has reused slave: {}".format( iface_state ) ) specified_slaves |= slaves
def _validate_routes( iface_route_sets, iface_enable_states, ipv4_enable_states, ipv6_enable_states, ): """ Check whether user desire routes next hop to: * down/absent interface * Non-exit interface * IPv4/IPv6 disabled """ for iface_name, route_set in iface_route_sets.items(): if not route_set: continue iface_enable_state = iface_enable_states.get(iface_name) if iface_enable_state is None: raise NmstateValueError("Cannot set route to non-exist interface") if iface_enable_state != InterfaceState.UP: raise NmstateValueError( "Cannot set route to {} interface".format(iface_enable_state) ) # Interface is already check, so the ip enable status should be defined ipv4_enabled = ipv4_enable_states[iface_name] ipv6_enabled = ipv6_enable_states[iface_name] for route_obj in route_set: if iplib.is_ipv6_address(route_obj.destination): if not ipv6_enabled: raise NmstateValueError( "Cannot set IPv6 route when IPv6 is disabled" ) elif not ipv4_enabled: raise NmstateValueError( "Cannot set IPv4 route when IPv4 is disabled" )
def validate_link_aggregation_state(desired_state, current_state): available_ifaces = { ifname for ifname, ifstate in six.viewitems(desired_state.interfaces) if ifstate.get('state') != 'absent' } available_ifaces |= set(current_state.interfaces) specified_slaves = set() for iface_state in six.viewvalues(desired_state.interfaces): if iface_state.get('state') != 'absent': link_aggregation = iface_state.get('link-aggregation') if link_aggregation: slaves = set(link_aggregation.get('slaves', [])) if not (slaves <= available_ifaces): raise NmstateValueError( "Link aggregation has missing slave: {}".format( iface_state ) ) if slaves & specified_slaves: raise NmstateValueError( "Link aggregation has reused slave: {}".format( iface_state ) ) specified_slaves |= slaves
def _validate_miimon_conflict_with_arp_interval(self): bond_options = self._bond_options if bond_options.get("miimon") and bond_options.get("arp_interval"): raise NmstateValueError( "Bond option arp_interval is conflicting with miimon, " "please disable one of them by setting to 0" )
def ip_address_full_to_tuple(addr): try: net = ipaddress.ip_network(addr) except (ipaddress.AddressValueError, ipaddress.NetmaskValueError) as err: raise NmstateValueError(f"Invalid IP address, error: {err}") return f"{net.network_address}", net.prefixlen
def _validate_mandatory_properties(self): if self.is_up: for prop in (VXLAN.ID, VXLAN.BASE_IFACE, VXLAN.REMOTE): if prop not in self._vxlan_config: raise NmstateValueError( f"Vxlan tunnel {self.name} has missing mandatory " f"property: {prop}")
def _rule_info_to_nm_rule(rule, family): nm_rule = nmclient.NM.IPRoutingRule.new(family) ip_from = rule.get(RouteRule.IP_FROM) ip_to = rule.get(RouteRule.IP_TO) if not ip_from and not ip_to: raise NmstateValueError( f"Neither {RouteRule.IP_FROM} or {RouteRule.IP_TO} is defined" ) if ip_from: nm_rule.set_from(*iplib.ip_address_full_to_tuple(ip_from)) if ip_to: nm_rule.set_to(*iplib.ip_address_full_to_tuple(ip_to)) priority = rule.get(RouteRule.PRIORITY) if priority and priority != RouteRule.USE_DEFAULT_PRIORITY: nm_rule.set_priority(priority) else: nm_rule.set_priority(ROUTE_RULE_DEFAULT_PRIORIRY) table = rule.get(RouteRule.ROUTE_TABLE) if table and table != RouteRule.USE_DEFAULT_ROUTE_TABLE: nm_rule.set_table(table) else: nm_rule.set_table(iplib.KERNEL_MAIN_ROUTE_TABLE_ID) return nm_rule
def _validate_linux_bond(original_desired_state, current_state): """ Raise NmstateValueError on these scenarios: * Bond mode not defined. * Original desire state contains illegal bond config. * After merge, user's intention will cause illegal bong config. For example: mac specified in desired_state without any bond options defined. While current state is fail_over_mac=1 with active-backup mode. """ merged_desired_state = copy.deepcopy(original_desired_state) merged_desired_state.merge_interfaces(current_state) original_iface_states = {} for iface_name, iface_state in original_desired_state.interfaces.items(): original_iface_states[iface_name] = iface_state for iface_state in merged_desired_state.interfaces.values(): if iface_state[Interface.STATE] != InterfaceState.UP: continue if iface_state[Interface.TYPE] == InterfaceType.BOND: if not iface_state.get(Bond.CONFIG_SUBTREE, {}).get(Bond.MODE): raise NmstateValueError("Bond mode is not defined") original_iface_state = original_iface_states.get( iface_state[Interface.NAME], {} ) _assert_bond_config_is_legal(iface_state, original_iface_state)
def _save_dns_metadata(desired_state, current_state, ipv4_iface, ipv6_iface, servers, searches): index = 0 searches_saved = False for server in servers: iface_name = None if iplib.is_ipv6_address(server): iface_name = ipv6_iface family = Interface.IPV6 else: iface_name = ipv4_iface family = Interface.IPV4 if not iface_name: raise NmstateValueError( "Failed to find suitable interface for saving DNS " "name servers: %s" % server) _include_name_only_iface_state(desired_state, current_state, [iface_name]) iface_state = desired_state.interfaces[iface_name] if family not in iface_state: iface_state[family] = {} if DNS_METADATA not in iface_state[family]: iface_state[family][DNS_METADATA] = { DNS.SERVER: [server], DNS.SEARCH: [] if searches_saved else searches, DNS_METADATA_PRIORITY: nm.dns.DNS_PRIORITY_STATIC_BASE + index, } else: iface_state[family][DNS_METADATA][DNS.SERVER].append(server) searches_saved = True index += 1
def _assert_vlan_filtering_trunk_tags(ports_state): for port_state in ports_state: port_vlan_state = port_state.get(LB.Port.VLAN_SUBTREE, {}) vlan_mode = port_vlan_state.get(LB.Port.Vlan.MODE) trunk_tags = port_vlan_state.get(LB.Port.Vlan.TRUNK_TAGS, []) if vlan_mode == LB.Port.Vlan.Mode.ACCESS: if trunk_tags: raise NmstateValueError("Access port cannot have trunk tags") elif port_vlan_state: if not trunk_tags: raise NmstateValueError( "A trunk port needs to specify trunk tags" ) for trunk_tag in trunk_tags: _assert_vlan_filtering_trunk_tag(trunk_tag)
def _assert_vlan_filtering_trunk_tag(trunk_tag_state): vlan_id = trunk_tag_state.get(LinuxBridge.Port.Vlan.TrunkTags.ID) vlan_id_range = trunk_tag_state.get( LinuxBridge.Port.Vlan.TrunkTags.ID_RANGE) if vlan_id and vlan_id_range: raise NmstateValueError( "Trunk port cannot be configured by both id and range: {}".format( trunk_tag_state)) elif vlan_id_range: if not ({ LinuxBridge.Port.Vlan.TrunkTags.MIN_RANGE, LinuxBridge.Port.Vlan.TrunkTags.MAX_RANGE, } <= set(vlan_id_range)): raise NmstateValueError( "Trunk port range requires min / max keys: {}".format( vlan_id_range))
def _validate_ovs_patch_peers(self): """ When OVS patch peer does not exist or is down, raise an error. """ for iface in self._ifaces.values(): if iface.type == InterfaceType.OVS_INTERFACE and iface.is_up: if iface.peer: peer_iface = self._ifaces.get(iface.peer) if not peer_iface or not peer_iface.is_up: raise NmstateValueError( f"OVS patch port peer {iface.peer} must exist and " "be up") elif (not peer_iface.type == InterfaceType.OVS_INTERFACE or not peer_iface.is_patch_port): raise NmstateValueError( f"OVS patch port peer {iface.peer} must be an OVS" " patch port")
def _iface_for_route_table(self, route_state, route_table): for routes in route_state.config_iface_routes.values(): for route in routes: if route.table_id == route_table: return route.next_hop_interface raise NmstateValueError( "Failed to find interface to with route table ID " f"{route_table} to store route rules")
def _validate_ovs_lag_slave_count(self): for port in self.port_configs: slaves_subtree = OVSBridge.Port.LinkAggregation.SLAVES_SUBTREE lag = port.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE) if lag and len(lag.get(slaves_subtree, ())) < 2: raise NmstateValueError( f"OVS {self.name} LAG port {lag} has less than 2 slaves." )
def __init__(self, des_iface_infos, cur_iface_infos, save_to_disk=True): self._save_to_disk = save_to_disk self._des_iface_infos = des_iface_infos self._cur_ifaces = {} self._ifaces = {} if cur_iface_infos: for iface_info in cur_iface_infos: cur_iface = _to_specific_iface_obj(iface_info, save_to_disk) self._ifaces[cur_iface.name] = cur_iface self._cur_ifaces[cur_iface.name] = cur_iface if des_iface_infos: for iface_info in des_iface_infos: iface = BaseIface(iface_info, save_to_disk) cur_iface = self._ifaces.get(iface.name) if cur_iface and cur_iface.is_desired: raise NmstateValueError( f"Duplicate interfaces names detected: {iface.name}") if iface_info.get(Interface.TYPE) is None: if cur_iface: iface_info[Interface.TYPE] = cur_iface.type elif iface.is_up: raise NmstateValueError( f"Interface {iface.name} has no type defined " "neither in desire state nor current state") iface = _to_specific_iface_obj(iface_info, save_to_disk) if (iface.type == InterfaceType.UNKNOWN # Allowing deletion of down profiles and not iface.is_absent): # Ignore interface with unknown type continue if cur_iface: iface.merge(cur_iface) iface.mark_as_desired() self._ifaces[iface.name] = iface self._create_virtual_slaves() self._validate_unknown_slaves() self._validate_unknown_parent() self._gen_metadata() for iface in self._ifaces.values(): iface.pre_edit_validation_and_cleanup() self._pre_edit_validation_and_cleanup()
def create_setting(options): bond_setting = nmclient.NM.SettingBond.new() for option_name, option_value in six.viewitems(options): success = bond_setting.add_option(option_name, option_value) if not success: raise NmstateValueError('Invalid bond option: \{}\=\'{}\''.format( option_name, option_value)) return bond_setting
def _validate_unknown_parent(self): """ Check the existance of parent interface """ for iface in self._ifaces.values(): if iface.parent and not self._ifaces.get(iface.parent): raise NmstateValueError( f"Interface {iface.name} has unknown parent: " f"{iface.parent}")
def _validate_slave_ip(self): for family in (Interface.IPV4, Interface.IPV6): ip_state = IPState(family, self._origin_info.get(family, {})) if (ip_state.is_enabled and self.master and not self.can_have_ip_when_enslaved): raise NmstateValueError( f"Interface {self.name} is enslaved by {self.master_type} " f"interface {self.master} which does not allow " f"slaves to have {family} enabled")
def create_setting(options): bond_setting = nmclient.NM.SettingBond.new() for option_name, option_value in options.items(): success = bond_setting.add_option(option_name, option_value) if not success: raise NmstateValueError("Invalid bond option: '{}'='{}'".format( option_name, option_value)) return bond_setting
def _fix_mac_restriced_mode(self): if self.is_in_mac_restricted_mode: if self.original_dict.get(Interface.MAC): raise NmstateValueError( "MAC address cannot be specified in bond interface along " "with fail_over_mac active on active backup mode" ) else: self.raw.pop(Interface.MAC, None)
def _generate_route_rule_per_stack_metadata(family, indexed_rules, routes, desired_state): for route_table, rules in indexed_rules.items(): iface_name = _find_iface_for_route_table(routes, route_table) if not iface_name: raise NmstateValueError( "Failed to find suitable interface for saving route rule: " "{}".format(rules[0])) iface_state = desired_state.interfaces[iface_name] _attach_route_rule_metadata(iface_state, rules, family)
def _validate_unknown_slaves(self): """ Check the existance of slave interface """ for iface in self._ifaces.values(): for slave_name in iface.slaves: if not self._ifaces.get(slave_name): raise NmstateValueError( f"Interface {iface.name} has unknown slave: " f"{slave_name}")
def _validate_vlan_filtering_enable_native(self): for port_config in self.original_dict.get(LinuxBridge.CONFIG_SUBTREE, {}).get( LinuxBridge.PORT_SUBTREE, []): vlan_config = _get_port_vlan_config(port_config) if _vlan_is_access_mode(vlan_config) and _vlan_is_enable_native( vlan_config): raise NmstateValueError( "enable-native cannot be set in access mode")
def _choose_checkpoint(dbuspath): if not dbuspath: candidates = nm.checkpoint.get_checkpoints() if candidates: dbuspath = candidates[0] if not dbuspath: raise NmstateValueError("No checkpoint specified or found") checkpoint = nm.checkpoint.CheckPoint(dbuspath=dbuspath) return checkpoint
def _activation_progress_check(self, action): if self._ctx.is_cancelled(): self._activation_clean_up() return devname = self._nm_dev.get_iface() cur_nm_dev = self._ctx.get_nm_dev(devname) if cur_nm_dev and cur_nm_dev != self._nm_dev: logging.debug(f"The NM.Device of profile {devname} changed") self._remove_dev_handlers() self._nm_dev = cur_nm_dev self.wait_dev_activation(action) cur_nm_ac = get_device_active_connection(self.nmdevice) if cur_nm_ac and cur_nm_ac != self._nm_ac: logging.debug( "Active connection of device {} has been replaced".format( self.devname ) ) self._remove_ac_handlers() self._nm_ac = cur_nm_ac self._wait_ac_activation(action) if is_activated(self._nm_ac, self._nm_dev): logging.debug( "Connection activation succeeded: dev=%s, con-state=%s, " "dev-state=%s, state-flags=%s", devname, self._nm_ac.get_state(), self._nm_dev.get_state(), self._nm_ac.get_state_flags(), ) self._activation_clean_up() self._ctx.finish_async(action) elif ( not self._is_activating() and self._is_sriov_parameter_not_supported_by_driver() ): reason = ( f"The device={self.devname} does not support one or " "more of the SR-IOV parameters set." ) self._activation_clean_up() self._ctx.fail( NmstateValueError(f"{action} failed: reason={reason}") ) elif not self._is_activating(): reason = f"{self._nm_ac.get_state_reason()}" if self.nmdevice: reason += f" {self.nmdevice.get_state_reason()}" self._activation_clean_up() self._ctx.fail( NmstateLibnmError(f"{action} failed: reason={reason}") )
def _parse_checkpoints(checkpoints): """ Return a dict mapping plugin name to checkpoint """ if not checkpoints: return None parsed = checkpoints.split("|") if len(parsed) % 2: raise NmstateValueError("Invalid format of checkpoint") checkpoint_index = {} for plugin_name, checkpoint in zip(parsed[0::2], parsed[1::2]): checkpoint_index[plugin_name] = checkpoint
def _validate_vlan_filtering_trunk_tags(self): for port_config in self.original_dict.get(LinuxBridge.CONFIG_SUBTREE, {}).get( LinuxBridge.PORT_SUBTREE, []): port_vlan_state = port_config.get(LinuxBridge.Port.VLAN_SUBTREE, {}) vlan_mode = port_vlan_state.get(LinuxBridge.Port.Vlan.MODE) trunk_tags = port_vlan_state.get(LinuxBridge.Port.Vlan.TRUNK_TAGS, []) if vlan_mode == LinuxBridge.Port.Vlan.Mode.ACCESS: if trunk_tags: raise NmstateValueError( "Access port cannot have trunk tags") elif port_vlan_state: if not trunk_tags: raise NmstateValueError( "A trunk port needs to specify trunk tags") for trunk_tag in trunk_tags: _assert_vlan_filtering_trunk_tag(trunk_tag)
def _load_checkpoint(self, checkpoint_path): if checkpoint_path: if self._checkpoint: # Old checkpoint might timeout, hence it's legal to load # another one. self._checkpoint.clean_up() candidates = get_checkpoints(self._ctx.client) if checkpoint_path in candidates: self._checkpoint = CheckPoint(nm_context=self._ctx, dbuspath=checkpoint_path) else: raise NmstateValueError("No checkpoint specified or found") else: if not self._checkpoint: # Get latest one candidates = get_checkpoints(self._ctx.client) if candidates: self._checkpoint = CheckPoint(nm_context=self._ctx, dbuspath=candidates[0]) else: raise NmstateValueError("No checkpoint specified or found")
def _assert_vxlan_has_missing_attribute(state, *attributes): vxlan_config = state.get(VXLAN.CONFIG_SUBTREE, {}) if not vxlan_config: return attributes_set = set(attributes) vxlan_config_set = set(vxlan_config) if not (attributes_set <= vxlan_config_set): raise NmstateValueError( "Vxlan tunnel {} has missing {}: {}".format( state[schema.Interface.NAME], attributes_set.difference(vxlan_config_set), state, ) )