Ejemplo n.º 1
0
    def test_invalid_subnet_data(self, etcd_client_cls):
        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        # Arrange to deliver invalid subnet data.
        def etcd_client_read(key, **kwargs):
            LOG.info('etcd_client_read %s %s', key, kwargs)
            if 'v4subnet-1' in key:
                return EtcdResponse(value=json.dumps({
                    'gateway_ip': '10.28.0.1'
                }))
            if 'v6subnet-1' in key:
                return EtcdResponse(value=json.dumps({
                    'gateway_ip': '2001:db8:1::1'
                }))

            eventlet.sleep(10)
            return None

        etcd_client.read.side_effect = etcd_client_read

        # Notify an endpoint.
        agent.etcd.on_endpoint_set(EtcdResponse(value=json.dumps({
            'state': 'active',
            'name': 'tap1234',
            'mac': 'fe:16:65:12:33:44',
            'profile_ids': ['profile-1'],
            'ipv4_nets': ['10.28.0.2/32'],
            'ipv4_subnet_ids': ['v4subnet-1'],
            'ipv4_gateway': '10.28.0.1',
            'ipv6_nets': ['2001:db8:1::2/128'],
            'ipv6_subnet_ids': ['v6subnet-1'],
            'ipv6_gateway': '2001:db8:1::1'
        })),
            'hostname-ignored',
            'openstack',
            'workload-id-ignored',
            'endpoint-1'
        )

        # Check that either the v4 or the v6 subnet was read from etcd.  Since
        # it's invalid, the processing of the new endpoint stops at that point,
        # and the other subnet is not read at all.
        read_calls = etcd_client.read.mock_calls
        self.assertEqual(1, len(read_calls))
        self.assertTrue(read_calls[0] in [
            mock.call(datamodel_v1.key_for_subnet('v4subnet-1'),
                      consistent=True),
            mock.call(datamodel_v1.key_for_subnet('v6subnet-1'),
                      consistent=True),
        ])
        etcd_client.read.reset_mock()
Ejemplo n.º 2
0
    def test_invalid_subnet_data(self, etcd_client_cls):
        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        # Arrange to deliver invalid subnet data.
        def etcd_client_read(key, **kwargs):
            LOG.info('etcd_client_read %s %s', key, kwargs)
            if 'v4subnet-1' in key:
                return EtcdResponse(
                    value=json.dumps({'gateway_ip': '10.28.0.1'}))
            if 'v6subnet-1' in key:
                return EtcdResponse(
                    value=json.dumps({'gateway_ip': '2001:db8:1::1'}))

            eventlet.sleep(10)
            return None

        etcd_client.read.side_effect = etcd_client_read

        # Notify an endpoint.
        agent.etcd.on_endpoint_set(
            EtcdResponse(value=json.dumps({
                'state': 'active',
                'name': 'tap1234',
                'mac': 'fe:16:65:12:33:44',
                'profile_ids': ['profile-1'],
                'ipv4_nets': ['10.28.0.2/32'],
                'ipv4_subnet_ids': ['v4subnet-1'],
                'ipv4_gateway': '10.28.0.1',
                'ipv6_nets': ['2001:db8:1::2/128'],
                'ipv6_subnet_ids': ['v6subnet-1'],
                'ipv6_gateway': '2001:db8:1::1'
            })), 'hostname-ignored', 'openstack', 'workload-id-ignored',
            'endpoint-1')

        # Check that either the v4 or the v6 subnet was read from etcd.  Since
        # it's invalid, the processing of the new endpoint stops at that point,
        # and the other subnet is not read at all.
        read_calls = etcd_client.read.mock_calls
        self.assertEqual(1, len(read_calls))
        self.assertTrue(read_calls[0] in [
            mock.call(datamodel_v1.key_for_subnet('v4subnet-1'),
                      consistent=True),
            mock.call(datamodel_v1.key_for_subnet('v6subnet-1'),
                      consistent=True),
        ])
        etcd_client.read.reset_mock()
