예제 #1
0
 def test_eq_other_is_none(self):
     dev1 = ip_lib.IPDevice('tap0', namespace='ns1')
     self.assertIsNotNone(dev1)
예제 #2
0
 def get_device_mtu(self, target_device, name_getter, namespace):
     device = ip_lib.IPDevice(name_getter(target_device), namespace)
     return device.link.mtu
예제 #3
0
 def fail_gw_router_port(router):
     r_br = ip_lib.IPDevice(router.driver.conf.external_network_bridge)
     r_br.link.set_down()
예제 #4
0
 def _get_device_plugin_tag(self, device_name, namespace=None):
     device = ip_lib.IPDevice(device_name, self.conf.root_helper, namespace)
     return device.link.alias
예제 #5
0
    def setUp(self):
        super(TunnelTest, self).setUp()
        cfg.CONF.set_override('rpc_backend',
                              'neutron.openstack.common.rpc.impl_fake')
        cfg.CONF.set_override('report_interval', 0, 'AGENT')
        self.mox = mox.Mox()
        self.addCleanup(self.mox.UnsetStubs)

        self.INT_BRIDGE = 'integration_bridge'
        self.TUN_BRIDGE = 'tunnel_bridge'
        self.MAP_TUN_BRIDGE = 'tunnel_bridge_mapping'
        self.NET_MAPPING = {'net1': self.MAP_TUN_BRIDGE}
        self.INT_OFPORT = 11111
        self.TUN_OFPORT = 22222
        self.MAP_TUN_OFPORT = 33333
        self.VETH_MTU = None
        self.inta = self.mox.CreateMock(ip_lib.IPDevice)
        self.intb = self.mox.CreateMock(ip_lib.IPDevice)
        self.inta.link = self.mox.CreateMock(ip_lib.IpLinkCommand)
        self.intb.link = self.mox.CreateMock(ip_lib.IpLinkCommand)

        self.mox.StubOutClassWithMocks(ovs_lib, 'OVSBridge')
        self.mock_int_bridge = ovs_lib.OVSBridge(self.INT_BRIDGE, 'sudo')
        self.mock_int_bridge.delete_port('patch-tun')
        self.mock_int_bridge.remove_all_flows()
        self.mock_int_bridge.add_flow(priority=1, actions='normal')

        self.mock_map_tun_bridge = ovs_lib.OVSBridge(self.MAP_TUN_BRIDGE,
                                                     'sudo')
        self.mock_map_tun_bridge.br_name = self.MAP_TUN_BRIDGE
        self.mock_map_tun_bridge.remove_all_flows()
        self.mock_map_tun_bridge.add_flow(priority=1, actions='normal')
        self.mock_int_bridge.delete_port('int-tunnel_bridge_mapping')
        self.mock_map_tun_bridge.delete_port('phy-tunnel_bridge_mapping')
        self.mock_int_bridge.add_port(self.inta)
        self.mock_map_tun_bridge.add_port(self.intb)
        self.inta.link.set_up()
        self.intb.link.set_up()

        self.mock_int_bridge.add_flow(priority=2, in_port=None, actions='drop')
        self.mock_map_tun_bridge.add_flow(priority=2,
                                          in_port=None,
                                          actions='drop')

        self.mock_tun_bridge = ovs_lib.OVSBridge(self.TUN_BRIDGE, 'sudo')
        self.mock_tun_bridge.reset_bridge()
        self.mock_int_bridge.add_patch_port(
            'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT)
        self.mock_tun_bridge.add_patch_port(
            'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT)
        self.mock_tun_bridge.remove_all_flows()
        self.mock_tun_bridge.add_flow(priority=1, actions='drop')

        self.mox.StubOutWithMock(ip_lib, 'device_exists')
        ip_lib.device_exists('tunnel_bridge_mapping', 'sudo').AndReturn(True)
        ip_lib.device_exists('int-tunnel_bridge_mapping',
                             'sudo').AndReturn(True)

        self.mox.StubOutWithMock(ip_lib.IpLinkCommand, 'delete')
        ip_lib.IPDevice('int-tunnel_bridge_mapping').link.delete()

        self.mox.StubOutClassWithMocks(ip_lib, 'IPWrapper')
        ip_lib.IPWrapper('sudo').add_veth(
            'int-tunnel_bridge_mapping',
            'phy-tunnel_bridge_mapping').AndReturn([self.inta, self.intb])

        self.mock_int_bridge.get_local_port_mac().AndReturn('000000000001')
        self.mox.StubOutWithMock(ovs_lib, 'get_bridges')
        ovs_lib.get_bridges('sudo').AndReturn(
            [self.INT_BRIDGE, self.TUN_BRIDGE, self.MAP_TUN_BRIDGE])
예제 #6
0
 def _set_tap_mtu(self, tap_device_name, mtu):
     ip_lib.IPDevice(tap_device_name).link.set_mtu(mtu)
예제 #7
0
    def _dvr_router_lifecycle(self,
                              enable_ha=False,
                              enable_snat=False,
                              custom_mtu=2000,
                              use_port_mtu=False,
                              ip_version=4,
                              dual_stack=False):
        '''Test dvr router lifecycle

        :param enable_ha: sets the ha value for the router.
        :param enable_snat:  the value of enable_snat is used
        to  set the  agent_mode.
        '''

        # The value of agent_mode can be dvr, dvr_snat, or legacy.
        # Since by definition this is a dvr (distributed = true)
        # only dvr and dvr_snat are applicable
        self.agent.conf.agent_mode = 'dvr_snat' if enable_snat else 'dvr'

        # We get the router info particular to a dvr router
        router_info = self.generate_dvr_router_info(enable_ha,
                                                    enable_snat,
                                                    extra_routes=True)
        if use_port_mtu:
            for key in ('_interfaces', '_snat_router_interfaces',
                        '_floatingip_agent_interfaces'):
                for port in router_info[key]:
                    port['mtu'] = custom_mtu
            router_info['gw_port']['mtu'] = custom_mtu
            router_info['_ha_interface']['mtu'] = custom_mtu
        else:
            self.agent.conf.network_device_mtu = custom_mtu

        # We need to mock the get_agent_gateway_port return value
        # because the whole L3PluginApi is mocked and we need the port
        # gateway_port information before the l3_agent will create it.
        # The port returned needs to have the same information as
        # router_info['gw_port']
        self.mock_plugin_api.get_agent_gateway_port.return_value = router_info[
            'gw_port']

        # We also need to mock the get_external_network_id method to
        # get the correct fip namespace.
        self.mock_plugin_api.get_external_network_id.return_value = (
            router_info['_floatingips'][0]['floating_network_id'])

        # With all that set we can now ask the l3_agent to
        # manage the router (create it, create namespaces,
        # attach interfaces, etc...)
        router = self.manage_router(self.agent, router_info)
        if enable_ha:
            port = router.get_ex_gw_port()
            interface_name = router.get_external_device_name(port['id'])
            self._assert_no_ip_addresses_on_interface(router.ha_namespace,
                                                      interface_name)
            utils.wait_until_true(lambda: router.ha_state == 'master')

            # Keepalived notifies of a state transition when it starts,
            # not when it ends. Thus, we have to wait until keepalived finishes
            # configuring everything. We verify this by waiting until the last
            # device has an IP address.
            device = router.router[l3_constants.INTERFACE_KEY][-1]
            device_exists = functools.partial(
                self.device_exists_with_ips_and_mac, device,
                router.get_internal_device_name, router.ns_name)
            utils.wait_until_true(device_exists)
            name = router.get_internal_device_name(device['id'])
            self.assertEqual(custom_mtu,
                             ip_lib.IPDevice(name, router.ns_name).link.mtu)

        ext_gateway_port = router_info['gw_port']
        self.assertTrue(self._namespace_exists(router.ns_name))
        utils.wait_until_true(
            lambda: self._metadata_proxy_exists(self.agent.conf, router))
        self._assert_internal_devices(router)
        self._assert_dvr_external_device(router)
        self._assert_dvr_gateway(router)
        self._assert_dvr_floating_ips(router)
        self._assert_snat_chains(router)
        self._assert_floating_ip_chains(router)
        self._assert_metadata_chains(router)
        self._assert_rfp_fpr_mtu(router, custom_mtu)
        if enable_snat:
            ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4]
            snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
                router.router_id)
            self._assert_onlink_subnet_routes(router, ip_versions,
                                              snat_ns_name)
            self._assert_extra_routes(router, namespace=snat_ns_name)

        # During normal operation, a router-gateway-clear followed by
        # a router delete results in two notifications to the agent.  This
        # code flow simulates the exceptional case where the notification of
        # the clearing of the gateway hast been missed, so we are checking
        # that the L3 agent is robust enough to handle that case and delete
        # the router correctly.
        self._delete_router(self.agent, router.router_id)
        self._assert_fip_namespace_deleted(ext_gateway_port)
        self._assert_router_does_not_exist(router)
        self._assert_snat_namespace_does_not_exist(router)
 def remove_fdb_ip_entry(self, mac, ip, interface):
     ip_lib.IPDevice(interface).neigh.delete(ip, mac)
