Refactor kuryr libnetwork code
Change-Id: Ia81975d15cab318dc6688a6ebd51dd6877ba0aaf Implement: blueprint migrate-docker-to-cni
This commit is contained in:
parent
cded431e7d
commit
05d55ec9bd
@ -284,9 +284,14 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
kwargs['volumes'] = [b['bind'] for b in binds.values()]
|
||||
self._process_exposed_ports(network_driver.neutron_api, container,
|
||||
kwargs)
|
||||
self._process_networking_config(
|
||||
context, container, requested_networks, host_config,
|
||||
kwargs, docker)
|
||||
# Process the first requested network at create time. The rest
|
||||
# will be processed after create.
|
||||
requested_network = requested_networks.pop()
|
||||
security_group_ids = utils.get_security_group_ids(
|
||||
context, container.security_groups)
|
||||
network_driver.process_networking_config(
|
||||
container, requested_network, host_config, kwargs, docker,
|
||||
security_group_ids=security_group_ids)
|
||||
if container.auto_remove:
|
||||
host_config['auto_remove'] = container.auto_remove
|
||||
if self._should_limit_memory(container):
|
||||
@ -377,48 +382,10 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
ports.append((port, proto))
|
||||
kwargs['ports'] = ports
|
||||
|
||||
def _process_networking_config(self, context, container,
|
||||
requested_networks, host_config,
|
||||
container_kwargs, docker):
|
||||
network_driver = zun_network.driver(context=context, docker_api=docker)
|
||||
neutron_api = network_driver.neutron_api
|
||||
|
||||
# Process the first requested network at create time. The rest
|
||||
# will be processed after create.
|
||||
requested_network = requested_networks.pop()
|
||||
docker_net_name = self._get_docker_network_name(
|
||||
context, requested_network['network'])
|
||||
security_group_ids = utils.get_security_group_ids(
|
||||
context, container.security_groups)
|
||||
docker_network = network_driver.inspect_network(docker_net_name)
|
||||
device_owner = network_driver.get_device_owner()
|
||||
neutron_net_id = docker_network['Options']['neutron.net.uuid']
|
||||
addresses, port = neutron_api.create_or_update_port(
|
||||
container, neutron_net_id, requested_network, device_owner,
|
||||
security_group_ids, set_binding_host=True)
|
||||
container.addresses = {requested_network['network']: addresses}
|
||||
|
||||
ipv4_address = None
|
||||
ipv6_address = None
|
||||
for address in addresses:
|
||||
if address['version'] == 4:
|
||||
ipv4_address = address['addr']
|
||||
if address['version'] == 6:
|
||||
ipv6_address = address['addr']
|
||||
|
||||
endpoint_config = docker.create_endpoint_config(
|
||||
ipv4_address=ipv4_address, ipv6_address=ipv6_address)
|
||||
network_config = docker.create_networking_config({
|
||||
docker_net_name: endpoint_config})
|
||||
|
||||
host_config['network_mode'] = docker_net_name
|
||||
container_kwargs['networking_config'] = network_config
|
||||
container_kwargs['mac_address'] = port['mac_address']
|
||||
|
||||
def _provision_network(self, context, network_driver, requested_networks):
|
||||
for rq_network in requested_networks:
|
||||
self._get_or_create_docker_network(
|
||||
context, network_driver, rq_network['network'])
|
||||
network_driver.get_or_create_network(context,
|
||||
rq_network['network'])
|
||||
|
||||
def _get_secgorup_name(self, container_uuid):
|
||||
return consts.NAME_PREFIX + container_uuid
|
||||
@ -443,11 +410,8 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
# This network is already setup so skip it
|
||||
continue
|
||||
|
||||
docker_net_name = self._get_docker_network_name(
|
||||
context, network['network'])
|
||||
addrs = network_driver.connect_container_to_network(
|
||||
container, docker_net_name, network,
|
||||
security_groups=security_group_ids)
|
||||
container, network, security_groups=security_group_ids)
|
||||
addresses[network['network']] = addrs
|
||||
|
||||
return addresses
|
||||
@ -475,9 +439,8 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
if not container.addresses:
|
||||
return
|
||||
for neutron_net in container.addresses:
|
||||
docker_net = neutron_net
|
||||
network_driver.disconnect_container_from_network(
|
||||
container, docker_net, neutron_network_id=neutron_net)
|
||||
container, neutron_network_id=neutron_net)
|
||||
|
||||
def _cleanup_exposed_ports(self, neutron_api, container):
|
||||
exposed_ports = {}
|
||||
@ -1031,21 +994,6 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
def _encode_utf8(self, value):
|
||||
return value.encode('utf-8')
|
||||
|
||||
def _get_or_create_docker_network(self, context, network_driver,
|
||||
neutron_net_id):
|
||||
docker_net_name = self._get_docker_network_name(context,
|
||||
neutron_net_id)
|
||||
docker_networks = network_driver.list_networks(names=[docker_net_name])
|
||||
if not docker_networks:
|
||||
network_driver.create_network(neutron_net_id=neutron_net_id,
|
||||
name=docker_net_name)
|
||||
|
||||
def _get_docker_network_name(self, context, neutron_net_id):
|
||||
# Note(kiseok7): neutron_net_id is a unique ID in neutron networks and
|
||||
# docker networks.
|
||||
# so it will not be duplicated across projects.
|
||||
return neutron_net_id
|
||||
|
||||
def get_container_name(self, container):
|
||||
return consts.NAME_PREFIX + container.uuid
|
||||
|
||||
@ -1146,9 +1094,8 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
with docker_utils.docker_client() as docker:
|
||||
network_driver = zun_network.driver(context,
|
||||
docker_api=docker)
|
||||
docker_net = self._get_docker_network_name(context, network)
|
||||
network_driver.disconnect_container_from_network(
|
||||
container, docker_net, network)
|
||||
network_driver.disconnect_container_from_network(container,
|
||||
network)
|
||||
|
||||
# Only clear network info related to this network
|
||||
# Cannot del container.address directly which will not update
|
||||
@ -1165,8 +1112,7 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
if container.security_groups:
|
||||
security_group_ids = utils.get_security_group_ids(
|
||||
context, container.security_groups)
|
||||
network_driver = zun_network.driver(context,
|
||||
docker_api=docker)
|
||||
network_driver = zun_network.driver(context, docker_api=docker)
|
||||
network = requested_network['network']
|
||||
if network in container.addresses:
|
||||
raise exception.ZunException('Container %(container)s has '
|
||||
@ -1174,11 +1120,9 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
'network %(network)s.'
|
||||
% {'container': container.uuid,
|
||||
'network': network})
|
||||
self._get_or_create_docker_network(context, network_driver,
|
||||
network)
|
||||
docker_net_name = self._get_docker_network_name(context, network)
|
||||
network_driver.get_or_create_network(context, network)
|
||||
addrs = network_driver.connect_container_to_network(
|
||||
container, docker_net_name, requested_network,
|
||||
container, requested_network,
|
||||
security_groups=security_group_ids)
|
||||
if addrs is None:
|
||||
raise exception.ZunException(_(
|
||||
@ -1192,13 +1136,8 @@ class DockerDriver(driver.BaseDriver, driver.ContainerDriver,
|
||||
|
||||
def create_network(self, context, neutron_net_id):
|
||||
with docker_utils.docker_client() as docker:
|
||||
network_driver = zun_network.driver(context,
|
||||
docker_api=docker)
|
||||
docker_net_name = self._get_docker_network_name(
|
||||
context, neutron_net_id)
|
||||
return network_driver.create_network(
|
||||
neutron_net_id=neutron_net_id,
|
||||
name=docker_net_name)
|
||||
network_driver = zun_network.driver(context, docker_api=docker)
|
||||
return network_driver.create_network(neutron_net_id)
|
||||
|
||||
def delete_network(self, context, network):
|
||||
with docker_utils.docker_client() as docker:
|
||||
|
@ -447,9 +447,6 @@ class ContainerDriver(object):
|
||||
def delete_network(self, context, network):
|
||||
raise NotImplementedError()
|
||||
|
||||
def inspect_network(self, network):
|
||||
raise NotImplementedError()
|
||||
|
||||
def pull_image(self, context, repo, tag, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -40,7 +40,13 @@ class KuryrNetwork(network.Network):
|
||||
self.neutron_api = neutron.NeutronAPI(context)
|
||||
self.context = context
|
||||
|
||||
def create_network(self, name, neutron_net_id):
|
||||
def get_or_create_network(self, context, neutron_net_id):
|
||||
docker_net_name = neutron_net_id
|
||||
docker_networks = self.docker.networks(names=[docker_net_name])
|
||||
if not docker_networks:
|
||||
self.create_network(neutron_net_id)
|
||||
|
||||
def create_network(self, neutron_net_id):
|
||||
"""Create a docker network with Kuryr driver.
|
||||
|
||||
The docker network to be created will be based on the specified
|
||||
@ -53,6 +59,7 @@ class KuryrNetwork(network.Network):
|
||||
neutron net, retrieving the cidr, gateway of each
|
||||
subnet, and compile the list of parameters for docker.create_network.
|
||||
"""
|
||||
name = neutron_net_id
|
||||
# find a v4 and/or v6 subnet of the network
|
||||
shared = \
|
||||
self.neutron_api.get_neutron_network(neutron_net_id)[
|
||||
@ -136,7 +143,7 @@ class KuryrNetwork(network.Network):
|
||||
"%(networks)s",
|
||||
{"net_id": network.neutron_net_id,
|
||||
"networks": networks})
|
||||
docker_networks = self.list_networks(names=[network.name])
|
||||
docker_networks = self.docker.networks(names=[network.name])
|
||||
LOG.debug("docker networks with name matching '%(name)s': "
|
||||
"%(networks)s",
|
||||
{"name": network.name,
|
||||
@ -187,26 +194,44 @@ class KuryrNetwork(network.Network):
|
||||
self.docker.remove_network(network.name)
|
||||
network.destroy()
|
||||
|
||||
def inspect_network(self, network_name):
|
||||
return self.docker.inspect_network(network_name)
|
||||
def process_networking_config(self, container, requested_network,
|
||||
host_config, container_kwargs, docker,
|
||||
security_group_ids):
|
||||
docker_net_name = requested_network['network']
|
||||
neutron_net_id = requested_network['network']
|
||||
addresses, port = self.neutron_api.create_or_update_port(
|
||||
container, neutron_net_id, requested_network, DEVICE_OWNER,
|
||||
security_group_ids, set_binding_host=True)
|
||||
container.addresses = {requested_network['network']: addresses}
|
||||
|
||||
def list_networks(self, **kwargs):
|
||||
return self.docker.networks(**kwargs)
|
||||
ipv4_address = None
|
||||
ipv6_address = None
|
||||
for address in addresses:
|
||||
if address['version'] == 4:
|
||||
ipv4_address = address['addr']
|
||||
if address['version'] == 6:
|
||||
ipv6_address = address['addr']
|
||||
|
||||
def get_device_owner(self):
|
||||
return DEVICE_OWNER
|
||||
endpoint_config = docker.create_endpoint_config(
|
||||
ipv4_address=ipv4_address, ipv6_address=ipv6_address)
|
||||
network_config = docker.create_networking_config({
|
||||
docker_net_name: endpoint_config})
|
||||
|
||||
def connect_container_to_network(self, container, network_name,
|
||||
requested_network, security_groups=None):
|
||||
host_config['network_mode'] = docker_net_name
|
||||
container_kwargs['networking_config'] = network_config
|
||||
container_kwargs['mac_address'] = port['mac_address']
|
||||
|
||||
def connect_container_to_network(self, container, requested_network,
|
||||
security_groups=None):
|
||||
"""Connect container to the network
|
||||
|
||||
This method will create a neutron port, retrieve the ip address(es)
|
||||
of the port, and pass them to docker.connect_container_to_network.
|
||||
"""
|
||||
network_name = requested_network['network']
|
||||
container_id = container.container_id
|
||||
|
||||
network = self.inspect_network(network_name)
|
||||
neutron_net_id = network['Options']['neutron.net.uuid']
|
||||
neutron_net_id = requested_network['network']
|
||||
addresses, original_port = self.neutron_api.create_or_update_port(
|
||||
container, neutron_net_id, requested_network, DEVICE_OWNER,
|
||||
security_groups)
|
||||
@ -259,8 +284,8 @@ class KuryrNetwork(network.Network):
|
||||
LOG.debug('Unable to delete port %s as it no longer '
|
||||
'exists.', port_id)
|
||||
|
||||
def disconnect_container_from_network(self, container, network_name,
|
||||
neutron_network_id=None):
|
||||
def disconnect_container_from_network(self, container, neutron_network_id):
|
||||
network_name = neutron_network_id
|
||||
container_id = container.container_id
|
||||
|
||||
addrs_list = []
|
||||
|
@ -37,16 +37,16 @@ class Network(object, metaclass=abc.ABCMeta):
|
||||
def init(self, context, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_or_create_network(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_network(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_network(self, network_name, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def inspect_network(self, network_name, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def list_networks(self, **kwargs):
|
||||
def process_networking_config(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def connect_container_to_network(self, container, network_name, **kwargs):
|
||||
|
@ -821,21 +821,19 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
def test_network_detach(self, mock_detach):
|
||||
mock_container = mock.MagicMock()
|
||||
self.driver.network_detach(self.context, mock_container, 'network')
|
||||
mock_detach.assert_called_once_with(mock_container,
|
||||
'network',
|
||||
mock.ANY)
|
||||
mock_detach.assert_called_once_with(mock_container, 'network')
|
||||
|
||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||
'.connect_container_to_network')
|
||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||
'.disconnect_container_from_network')
|
||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||
'.list_networks')
|
||||
def test_network_attach(self, mock_list, mock_disconnect, mock_connect):
|
||||
'.get_or_create_network')
|
||||
def test_network_attach(self, mock_get_or_create, mock_disconnect,
|
||||
mock_connect):
|
||||
mock_container = mock.Mock()
|
||||
mock_container.security_groups = None
|
||||
mock_container.addresses = {}
|
||||
mock_list.return_value = {'network': 'network'}
|
||||
requested_network = {'network': 'network',
|
||||
'port': '',
|
||||
'fixed_ip': '',
|
||||
@ -843,7 +841,6 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
self.driver.network_attach(self.context, mock_container,
|
||||
requested_network)
|
||||
mock_connect.assert_called_once_with(mock_container,
|
||||
'network',
|
||||
requested_network,
|
||||
security_groups=None)
|
||||
|
||||
@ -863,15 +860,14 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||
'.connect_container_to_network')
|
||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||
'.list_networks')
|
||||
def test_network_attach_with_security_group(self, mock_list,
|
||||
'.get_or_create_network')
|
||||
def test_network_attach_with_security_group(self, mock_get_or_create,
|
||||
mock_connect,
|
||||
mock_get_sec_group_id):
|
||||
test_sec_group_id = '84e3a4c1-c8cd-46b1-a0d9-c8c35f6a32a4'
|
||||
mock_container = mock.Mock()
|
||||
mock_container.security_groups = ['test_sec_group']
|
||||
mock_container.addresses = {}
|
||||
mock_list.return_value = {'network': 'network'}
|
||||
mock_get_sec_group_id.return_value = test_sec_group_id
|
||||
requested_network = {'network': 'network',
|
||||
'port': '',
|
||||
@ -880,7 +876,6 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
self.driver.network_attach(self.context, mock_container,
|
||||
requested_network)
|
||||
mock_connect.assert_called_once_with(mock_container,
|
||||
'network',
|
||||
requested_network,
|
||||
security_groups=test_sec_group_id)
|
||||
|
||||
|
@ -191,15 +191,14 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
self, mock_neutron_api_cls, mock_save, mock_create):
|
||||
self.network_driver.neutron_api.subnets[0].pop('subnetpool_id')
|
||||
mock_neutron_api_cls.return_value = self.network_driver.neutron_api
|
||||
name = 'test_kuryr_network'
|
||||
neutron_net_id = 'fake-net-id'
|
||||
with mock.patch.object(self.network_driver.docker, 'create_network',
|
||||
return_value={'Id': 'docker-net'}
|
||||
) as mock_create_network:
|
||||
network = self.network_driver.create_network(name, neutron_net_id)
|
||||
network = self.network_driver.create_network(neutron_net_id)
|
||||
self.assertEqual('docker-net', network.network_id)
|
||||
mock_create_network.assert_called_once_with(
|
||||
name=name,
|
||||
name=neutron_net_id,
|
||||
driver='kuryr',
|
||||
enable_ipv6=False,
|
||||
ipam={'Config': [{'Subnet': '10.5.0.0/16', 'Gateway': '10.5.0.1'}],
|
||||
@ -216,15 +215,14 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
def test_create_network_with_subnetpool(
|
||||
self, mock_neutron_api_cls, mock_save, mock_create):
|
||||
mock_neutron_api_cls.return_value = self.network_driver.neutron_api
|
||||
name = 'test_kuryr_network'
|
||||
neutron_net_id = 'fake-net-id'
|
||||
with mock.patch.object(self.network_driver.docker, 'create_network',
|
||||
return_value={'Id': 'docker-net'}
|
||||
) as mock_create_network:
|
||||
network = self.network_driver.create_network(name, neutron_net_id)
|
||||
network = self.network_driver.create_network(neutron_net_id)
|
||||
self.assertEqual('docker-net', network.network_id)
|
||||
mock_create_network.assert_called_once_with(
|
||||
name=name,
|
||||
name=neutron_net_id,
|
||||
driver='kuryr',
|
||||
enable_ipv6=False,
|
||||
ipam={'Config': [{'Subnet': '10.5.0.0/16', 'Gateway': '10.5.0.1'}],
|
||||
@ -242,7 +240,6 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
def test_create_network_already_exist(
|
||||
self, mock_neutron_api_cls, mock_list, mock_save, mock_create):
|
||||
mock_neutron_api_cls.return_value = self.network_driver.neutron_api
|
||||
name = 'test_kuryr_network'
|
||||
neutron_net_id = 'fake-net-id'
|
||||
docker_net_id = 'docker-net'
|
||||
fake_network = mock.Mock()
|
||||
@ -253,34 +250,23 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
with mock.patch.object(self.network_driver.docker, 'networks',
|
||||
return_value=[{'Id': docker_net_id}]
|
||||
) as mock_list_network:
|
||||
network = self.network_driver.create_network(name, neutron_net_id)
|
||||
network = self.network_driver.create_network(neutron_net_id)
|
||||
self.assertEqual(docker_net_id, network.network_id)
|
||||
mock_list.assert_called_once_with(
|
||||
self.context, filters={'neutron_net_id': neutron_net_id})
|
||||
mock_list_network.assert_called_once_with(names=[name])
|
||||
mock_list_network.assert_called_once_with(names=[neutron_net_id])
|
||||
|
||||
def test_remove_network(self):
|
||||
network = mock.Mock(name='c02afe4e-8350-4263-8078')
|
||||
self.network_driver.remove_network(network)
|
||||
network.destroy.assert_called_once_with()
|
||||
|
||||
def test_inspect_network(self):
|
||||
network_name = 'c02afe4e-8350-4263-8078'
|
||||
expected = {'Name': 'c02afe4e-8350-4263-8078',
|
||||
'Options': {'neutron.net.uuid': '1234567'}}
|
||||
info = self.network_driver.inspect_network(network_name)
|
||||
self.assertEqual(expected, info)
|
||||
|
||||
def test_list_networks(self):
|
||||
expected = [{'Name': 'test_network'}]
|
||||
networks = self.network_driver.list_networks()
|
||||
self.assertEqual(expected, networks)
|
||||
|
||||
def test_connect_container_to_network(self):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
network_name = 'c02afe4e-8350-4263-8078'
|
||||
requested_net = {'ipv4_address': '10.5.0.22',
|
||||
'port': 'fake-port-id',
|
||||
'network': network_name,
|
||||
'preserve_on_delete': True}
|
||||
expected_address = [{'version': 4, 'addr': '10.5.0.22',
|
||||
'port': 'fake-port-id',
|
||||
@ -292,7 +278,7 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
with mock.patch.object(self.network_driver.docker,
|
||||
'connect_container_to_network') as mock_connect:
|
||||
address = self.network_driver.connect_container_to_network(
|
||||
container, network_name, requested_net)
|
||||
container, requested_net)
|
||||
|
||||
self.assertEqual(expected_address, address)
|
||||
mock_connect.assert_called_once_with(
|
||||
@ -306,6 +292,7 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
network_name = 'c02afe4e-8350-4263-8078'
|
||||
requested_net = {'ipv4_address': '10.5.0.22',
|
||||
'network': network_name,
|
||||
'port': 'fake-port-id',
|
||||
'preserve_on_delete': True}
|
||||
mock_neutron_api_cls.return_value = self.network_driver.neutron_api
|
||||
@ -317,7 +304,7 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
mock.Mock(side_effect=exception.DockerError)
|
||||
self.assertRaises(exception.DockerError,
|
||||
self.network_driver.connect_container_to_network,
|
||||
container, network_name, requested_net)
|
||||
container, requested_net)
|
||||
new_port = self.network_driver.neutron_api.list_ports(
|
||||
id='fake-port-id')['ports'][0]
|
||||
self.assertEqual('', new_port['device_id'])
|
||||
@ -327,7 +314,6 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
'preserve_on_delete': False}]}
|
||||
container = Container(self.context, **utils.get_test_container(
|
||||
addresses=addresses))
|
||||
network_name = 'c02afe4e-8350-4263-8078'
|
||||
ports = self.network_driver.neutron_api.list_ports(
|
||||
id='fake-port-id')['ports']
|
||||
self.assertEqual(1, len(ports))
|
||||
@ -335,9 +321,9 @@ class KuryrNetworkTestCase(base.TestCase):
|
||||
'disconnect_container_from_network'
|
||||
) as mock_disconnect:
|
||||
self.network_driver.disconnect_container_from_network(
|
||||
container, network_name, 'fake-net-id')
|
||||
container, 'fake-net-id')
|
||||
mock_disconnect.assert_called_once_with(
|
||||
container.container_id, network_name)
|
||||
container.container_id, 'fake-net-id')
|
||||
# assert the neutron port is deleted
|
||||
ports = self.network_driver.neutron_api.list_ports(
|
||||
id='fake-port-id')['ports']
|
||||
|
Loading…
Reference in New Issue
Block a user