Ejemplo n.º 3
0
    def _get_subnet(self, subnet_id):
        # Read data for subnet SUBNET_ID from etcd and translate it to the dict
        # form expected for insertion into a Neutron NetModel.
        LOG.debug("Read subnet %s from etcd", subnet_id)
        self.dirty_subnets.discard(subnet_id)

        subnet_key = datamodel_v1.key_for_subnet(subnet_id)
        response = self.client.read(subnet_key, consistent=True)
        data = etcdutils.safe_decode_json(response.value, 'subnet')
        LOG.debug("Subnet data: %s", data)

        if not (isinstance(data, dict) and
                'cidr' in data and
                'gateway_ip' in data):
            # Subnet data was invalid.
            LOG.warning("Invalid subnet data: %s => %s", response.value, data)
            raise ValidationFailed("Invalid subnet data")

        # Convert to form expected by NetModel.
        ip_version = 6 if ':' in data['cidr'] else 4
        subnet = {'enable_dhcp': True,
                  'ip_version': ip_version,
                  'cidr': data['cidr'],
                  'dns_nameservers': data.get('dns_servers') or [],
                  'id': subnet_id,
                  'gateway_ip': data['gateway_ip'],
                  'host_routes': data.get('host_routes', []),
                  'network_id': data.get('network_id', NETWORK_ID)}
        if ip_version == 6:
            subnet['ipv6_address_mode'] = DHCPV6_STATEFUL
            subnet['ipv6_ra_mode'] = DHCPV6_STATEFUL

        return dhcp.DictModel(subnet)
Ejemplo n.º 4
0
    def _get_subnet(self, subnet_id):
        # Read data for subnet SUBNET_ID from etcd and translate it to the dict
        # form expected for insertion into a Neutron NetModel.
        LOG.debug("Read subnet %s from etcd", subnet_id)
        self.dirty_subnets.discard(subnet_id)

        subnet_key = datamodel_v1.key_for_subnet(subnet_id)
        response = self.client.read(subnet_key, consistent=True)
        data = etcdutils.safe_decode_json(response.value, 'subnet')
        LOG.debug("Subnet data: %s", data)

        if not (isinstance(data, dict) and 'cidr' in data
                and 'gateway_ip' in data):
            # Subnet data was invalid.
            LOG.warning("Invalid subnet data: %s => %s", response.value, data)
            raise ValidationFailed("Invalid subnet data")

        # Convert to form expected by NetModel.
        ip_version = 6 if ':' in data['cidr'] else 4
        subnet = {
            'enable_dhcp': True,
            'ip_version': ip_version,
            'cidr': data['cidr'],
            'dns_nameservers': data.get('dns_servers') or [],
            'id': subnet_id,
            'gateway_ip': data['gateway_ip'],
            'host_routes': data.get('host_routes', []),
            'network_id': data.get('network_id', NETWORK_ID)
        }
        if ip_version == 6:
            subnet['ipv6_address_mode'] = DHCPV6_STATEFUL
            subnet['ipv6_ra_mode'] = DHCPV6_STATEFUL

        return dhcp.DictModel(subnet)
Ejemplo n.º 5
0
 def subnet_deleted(self, subnet_id):
     """Delete data from etcd for a subnet that is no longer wanted."""
     LOG.info("Deleting subnet %s", subnet_id)
     # Delete the etcd key for this endpoint.
     key = datamodel_v1.key_for_subnet(subnet_id)
     try:
         self.client.delete(key)
     except etcd.EtcdKeyNotFound:
         # Already gone, treat as success.
         LOG.debug("Key %s, which we were deleting, disappeared", key)
Ejemplo n.º 6
0
 def subnet_deleted(self, subnet_id):
     """Delete data from etcd for a subnet that is no longer wanted."""
     LOG.info("Deleting subnet %s", subnet_id)
     # Delete the etcd key for this endpoint.
     key = datamodel_v1.key_for_subnet(subnet_id)
     try:
         self.client.delete(key)
     except etcd.EtcdKeyNotFound:
         # Already gone, treat as success.
         LOG.debug("Key %s, which we were deleting, disappeared", key)
Ejemplo n.º 7
0
    def subnet_created(self, subnet, prev_index=None):
        """Write data to etcd to describe a DHCP-enabled subnet."""
        LOG.info("Write subnet %s %s to etcd", subnet['id'], subnet['cidr'])
        data = subnet_etcd_data(subnet)

        # python-etcd doesn't keyword argument properly.
        kwargs = {}
        if prev_index is not None:
            kwargs['prevIndex'] = prev_index

        self.client.write(datamodel_v1.key_for_subnet(subnet['id']),
                          json.dumps(data), **kwargs)