예제 #9
0
 def assert_dhcp_device(self, namespace, dhcp_iface_name, dhcp_enabled):
     dev = ip_lib.IPDevice(dhcp_iface_name, namespace)
     self.assertEqual(dhcp_enabled,
                      ip_lib.device_exists(dhcp_iface_name, namespace))
     if dhcp_enabled:
         self.assertEqual(self._DHCP_PORT_MAC_ADDRESS, dev.link.address)
 def ensure_tap_mtu(self, tap_dev_name, phy_dev_name):
     """Ensure the MTU on the tap is the same as the physical device."""
     phy_dev_mtu = ip_lib.IPDevice(phy_dev_name).link.mtu
     ip_lib.IPDevice(tap_dev_name).link.set_mtu(phy_dev_mtu)
 def add_fdb_ip_entry(self, mac, ip, interface):
     ip_lib.IPDevice(interface).neigh.add(ip, mac)
예제 #12
0
 def _get_device_plugin_tag(self, device_name, namespace=None):
     device = ip_lib.IPDevice(device_name, namespace=namespace)
     return device.link.alias
예제 #13
0
 def _ip_list_for_vif(self, vif_name, namespace):
     ip_device = ip_lib.IPDevice(vif_name, namespace)
     return ip_device.addr.list(ip_version=lib_const.IP_VERSION_4)