Ejemplo n.º 8
0
    def subnet_created(self, subnet, prev_index=None):
        """Write data to etcd to describe a DHCP-enabled subnet."""
        LOG.info("Write subnet %s %s to etcd", subnet['id'], subnet['cidr'])
        data = subnet_etcd_data(subnet)

        # python-etcd doesn't keyword argument properly.
        kwargs = {}
        if prev_index is not None:
            kwargs['prevIndex'] = prev_index

        self.client.write(datamodel_v1.key_for_subnet(subnet['id']),
                          json.dumps(data),
                          **kwargs)
Ejemplo n.º 9
0
    def test_initial_snapshot(self, etcd_client_cls):
        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        # Check that running it invokes the etcd watcher loop.
        with mock.patch.object(agent, 'etcd') as etcdobj:
            agent.run()
            etcdobj.loop.assert_called_with()

        # Arrange for subnet read to fail.
        etcd_client.read.side_effect = etcd.EtcdKeyNotFound

        with mock.patch.object(agent, 'call_driver') as call_driver:
            # Notify a non-empty initial snapshot.
            etcd_snapshot_node = mock.Mock()
            etcd_snapshot_node.action = 'exist'
            etcd_snapshot_node.key = ("/calico/v1/host/" +
                                      socket.gethostname() +
                                      "/workload/openstack" +
                                      "/workload_id/endpoint/endpoint-4")
            etcd_snapshot_node.value = json.dumps({
                'state':
                'active',
                'name':
                'tap1234',
                'mac':
                'fe:16:65:12:33:44',
                'profile_ids': ['profile-1'],
                'ipv4_nets': ['10.28.0.2/32'],
                'ipv4_subnet_ids': ['v4subnet-4'],
                'ipv4_gateway':
                '10.28.0.1',
                'ipv6_nets': [],
                'ipv6_subnet_ids': []
            })
            etcd_snapshot_response = mock.Mock()
            etcd_snapshot_response.leaves = [etcd_snapshot_node]
            agent.etcd._on_snapshot_loaded(etcd_snapshot_response)

            # Check expected subnet was read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-4'),
                          consistent=True)
            ])
            etcd_client.read.reset_mock()

            # Check DHCP driver was not troubled - because the subnet data was
            # missing and so the port could not be processed further.
            call_driver.assert_not_called()
Ejemplo n.º 10
0
    def test_initial_snapshot(self, etcd_client_cls):
        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        # Check that running it invokes the etcd watcher loop.
        with mock.patch.object(agent, 'etcd') as etcdobj:
            agent.run()
            etcdobj.loop.assert_called_with()

        # Arrange for subnet read to fail.
        etcd_client.read.side_effect = etcd.EtcdKeyNotFound

        with mock.patch.object(agent, 'call_driver') as call_driver:
            # Notify a non-empty initial snapshot.
            etcd_snapshot_node = mock.Mock()
            etcd_snapshot_node.action = 'exist'
            etcd_snapshot_node.key = ("/calico/v1/host/" +
                                      socket.gethostname() +
                                      "/workload/openstack" +
                                      "/workload_id/endpoint/endpoint-4")
            etcd_snapshot_node.value = json.dumps({
                'state': 'active',
                'name': 'tap1234',
                'mac': 'fe:16:65:12:33:44',
                'profile_ids': ['profile-1'],
                'ipv4_nets': ['10.28.0.2/32'],
                'ipv4_subnet_ids': ['v4subnet-4'],
                'ipv4_gateway': '10.28.0.1',
                'ipv6_nets': [],
                'ipv6_subnet_ids': []
            })
            etcd_snapshot_response = mock.Mock()
            etcd_snapshot_response.leaves = [etcd_snapshot_node]
            agent.etcd._on_snapshot_loaded(etcd_snapshot_response)

            # Check expected subnet was read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-4'),
                          consistent=True)
            ])
            etcd_client.read.reset_mock()

            # Check DHCP driver was not troubled - because the subnet data was
            # missing and so the port could not be processed further.
            call_driver.assert_not_called()
Ejemplo n.º 11
0
    def get_subnet_data(self, subnet):
        """Get data for an subnet out of etcd.

        This should be used on subnets returned from functions like
        ``get_subnets``.

        :param subnet: A ``Subnet`` class.
        :return: A ``Subnet`` class with ``data`` not None.
        """
        LOG.debug("Getting subnet %s", subnet.id)

        result = self.client.read(datamodel_v1.key_for_subnet(subnet.id),
                                  timeout=ETCD_TIMEOUT)
        return Subnet(
            id=subnet.id,
            modified_index=result.modifiedIndex,
            data=result.value,
        )
Ejemplo n.º 12
0
    def get_subnet_data(self, subnet):
        """Get data for an subnet out of etcd.

        This should be used on subnets returned from functions like
        ``get_subnets``.

        :param subnet: A ``Subnet`` class.
        :return: A ``Subnet`` class with ``data`` not None.
        """
        LOG.debug("Getting subnet %s", subnet.id)

        result = self.client.read(datamodel_v1.key_for_subnet(subnet.id),
                                  timeout=ETCD_TIMEOUT)
        return Subnet(
            id=subnet.id,
            modified_index=result.modifiedIndex,
            data=result.value,
        )
Ejemplo n.º 13
0
    def atomic_delete_subnet(self, subnet):
        """Atomically delete a given subnet.

        This method tolerates attempting to delete keys that are already
        missing, otherwise allows exceptions from etcd to bubble up.
        """
        LOG.info("Atomically deleting subnet id %s, modified %s", subnet.id,
                 subnet.modified_index)

        try:
            self.client.delete(
                datamodel_v1.key_for_subnet(subnet.id),
                prevIndex=subnet.modified_index,
                timeout=ETCD_TIMEOUT,
            )
        except etcd.EtcdKeyNotFound:
            # Trying to delete stuff that doesn't exist is ok, but log it.
            LOG.info("Subnet %s was already deleted, nothing to do.",
                     subnet.id)
Ejemplo n.º 14
0
    def atomic_delete_subnet(self, subnet):
        """Atomically delete a given subnet.

        This method tolerates attempting to delete keys that are already
        missing, otherwise allows exceptions from etcd to bubble up.
        """
        LOG.info(
            "Atomically deleting subnet id %s, modified %s",
            subnet.id,
            subnet.modified_index
        )

        try:
            self.client.delete(
                datamodel_v1.key_for_subnet(subnet.id),
                prevIndex=subnet.modified_index,
                timeout=ETCD_TIMEOUT,
            )
        except etcd.EtcdKeyNotFound:
            # Trying to delete stuff that doesn't exist is ok, but log it.
            LOG.info(
                "Subnet %s was already deleted, nothing to do.",
                subnet.id
            )