예제 #14
0
 def test_str(self):
     self.assertEqual(str(ip_lib.IPDevice('tap0')), 'tap0')
예제 #15
0
 def _get_addresses_on_device(cls, namespace, interface):
     return [
         address['cidr'] for address in ip_lib.IPDevice(
             interface, namespace=namespace).addr.list()
     ]
예제 #16
0
 def _ip_list_for_vif(self, vif_name, namespace):
     ip_device = ip_lib.IPDevice(vif_name, namespace)
     return ip_device.addr.list(ip_version=4)
예제 #17
0
 def gateway_redirect_cleanup(self, rtr_interface):
     ns_ipd = ip_lib.IPDevice(rtr_interface, namespace=self.ns_name)
     self._stale_ip_rule_cleanup(self.ns_name, ns_ipd,
                                 lib_constants.IP_VERSION_4)
     self._stale_ip_rule_cleanup(self.ns_name, ns_ipd,
                                 lib_constants.IP_VERSION_6)
예제 #18
0
    def _process_create(self, port_forwardings, ri, interface_name, namespace,
                        iptables_manager):
        if not port_forwardings:
            return
        device = ip_lib.IPDevice(interface_name, namespace=namespace)

        is_distributed = ri.router.get('distributed')
        ha_port = ri.router.get(constants.HA_INTERFACE_KEY, None)
        fip_statuses = {}
        for port_forwarding in port_forwardings:
            # check if the port forwarding is managed in this agent from
            # OVO and router rpc.
            if port_forwarding.id in self.mapping.managed_port_forwardings:
                LOG.debug(
                    "Skip port forwarding %s for create, as it had been "
                    "managed by agent", port_forwarding.id)
                continue
            existing_cidrs = ri.get_router_cidrs(device)
            fip_ip = str(port_forwarding.floating_ip_address)
            fip_cidr = str(netaddr.IPNetwork(fip_ip))
            status = ''
            if fip_cidr not in existing_cidrs:
                try:
                    if not is_distributed:
                        fip_statuses[port_forwarding.floatingip_id] = (
                            ri.add_floating_ip({'floating_ip_address': fip_ip},
                                               interface_name, device))
                    else:
                        if not ha_port:
                            device.addr.add(fip_cidr)
                            ip_lib.send_ip_addr_adv_notif(
                                namespace, interface_name, fip_ip)
                        else:
                            ri._add_vip(fip_cidr, interface_name)
                        status = constants.FLOATINGIP_STATUS_ACTIVE
                except Exception:
                    # Any error will causes the fip status to be set 'ERROR'
                    status = constants.FLOATINGIP_STATUS_ERROR
                    LOG.warning(
                        "Unable to configure floating IP %(fip_id)s "
                        "for port forwarding %(pf_id)s", {
                            'fip_id': port_forwarding.floatingip_id,
                            'pf_id': port_forwarding.id
                        })
            else:
                if not ha_port:
                    ip_lib.send_ip_addr_adv_notif(namespace, interface_name,
                                                  fip_ip)
            if status:
                fip_statuses[port_forwarding.floatingip_id] = status

        if ha_port and ha_port['status'] == constants.PORT_STATUS_ACTIVE:
            ri.enable_keepalived()

        for port_forwarding in port_forwardings:
            rule_tag = PORT_FORWARDING_PREFIX + port_forwarding.id
            self._rule_apply(iptables_manager, port_forwarding, rule_tag)

        iptables_manager.apply()
        self._sending_port_forwarding_fip_status(ri, fip_statuses)
        self._store_local(port_forwardings, events.CREATED)
예제 #19
0
 def test_get_datapath_id(self):
     brdev = ip_lib.IPDevice(self.br.br_name)
     dpid = brdev.link.attributes['link/ether'].replace(':', '')
     self.br.set_db_attribute('Bridge', self.br.br_name, 'datapath_id',
                              dpid)
     self.assertIn(dpid, self.br.get_datapath_id())
예제 #20
0
    def get_ipv6_llas(self, device_name, namespace):
        device = ip_lib.IPDevice(device_name, namespace=namespace)

        return device.addr.list(scope='link', ip_version=6)
예제 #21
0
 def _set_device_plugin_tag(self, network_id, device_name, namespace=None):
     plugin_tag = self._get_flavor_by_network_id(network_id)
     device = ip_lib.IPDevice(device_name, self.conf.root_helper, namespace)
     device.link.set_alias(plugin_tag)
예제 #22
0
 def add_ipv6_addr(self, device_name, v6addr, namespace, scope='global'):
     device = ip_lib.IPDevice(device_name,
                              namespace=namespace)
     net = netaddr.IPNetwork(v6addr)
     device.addr.add(str(net), scope)
예제 #23
0
    def test_provision_datapath(self):
        """Test datapath provisioning.

        Check that the VETH pair, OVS port and namespace associated to this
        namespace are created, that the interface is properly configured with
        the right IP addresses and that the metadata proxy is spawned.
        """

        metadata_port = makePort(mac=['aa:bb:cc:dd:ee:ff'],
                                 external_ids={
                                     'neutron:cidrs':
                                     '10.0.0.1/23 '
                                     '2001:470:9:1224:5595:dd51:6ba2:e788/64'
                                 },
                                 logical_port='port')

        with mock.patch.object(self.agent.sb_idl,
                               'get_metadata_port_network',
                               return_value=metadata_port),\
                mock.patch.object(
                    ip_lib, 'device_exists', return_value=False),\
                mock.patch.object(agent.MetadataAgent, '_get_veth_name',
                                  return_value=['veth_0', 'veth_1']),\
                mock.patch.object(agent.MetadataAgent, '_get_namespace_name',
                                  return_value='namespace'),\
                mock.patch.object(ip_link, 'set_up') as link_set_up,\
                mock.patch.object(ip_link, 'set_address') as link_set_addr,\
                mock.patch.object(ip_addr, 'list', return_value=[]),\
                mock.patch.object(ip_addr, 'add') as ip_addr_add,\
                mock.patch.object(
                    ip_wrap, 'add_veth',
                    return_value=[ip_lib.IPDevice('ip1'),
                                  ip_lib.IPDevice('ip2')]) as add_veth,\
                mock.patch.object(
                    self.agent,
                    'update_chassis_metadata_networks') as update_chassis,\
                mock.patch.object(
                    driver.MetadataDriver,
                    'spawn_monitored_metadata_proxy') as spawn_mdp:

            self.agent.provision_datapath('1')

            # Check that the VETH pair is created
            add_veth.assert_called_once_with('veth_0', 'veth_1', 'namespace')
            # Make sure that the two ends of the VETH pair have been set as up.
            self.assertEqual(2, link_set_up.call_count)
            link_set_addr.assert_called_once_with('aa:bb:cc:dd:ee:ff')
            # Make sure that the port has been added to OVS.
            self.agent.ovs_idl.add_port.assert_called_once_with(
                'br-int', 'veth_0')
            self.agent.ovs_idl.db_set.assert_called_once_with(
                'Interface', 'veth_0', ('external_ids', {
                    'iface-id': 'port'
                }))
            # Check that the metadata port has the IP addresses properly
            # configured and that IPv6 address has been skipped.
            expected_calls = [
                mock.call('10.0.0.1/23'),
                mock.call('169.254.169.254/16')
            ]
            self.assertEqual(sorted(expected_calls),
                             sorted(ip_addr_add.call_args_list))
            # Check that metadata proxy has been spawned
            spawn_mdp.assert_called_once()
            # Check that the chassis has been updated with the datapath.
            update_chassis.assert_called_once_with('1')