Ejemplo n.º 15
0
    def test_mainline(self, etcd_client_cls):
        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        # Check that running it invokes the etcd watcher loop.
        with mock.patch.object(agent, 'etcd') as etcdobj:
            agent.run()
            etcdobj.loop.assert_called_with()

        # Notify initial snapshot (empty).
        with mock.patch.object(agent, 'call_driver') as call_driver:
            etcd_snapshot_response = mock.Mock()
            etcd_snapshot_response.leaves = []
            agent.etcd._on_snapshot_loaded(etcd_snapshot_response)
            call_driver.assert_not_called()

        # Prepare subnet reads for the endpoints that we will notify.
        self.first_workload_read = True

        def etcd_client_read(key, **kwargs):
            LOG.info('etcd_client_read %s %s', key, kwargs)
            if 'v4subnet-1' in key:
                self.assertEqual('/calico/dhcp/v1/subnet/v4subnet-1', key)
                return EtcdResponse(value=json.dumps({
                    'cidr': '10.28.0.0/24',
                    'gateway_ip': '10.28.0.1',
                    'host_routes': []
                }))
            if 'v6subnet-1' in key:
                return EtcdResponse(
                    value=json.dumps({
                        'cidr': '2001:db8:1::/80',
                        'gateway_ip': '2001:db8:1::1'
                    }))
            if 'v4subnet-2' in key:
                return EtcdResponse(value=json.dumps({
                    'cidr':
                    '10.29.0.0/24',
                    'gateway_ip':
                    '10.29.0.1',
                    'host_routes': [{
                        'destination': '11.11.0.0/16',
                        'nexthop': '10.65.0.1'
                    }]
                }))
            if key == '/calico/v1/host/nj-ubuntu/workload':
                if self.first_workload_read:
                    # This is the recursive read that the CalicoEtcdWatcher
                    # loop makes after we've triggered it to resync.
                    etcd_snapshot_node = mock.Mock()
                    etcd_snapshot_node.action = 'exist'
                    etcd_snapshot_node.key = (
                        "/calico/v1/host/" + socket.gethostname() +
                        "/workload/openstack" +
                        "/workload_id/endpoint/endpoint-4")
                    etcd_snapshot_node.value = json.dumps({
                        'state':
                        'active',
                        'name':
                        'tap1234',
                        'mac':
                        'fe:16:65:12:33:44',
                        'profile_ids': ['profile-1'],
                        'ipv4_nets': ['10.28.0.2/32'],
                        'ipv4_subnet_ids': ['v4subnet-1'],
                        'ipv4_gateway':
                        '10.28.0.1',
                        'ipv6_nets': [],
                        'ipv6_subnet_ids': []
                    })
                    etcd_snapshot_response = mock.Mock()
                    etcd_snapshot_response.etcd_index = 99
                    etcd_snapshot_response.leaves = [etcd_snapshot_node]
                    self.first_workload_read = False
                    return etcd_snapshot_response

            eventlet.sleep(10)
            return None

        etcd_client.read.side_effect = etcd_client_read

        with mock.patch.object(agent, 'call_driver') as call_driver:
            # Notify an endpoint.
            agent.etcd.on_endpoint_set(
                EtcdResponse(
                    value=json.dumps({
                        'state': 'active',
                        'name': 'tap1234',
                        'mac': 'fe:16:65:12:33:44',
                        'profile_ids': ['profile-1'],
                        'ipv4_nets': ['10.28.0.2/32'],
                        'ipv4_subnet_ids': ['v4subnet-1'],
                        'ipv4_gateway': '10.28.0.1',
                        'ipv6_nets': ['2001:db8:1::2/128'],
                        'ipv6_subnet_ids': ['v6subnet-1'],
                        'ipv6_gateway': '2001:db8:1::1'
                    })), 'hostname-ignored', 'openstack',
                'workload-id-ignored', 'endpoint-1')

            # Check expected subnets were read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-1'),
                          consistent=True),
                mock.call(datamodel_v1.key_for_subnet('v6subnet-1'),
                          consistent=True)
            ],
                                              any_order=True)
            etcd_client.read.reset_mock()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify another endpoint (using the same subnets).
            agent.etcd.on_endpoint_set(
                EtcdResponse(
                    value=json.dumps({
                        'state': 'active',
                        'name': 'tap5678',
                        'mac': 'fe:16:65:12:33:55',
                        'profile_ids': ['profile-1'],
                        'ipv4_nets': ['10.28.0.3/32'],
                        'ipv4_subnet_ids': ['v4subnet-1'],
                        'ipv4_gateway': '10.28.0.1',
                        'ipv6_nets': ['2001:db8:1::3/128'],
                        'ipv6_subnet_ids': ['v6subnet-1'],
                        'ipv6_gateway': '2001:db8:1::1',
                        'fqdn': 'calico-vm17.datcon.co.uk'
                    })), 'hostname-ignored', 'openstack',
                'workload-id-ignored', 'endpoint-2')

            # Check no further etcd reads.
            etcd_client.read.assert_not_called()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify deletion of the first endpoint.
            agent.etcd.on_endpoint_delete(None, 'hostname-ignored',
                                          'openstack', 'workload-id-ignored',
                                          'endpoint-1')

            # Check no further etcd reads.
            etcd_client.read.assert_not_called()

            # Check DHCP driver was asked to reload allocations.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify another endpoint using a new subnet.
            agent.etcd.on_endpoint_set(
                EtcdResponse(value=json.dumps({
                    'state': 'active',
                    'name': 'tapABCD',
                    'mac': 'fe:16:65:12:33:66',
                    'profile_ids': ['profile-1'],
                    'ipv4_nets': ['10.29.0.3/32'],
                    'ipv4_subnet_ids': ['v4subnet-2'],
                    'ipv4_gateway': '10.29.0.1',
                    'ipv6_nets': [],
                    'ipv6_subnet_ids': []
                })), 'hostname-ignored', 'openstack', 'workload-id-ignored',
                'endpoint-3')

            # Check expected new subnet was read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-2'),
                          consistent=True)
            ])
            etcd_client.read.reset_mock()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Set the endpoint watcher loop running.
            eventlet.spawn(agent.etcd.loop)

            # Report that the subnet watcher noticed a change.
            agent.etcd.on_subnet_set(None, 'some-subnet-X')
            eventlet.sleep(0.2)

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Report that the subnet watcher loaded a new snapshot.
            agent.etcd.subnet_watcher._on_snapshot_loaded('ignored')
            eventlet.sleep(0.2)

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)
Ejemplo n.º 16
0
    def test_dir_delete(self, etcd_client_cls):
        LOG.debug('test_dir_delete')

        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        def etcd_client_read(key, **kwargs):
            LOG.info('etcd_client_read %s %s', key, kwargs)
            if 'v4subnet-1' in key:
                return EtcdResponse(value=json.dumps({
                    'cidr': '10.28.0.0/24',
                    'gateway_ip': '10.28.0.1'
                }))
            if 'v6subnet-1' in key:
                return EtcdResponse(value=json.dumps({
                    'cidr': '2001:db8:1::/80',
                    'gateway_ip': '2001:db8:1::1'
                }))

            eventlet.sleep(10)
            return None

        LOG.debug('etcd_client=%r', etcd_client)
        etcd_client.read.side_effect = etcd_client_read

        # Notify initial snapshot (empty).
        etcd_snapshot_response = mock.Mock()
        etcd_snapshot_response.leaves = []
        LOG.debug('Call _on_snapshot_loaded')
        agent.etcd._on_snapshot_loaded(etcd_snapshot_response)

        with mock.patch.object(agent, 'call_driver') as call_driver:
            # Notify an endpoint.
            agent.etcd.on_endpoint_set(EtcdResponse(value=json.dumps({
                'state': 'active',
                'name': 'tap1234',
                'mac': 'fe:16:65:12:33:44',
                'profile_ids': ['profile-1'],
                'ipv4_nets': ['10.28.0.2/32'],
                'ipv4_subnet_ids': ['v4subnet-1'],
                'ipv4_gateway': '10.28.0.1',
                'ipv6_nets': ['2001:db8:1::2/128'],
                'ipv6_subnet_ids': ['v6subnet-1'],
                'ipv6_gateway': '2001:db8:1::1'
            })),
                'hostname-ignored',
                'openstack',
                'workload-id-ignored',
                'endpoint-1'
            )

            # Check expected subnets were read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-1'),
                          consistent=True),
                mock.call(datamodel_v1.key_for_subnet('v6subnet-1'),
                          consistent=True)
            ],
                any_order=True)
            etcd_client.read.reset_mock()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify deletion of one of that endpoint's parent directories.
            agent.etcd.on_dir_delete(None,
                                     hostname='hostname-ignored',
                                     orchestrator='openstack',
                                     workload_id='workload-id-ignored')
            self.assertTrue(agent.etcd.resync_after_current_poll)