예제 #24
0
 def delete_ipv6_addr(self, device_name, v6addr, namespace):
     device = ip_lib.IPDevice(device_name,
                              namespace=namespace)
     device.delete_addr_and_conntrack_state(v6addr)
예제 #25
0
    def provision_datapath(self, datapath):
        """Provision the datapath so that it can serve metadata.

        This function will create the namespace and VETH pair if needed
        and assign the IP addresses to the interface corresponding to the
        metadata port of the network. It will also remove existing IP
        addresses that are no longer needed.

        :return: The metadata namespace name of this datapath
        """
        LOG.debug("Provisioning datapath %s", datapath)
        port = self.sb_idl.get_metadata_port_network(datapath)
        # If there's no metadata port or it doesn't have a MAC or IP
        # addresses, then tear the namespace down if needed. This might happen
        # when there are no subnets yet created so metadata port doesn't have
        # an IP address.
        if not (port and port.mac and port.external_ids.get(
                ovn_const.OVN_CIDRS_EXT_ID_KEY, None)):
            LOG.debug(
                "There is no metadata port for datapath %s or it has no "
                "MAC or IP addresses configured, tearing the namespace "
                "down if needed", datapath)
            self.teardown_datapath(datapath)
            return

        # First entry of the mac field must be the MAC address.
        match = MAC_PATTERN.match(port.mac[0].split(' ')[0])
        # If it is not, we can't provision the namespace. Tear it down if
        # needed and log the error.
        if not match:
            LOG.error(
                "Metadata port for datapath %s doesn't have a MAC "
                "address, tearing the namespace down if needed", datapath)
            self.teardown_datapath(datapath)
            return

        mac = match.group()
        ip_addresses = set(
            port.external_ids[ovn_const.OVN_CIDRS_EXT_ID_KEY].split(' '))
        ip_addresses.add(METADATA_DEFAULT_CIDR)
        metadata_port = MetadataPortInfo(mac, ip_addresses)

        # Create the VETH pair if it's not created. Also the add_veth function
        # will create the namespace for us.
        namespace = self._get_namespace_name(datapath)
        veth_name = self._get_veth_name(datapath)

        ip1 = ip_lib.IPDevice(veth_name[0])
        if ip_lib.device_exists(veth_name[1], namespace):
            ip2 = ip_lib.IPDevice(veth_name[1], namespace)
        else:
            LOG.debug("Creating VETH %s in %s namespace", veth_name[1],
                      namespace)
            # Might happen that the end in the root namespace exists even
            # though the other end doesn't. Make sure we delete it first if
            # that's the case.
            if ip1.exists():
                ip1.link.delete()
            ip1, ip2 = ip_lib.IPWrapper().add_veth(veth_name[0], veth_name[1],
                                                   namespace)

        # Make sure both ends of the VETH are up
        ip1.link.set_up()
        ip2.link.set_up()

        # Configure the MAC address.
        ip2.link.set_address(metadata_port.mac)
        dev_info = ip2.addr.list()

        # Configure the IP addresses on the VETH pair and remove those
        # that we no longer need.
        current_cidrs = {dev['cidr'] for dev in dev_info}
        for ipaddr in current_cidrs - metadata_port.ip_addresses:
            ip2.addr.delete(ipaddr)
        for ipaddr in metadata_port.ip_addresses - current_cidrs:
            # NOTE(dalvarez): metadata only works on IPv4. We're doing this
            # extra check here because it could be that the metadata port has
            # an IPv6 address if there's an IPv6 subnet with SLAAC in its
            # network. Neutron IPAM will autoallocate an IPv6 address for every
            # port in the network.
            if utils.get_ip_version(ipaddr) == 4:
                ip2.addr.add(ipaddr)

        # Configure the OVS port and add external_ids:iface-id so that it
        # can be tracked by OVN.
        self.ovs_idl.add_port(self.conf.ovs_integration_bridge,
                              veth_name[0]).execute()
        self.ovs_idl.db_set('Interface', veth_name[0],
                            ('external_ids', {
                                'iface-id': port.logical_port
                            })).execute()

        # Spawn metadata proxy if it's not already running.
        metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy(
            self._process_monitor,
            namespace,
            METADATA_PORT,
            self.conf,
            network_id=datapath)

        self.update_chassis_metadata_networks(datapath)
        return namespace
예제 #26
0
def _get_veth(name1, name2, namespace2):
    return (ip_lib.IPDevice(name1),
            ip_lib.IPDevice(name2, namespace=namespace2))
예제 #27
0
 def fail_ha_router(self, router):
     device_name = router.get_ha_device_name()
     ha_device = ip_lib.IPDevice(device_name, router.ha_namespace)
     ha_device.link.set_down()
예제 #28
0
    def init_l3(self, device_name, ip_cidrs, namespace=None,
                preserve_ips=None, clean_connections=False):
        """Set the L3 settings for the interface using data from the port.

        ip_cidrs: list of 'X.X.X.X/YY' strings
        preserve_ips: list of ip cidrs that should not be removed from device
        clean_connections: Boolean to indicate if we should cleanup connections
          associated to removed ips
        """
        preserve_ips = preserve_ips or []
        device = ip_lib.IPDevice(device_name, namespace=namespace)

        # The LLA generated by the operating system is not known to
        # Neutron, so it would be deleted if we added it to the 'previous'
        # list here
        default_ipv6_lla = ip_lib.get_ipv6_lladdr(device.link.address)

        cidrs = set()
        remove_ips = set()

        # normalize all the IP addresses first
        for ip_cidr in ip_cidrs:
            net = netaddr.IPNetwork(ip_cidr)
            # Convert to compact IPv6 address because the return values of
            # "ip addr list" are compact.
            if net.version == 6:
                ip_cidr = str(net)
            cidrs.add(ip_cidr)

        # Determine the addresses that must be added and removed
        for address in device.addr.list():
            cidr = address['cidr']
            dynamic = address['dynamic']

            # skip the IPv6 link-local
            if cidr == default_ipv6_lla:
                # it's already configured, leave it alone
                cidrs.discard(cidr)
                continue

            if cidr in preserve_ips:
                continue

            # Statically created addresses are OK, dynamically created
            # addresses must be removed and replaced
            if cidr in cidrs and not dynamic:
                cidrs.remove(cidr)
                continue

            remove_ips.add(cidr)

        # Clean up any old addresses.  This must be done first since there
        # could be a dynamic address being replaced with a static one.
        for ip_cidr in remove_ips:
            if clean_connections:
                device.delete_addr_and_conntrack_state(ip_cidr)
            else:
                device.addr.delete(ip_cidr)

        # add any new addresses
        for ip_cidr in cidrs:
            device.addr.add(ip_cidr)
예제 #29
0
 def restore_gw_router_port(router):
     r_br = ip_lib.IPDevice(router.driver.conf.external_network_bridge)
     r_br.link.set_up()
예제 #30
0
 def test_eq_diff_namespace(self):
     dev1 = ip_lib.IPDevice('tap0', namespace='ns1')
     dev2 = ip_lib.IPDevice('tap0', namespace='ns2')
     self.assertNotEqual(dev1, dev2)