Ejemplo n.º 17
0
    def test_dir_delete(self, etcd_client_cls):
        LOG.debug('test_dir_delete')

        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        def etcd_client_read(key, **kwargs):
            LOG.info('etcd_client_read %s %s', key, kwargs)
            if 'v4subnet-1' in key:
                return EtcdResponse(value=json.dumps({
                    'cidr': '10.28.0.0/24',
                    'gateway_ip': '10.28.0.1'
                }))
            if 'v6subnet-1' in key:
                return EtcdResponse(
                    value=json.dumps({
                        'cidr': '2001:db8:1::/80',
                        'gateway_ip': '2001:db8:1::1'
                    }))

            eventlet.sleep(10)
            return None

        LOG.debug('etcd_client=%r', etcd_client)
        etcd_client.read.side_effect = etcd_client_read

        # Notify initial snapshot (empty).
        etcd_snapshot_response = mock.Mock()
        etcd_snapshot_response.leaves = []
        LOG.debug('Call _on_snapshot_loaded')
        agent.etcd._on_snapshot_loaded(etcd_snapshot_response)

        with mock.patch.object(agent, 'call_driver') as call_driver:
            # Notify an endpoint.
            agent.etcd.on_endpoint_set(
                EtcdResponse(
                    value=json.dumps({
                        'state': 'active',
                        'name': 'tap1234',
                        'mac': 'fe:16:65:12:33:44',
                        'profile_ids': ['profile-1'],
                        'ipv4_nets': ['10.28.0.2/32'],
                        'ipv4_subnet_ids': ['v4subnet-1'],
                        'ipv4_gateway': '10.28.0.1',
                        'ipv6_nets': ['2001:db8:1::2/128'],
                        'ipv6_subnet_ids': ['v6subnet-1'],
                        'ipv6_gateway': '2001:db8:1::1'
                    })), 'hostname-ignored', 'openstack',
                'workload-id-ignored', 'endpoint-1')

            # Check expected subnets were read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-1'),
                          consistent=True),
                mock.call(datamodel_v1.key_for_subnet('v6subnet-1'),
                          consistent=True)
            ],
                                              any_order=True)
            etcd_client.read.reset_mock()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify deletion of one of that endpoint's parent directories.
            agent.etcd.on_dir_delete(None,
                                     hostname='hostname-ignored',
                                     orchestrator='openstack',
                                     workload_id='workload-id-ignored')
            self.assertTrue(agent.etcd.resync_after_current_poll)
Ejemplo n.º 18
0
    def test_mainline(self, etcd_client_cls):
        # Create the DHCP agent.
        agent = CalicoDhcpAgent()
        etcd_client = etcd_client_cls.return_value

        # Check that running it invokes the etcd watcher loop.
        with mock.patch.object(agent, 'etcd') as etcdobj:
            agent.run()
            etcdobj.loop.assert_called_with()

        # Notify initial snapshot (empty).
        with mock.patch.object(agent, 'call_driver') as call_driver:
            etcd_snapshot_response = mock.Mock()
            etcd_snapshot_response.leaves = []
            agent.etcd._on_snapshot_loaded(etcd_snapshot_response)
            call_driver.assert_not_called()

        # Prepare subnet reads for the endpoints that we will notify.
        self.first_workload_read = True

        def etcd_client_read(key, **kwargs):
            LOG.info('etcd_client_read %s %s', key, kwargs)
            if 'v4subnet-1' in key:
                self.assertEqual('/calico/dhcp/v1/subnet/v4subnet-1',
                                 key)
                return EtcdResponse(value=json.dumps({
                    'cidr': '10.28.0.0/24',
                    'gateway_ip': '10.28.0.1',
                    'host_routes': []
                }))
            if 'v6subnet-1' in key:
                return EtcdResponse(value=json.dumps({
                    'cidr': '2001:db8:1::/80',
                    'gateway_ip': '2001:db8:1::1'
                }))
            if 'v4subnet-2' in key:
                return EtcdResponse(value=json.dumps({
                    'cidr': '10.29.0.0/24',
                    'gateway_ip': '10.29.0.1',
                    'host_routes': [{'destination': '11.11.0.0/16',
                                     'nexthop': '10.65.0.1'}]
                }))
            if key == '/calico/v1/host/nj-ubuntu/workload':
                if self.first_workload_read:
                    # This is the recursive read that the CalicoEtcdWatcher
                    # loop makes after we've triggered it to resync.
                    etcd_snapshot_node = mock.Mock()
                    etcd_snapshot_node.action = 'exist'
                    etcd_snapshot_node.key = (
                        "/calico/v1/host/" +
                        socket.gethostname() +
                        "/workload/openstack" +
                        "/workload_id/endpoint/endpoint-4"
                    )
                    etcd_snapshot_node.value = json.dumps({
                        'state': 'active',
                        'name': 'tap1234',
                        'mac': 'fe:16:65:12:33:44',
                        'profile_ids': ['profile-1'],
                        'ipv4_nets': ['10.28.0.2/32'],
                        'ipv4_subnet_ids': ['v4subnet-1'],
                        'ipv4_gateway': '10.28.0.1',
                        'ipv6_nets': [],
                        'ipv6_subnet_ids': []
                    })
                    etcd_snapshot_response = mock.Mock()
                    etcd_snapshot_response.etcd_index = 99
                    etcd_snapshot_response.leaves = [etcd_snapshot_node]
                    self.first_workload_read = False
                    return etcd_snapshot_response

            eventlet.sleep(10)
            return None

        etcd_client.read.side_effect = etcd_client_read

        with mock.patch.object(agent, 'call_driver') as call_driver:
            # Notify an endpoint.
            agent.etcd.on_endpoint_set(EtcdResponse(value=json.dumps({
                'state': 'active',
                'name': 'tap1234',
                'mac': 'fe:16:65:12:33:44',
                'profile_ids': ['profile-1'],
                'ipv4_nets': ['10.28.0.2/32'],
                'ipv4_subnet_ids': ['v4subnet-1'],
                'ipv4_gateway': '10.28.0.1',
                'ipv6_nets': ['2001:db8:1::2/128'],
                'ipv6_subnet_ids': ['v6subnet-1'],
                'ipv6_gateway': '2001:db8:1::1'
            })),
                'hostname-ignored',
                'openstack',
                'workload-id-ignored',
                'endpoint-1'
            )

            # Check expected subnets were read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-1'),
                          consistent=True),
                mock.call(datamodel_v1.key_for_subnet('v6subnet-1'),
                          consistent=True)
            ],
                any_order=True)
            etcd_client.read.reset_mock()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify another endpoint (using the same subnets).
            agent.etcd.on_endpoint_set(EtcdResponse(value=json.dumps({
                'state': 'active',
                'name': 'tap5678',
                'mac': 'fe:16:65:12:33:55',
                'profile_ids': ['profile-1'],
                'ipv4_nets': ['10.28.0.3/32'],
                'ipv4_subnet_ids': ['v4subnet-1'],
                'ipv4_gateway': '10.28.0.1',
                'ipv6_nets': ['2001:db8:1::3/128'],
                'ipv6_subnet_ids': ['v6subnet-1'],
                'ipv6_gateway': '2001:db8:1::1',
                'fqdn': 'calico-vm17.datcon.co.uk'
            })),
                'hostname-ignored',
                'openstack',
                'workload-id-ignored',
                'endpoint-2'
            )

            # Check no further etcd reads.
            etcd_client.read.assert_not_called()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify deletion of the first endpoint.
            agent.etcd.on_endpoint_delete(None,
                                          'hostname-ignored',
                                          'openstack',
                                          'workload-id-ignored',
                                          'endpoint-1')

            # Check no further etcd reads.
            etcd_client.read.assert_not_called()

            # Check DHCP driver was asked to reload allocations.
            call_driver.assert_called_with('restart', mock.ANY)

            # Notify another endpoint using a new subnet.
            agent.etcd.on_endpoint_set(EtcdResponse(value=json.dumps({
                'state': 'active',
                'name': 'tapABCD',
                'mac': 'fe:16:65:12:33:66',
                'profile_ids': ['profile-1'],
                'ipv4_nets': ['10.29.0.3/32'],
                'ipv4_subnet_ids': ['v4subnet-2'],
                'ipv4_gateway': '10.29.0.1',
                'ipv6_nets': [],
                'ipv6_subnet_ids': []
            })),
                'hostname-ignored',
                'openstack',
                'workload-id-ignored',
                'endpoint-3'
            )

            # Check expected new subnet was read from etcd.
            etcd_client.read.assert_has_calls([
                mock.call(datamodel_v1.key_for_subnet('v4subnet-2'),
                          consistent=True)
            ])
            etcd_client.read.reset_mock()

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Set the endpoint watcher loop running.
            eventlet.spawn(agent.etcd.loop)

            # Report that the subnet watcher noticed a change.
            agent.etcd.on_subnet_set(None, 'some-subnet-X')
            eventlet.sleep(0.2)

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)

            # Report that the subnet watcher loaded a new snapshot.
            agent.etcd.subnet_watcher._on_snapshot_loaded('ignored')
            eventlet.sleep(0.2)

            # Check DHCP driver was asked to restart.
            call_driver.assert_called_with('restart', mock.ANY)