merged trunk
This commit is contained in:
1
Authors
1
Authors
@@ -32,6 +32,7 @@ Jesse Andrews <anotherjesse@gmail.com>
|
|||||||
Joe Heck <heckj@mac.com>
|
Joe Heck <heckj@mac.com>
|
||||||
Joel Moore <joelbm24@gmail.com>
|
Joel Moore <joelbm24@gmail.com>
|
||||||
John Dewey <john@dewey.ws>
|
John Dewey <john@dewey.ws>
|
||||||
|
John Tran <jtran@attinteractive.com>
|
||||||
Jonathan Bryce <jbryce@jbryce.com>
|
Jonathan Bryce <jbryce@jbryce.com>
|
||||||
Jordan Rinke <jordan@openstack.org>
|
Jordan Rinke <jordan@openstack.org>
|
||||||
Josh Durgin <joshd@hq.newdream.net>
|
Josh Durgin <joshd@hq.newdream.net>
|
||||||
|
|||||||
11
CA/.gitignore
vendored
11
CA/.gitignore
vendored
@@ -1,11 +0,0 @@
|
|||||||
index.txt
|
|
||||||
index.txt.old
|
|
||||||
index.txt.attr
|
|
||||||
index.txt.attr.old
|
|
||||||
cacert.pem
|
|
||||||
serial
|
|
||||||
serial.old
|
|
||||||
openssl.cnf
|
|
||||||
private/*
|
|
||||||
newcerts/*
|
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# $1 is the id of the project and $2 is the subject of the cert
|
|
||||||
NAME=$1
|
|
||||||
SUBJ=$2
|
|
||||||
mkdir -p projects/$NAME
|
|
||||||
cd projects/$NAME
|
|
||||||
cp ../../openssl.cnf.tmpl openssl.cnf
|
|
||||||
sed -i -e s/%USERNAME%/$NAME/g openssl.cnf
|
|
||||||
mkdir certs crl newcerts private
|
|
||||||
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
|
|
||||||
echo "10" > serial
|
|
||||||
touch index.txt
|
|
||||||
# NOTE(vish): Disabling intermediate ca's because we don't actually need them.
|
|
||||||
# It makes more sense to have each project have its own root ca.
|
|
||||||
# openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
|
|
||||||
# openssl req -new -sha256 -key private/cakey.pem -out ../../reqs/inter$NAME.csr -batch -subj "$SUBJ"
|
|
||||||
openssl ca -gencrl -config ./openssl.cnf -out crl.pem
|
|
||||||
if [ "`id -u`" != "`grep nova /etc/passwd | cut -d':' -f3`" ]; then
|
|
||||||
sudo chown -R nova:nogroup .
|
|
||||||
fi
|
|
||||||
# cd ../../
|
|
||||||
# openssl ca -extensions v3_ca -days 365 -out INTER/$NAME/cacert.pem -in reqs/inter$NAME.csr -config openssl.cnf -batch
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
if [ -f "cacert.pem" ];
|
|
||||||
then
|
|
||||||
echo "Not installing, it's already done."
|
|
||||||
else
|
|
||||||
cp openssl.cnf.tmpl openssl.cnf
|
|
||||||
sed -i -e s/%USERNAME%/ROOT/g openssl.cnf
|
|
||||||
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
|
|
||||||
touch index.txt
|
|
||||||
echo "10" > serial
|
|
||||||
openssl ca -gencrl -config ./openssl.cnf -out crl.pem
|
|
||||||
fi
|
|
||||||
36
CA/genvpn.sh
36
CA/genvpn.sh
@@ -1,36 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# This gets zipped and run on the cloudpipe-managed OpenVPN server
|
|
||||||
NAME=$1
|
|
||||||
SUBJ=$2
|
|
||||||
|
|
||||||
mkdir -p projects/$NAME
|
|
||||||
cd projects/$NAME
|
|
||||||
|
|
||||||
# generate a server priv key
|
|
||||||
openssl genrsa -out server.key 2048
|
|
||||||
|
|
||||||
# generate a server CSR
|
|
||||||
openssl req -new -key server.key -out server.csr -batch -subj "$SUBJ"
|
|
||||||
|
|
||||||
novauid=`getent passwd nova | awk -F: '{print $3}'`
|
|
||||||
if [ ! -z "${novauid}" ] && [ "`id -u`" != "${novauid}" ]; then
|
|
||||||
sudo chown -R nova:nogroup .
|
|
||||||
fi
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
#
|
|
||||||
# OpenSSL configuration file.
|
|
||||||
#
|
|
||||||
|
|
||||||
# Establish working directory.
|
|
||||||
|
|
||||||
dir = .
|
|
||||||
|
|
||||||
[ ca ]
|
|
||||||
default_ca = CA_default
|
|
||||||
|
|
||||||
[ CA_default ]
|
|
||||||
serial = $dir/serial
|
|
||||||
database = $dir/index.txt
|
|
||||||
new_certs_dir = $dir/newcerts
|
|
||||||
certificate = $dir/cacert.pem
|
|
||||||
private_key = $dir/private/cakey.pem
|
|
||||||
unique_subject = no
|
|
||||||
default_crl_days = 365
|
|
||||||
default_days = 365
|
|
||||||
default_md = md5
|
|
||||||
preserve = no
|
|
||||||
email_in_dn = no
|
|
||||||
nameopt = default_ca
|
|
||||||
certopt = default_ca
|
|
||||||
policy = policy_match
|
|
||||||
|
|
||||||
[ policy_match ]
|
|
||||||
countryName = match
|
|
||||||
stateOrProvinceName = match
|
|
||||||
organizationName = optional
|
|
||||||
organizationalUnitName = optional
|
|
||||||
commonName = supplied
|
|
||||||
emailAddress = optional
|
|
||||||
|
|
||||||
|
|
||||||
[ req ]
|
|
||||||
default_bits = 1024 # Size of keys
|
|
||||||
default_keyfile = key.pem # name of generated keys
|
|
||||||
default_md = md5 # message digest algorithm
|
|
||||||
string_mask = nombstr # permitted characters
|
|
||||||
distinguished_name = req_distinguished_name
|
|
||||||
|
|
||||||
[ req_distinguished_name ]
|
|
||||||
# Variable name Prompt string
|
|
||||||
#---------------------- ----------------------------------
|
|
||||||
0.organizationName = Organization Name (company)
|
|
||||||
organizationalUnitName = Organizational Unit Name (department, division)
|
|
||||||
emailAddress = Email Address
|
|
||||||
emailAddress_max = 40
|
|
||||||
localityName = Locality Name (city, district)
|
|
||||||
stateOrProvinceName = State or Province Name (full name)
|
|
||||||
countryName = Country Name (2 letter code)
|
|
||||||
countryName_min = 2
|
|
||||||
countryName_max = 2
|
|
||||||
commonName = Common Name (hostname, IP, or your name)
|
|
||||||
commonName_max = 64
|
|
||||||
|
|
||||||
# Default values for the above, for consistency and less typing.
|
|
||||||
# Variable name Value
|
|
||||||
#------------------------------ ------------------------------
|
|
||||||
0.organizationName_default = NOVA %USERNAME%
|
|
||||||
localityName_default = Mountain View
|
|
||||||
stateOrProvinceName_default = California
|
|
||||||
countryName_default = US
|
|
||||||
|
|
||||||
[ v3_ca ]
|
|
||||||
basicConstraints = CA:TRUE
|
|
||||||
subjectKeyIdentifier = hash
|
|
||||||
authorityKeyIdentifier = keyid:always,issuer:always
|
|
||||||
|
|
||||||
[ v3_req ]
|
|
||||||
basicConstraints = CA:FALSE
|
|
||||||
subjectKeyIdentifier = hash
|
|
||||||
1
CA/projects/.gitignore
vendored
1
CA/projects/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
*
|
|
||||||
1
CA/reqs/.gitignore
vendored
1
CA/reqs/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
*
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
include HACKING LICENSE run_tests.py run_tests.sh
|
include HACKING LICENSE run_tests.py run_tests.sh
|
||||||
include README builddeb.sh exercise_rsapi.py
|
include README builddeb.sh exercise_rsapi.py
|
||||||
include ChangeLog MANIFEST.in pylintrc Authors
|
include ChangeLog MANIFEST.in pylintrc Authors
|
||||||
graft CA
|
graft nova/CA
|
||||||
graft doc
|
graft doc
|
||||||
graft smoketests
|
graft smoketests
|
||||||
graft tools
|
graft tools
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from nova.compute import power_state
|
|||||||
from nova.api.ec2 import cloud
|
from nova.api.ec2 import cloud
|
||||||
from nova.api.ec2 import ec2utils
|
from nova.api.ec2 import ec2utils
|
||||||
from nova.image import local
|
from nova.image import local
|
||||||
|
from nova.exception import NotFound
|
||||||
|
|
||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
@@ -71,7 +72,8 @@ class CloudTestCase(test.TestCase):
|
|||||||
host = self.network.get_network_host(self.context.elevated())
|
host = self.network.get_network_host(self.context.elevated())
|
||||||
|
|
||||||
def fake_show(meh, context, id):
|
def fake_show(meh, context, id):
|
||||||
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
|
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
|
||||||
|
'type': 'machine'}}
|
||||||
|
|
||||||
self.stubs.Set(local.LocalImageService, 'show', fake_show)
|
self.stubs.Set(local.LocalImageService, 'show', fake_show)
|
||||||
self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
|
self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
|
||||||
@@ -216,6 +218,35 @@ class CloudTestCase(test.TestCase):
|
|||||||
db.service_destroy(self.context, comp1['id'])
|
db.service_destroy(self.context, comp1['id'])
|
||||||
db.service_destroy(self.context, comp2['id'])
|
db.service_destroy(self.context, comp2['id'])
|
||||||
|
|
||||||
|
def test_describe_images(self):
|
||||||
|
describe_images = self.cloud.describe_images
|
||||||
|
|
||||||
|
def fake_detail(meh, context):
|
||||||
|
return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
|
||||||
|
'type': 'machine'}}]
|
||||||
|
|
||||||
|
def fake_show_none(meh, context, id):
|
||||||
|
raise NotFound
|
||||||
|
|
||||||
|
self.stubs.Set(local.LocalImageService, 'detail', fake_detail)
|
||||||
|
# list all
|
||||||
|
result1 = describe_images(self.context)
|
||||||
|
result1 = result1['imagesSet'][0]
|
||||||
|
self.assertEqual(result1['imageId'], 'ami-00000001')
|
||||||
|
# provided a valid image_id
|
||||||
|
result2 = describe_images(self.context, ['ami-00000001'])
|
||||||
|
self.assertEqual(1, len(result2['imagesSet']))
|
||||||
|
# provide more than 1 valid image_id
|
||||||
|
result3 = describe_images(self.context, ['ami-00000001',
|
||||||
|
'ami-00000002'])
|
||||||
|
self.assertEqual(2, len(result3['imagesSet']))
|
||||||
|
# provide an non-existing image_id
|
||||||
|
self.stubs.UnsetAll()
|
||||||
|
self.stubs.Set(local.LocalImageService, 'show', fake_show_none)
|
||||||
|
self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show_none)
|
||||||
|
self.assertRaises(NotFound, describe_images,
|
||||||
|
self.context, ['ami-fake'])
|
||||||
|
|
||||||
def test_console_output(self):
|
def test_console_output(self):
|
||||||
instance_type = FLAGS.default_instance_type
|
instance_type = FLAGS.default_instance_type
|
||||||
max_count = 1
|
max_count = 1
|
||||||
|
|||||||
29
nova/wsgi.py
29
nova/wsgi.py
@@ -355,24 +355,25 @@ class Controller(object):
|
|||||||
|
|
||||||
if type(result) is dict:
|
if type(result) is dict:
|
||||||
content_type = req.best_match_content_type()
|
content_type = req.best_match_content_type()
|
||||||
body = self._serialize(result, content_type)
|
default_xmlns = self.get_default_xmlns(req)
|
||||||
|
body = self._serialize(result, content_type, default_xmlns)
|
||||||
|
|
||||||
response = webob.Response()
|
response = webob.Response()
|
||||||
response.headers["Content-Type"] = content_type
|
response.headers["Content-Type"] = content_type
|
||||||
response.body = body
|
response.body = body
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _serialize(self, data, content_type):
|
def _serialize(self, data, content_type, default_xmlns):
|
||||||
"""
|
"""
|
||||||
Serialize the given dict to the provided content_type.
|
Serialize the given dict to the provided content_type.
|
||||||
Uses self._serialization_metadata if it exists, which is a dict mapping
|
Uses self._serialization_metadata if it exists, which is a dict mapping
|
||||||
MIME types to information needed to serialize to that type.
|
MIME types to information needed to serialize to that type.
|
||||||
"""
|
"""
|
||||||
_metadata = getattr(type(self), "_serialization_metadata", {})
|
_metadata = getattr(type(self), "_serialization_metadata", {})
|
||||||
serializer = Serializer(_metadata)
|
|
||||||
|
serializer = Serializer(_metadata, default_xmlns)
|
||||||
try:
|
try:
|
||||||
return serializer.serialize(data, content_type)
|
return serializer.serialize(data, content_type)
|
||||||
except exception.InvalidContentType:
|
except exception.InvalidContentType:
|
||||||
@@ -388,19 +389,24 @@ class Controller(object):
|
|||||||
serializer = Serializer(_metadata)
|
serializer = Serializer(_metadata)
|
||||||
return serializer.deserialize(data, content_type)
|
return serializer.deserialize(data, content_type)
|
||||||
|
|
||||||
|
def get_default_xmlns(self, req):
|
||||||
|
"""Provide the XML namespace to use if none is otherwise specified."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Serializer(object):
|
class Serializer(object):
|
||||||
"""
|
"""
|
||||||
Serializes and deserializes dictionaries to certain MIME types.
|
Serializes and deserializes dictionaries to certain MIME types.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, metadata=None):
|
def __init__(self, metadata=None, default_xmlns=None):
|
||||||
"""
|
"""
|
||||||
Create a serializer based on the given WSGI environment.
|
Create a serializer based on the given WSGI environment.
|
||||||
'metadata' is an optional dict mapping MIME types to information
|
'metadata' is an optional dict mapping MIME types to information
|
||||||
needed to serialize a dictionary to that type.
|
needed to serialize a dictionary to that type.
|
||||||
"""
|
"""
|
||||||
self.metadata = metadata or {}
|
self.metadata = metadata or {}
|
||||||
|
self.default_xmlns = default_xmlns
|
||||||
|
|
||||||
def _get_serialize_handler(self, content_type):
|
def _get_serialize_handler(self, content_type):
|
||||||
handlers = {
|
handlers = {
|
||||||
@@ -478,11 +484,23 @@ class Serializer(object):
|
|||||||
root_key = data.keys()[0]
|
root_key = data.keys()[0]
|
||||||
doc = minidom.Document()
|
doc = minidom.Document()
|
||||||
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
|
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
|
||||||
|
|
||||||
|
xmlns = node.getAttribute('xmlns')
|
||||||
|
if not xmlns and self.default_xmlns:
|
||||||
|
node.setAttribute('xmlns', self.default_xmlns)
|
||||||
|
|
||||||
return node.toprettyxml(indent=' ')
|
return node.toprettyxml(indent=' ')
|
||||||
|
|
||||||
def _to_xml_node(self, doc, metadata, nodename, data):
|
def _to_xml_node(self, doc, metadata, nodename, data):
|
||||||
"""Recursive method to convert data members to XML nodes."""
|
"""Recursive method to convert data members to XML nodes."""
|
||||||
result = doc.createElement(nodename)
|
result = doc.createElement(nodename)
|
||||||
|
|
||||||
|
# Set the xml namespace if one is specified
|
||||||
|
# TODO(justinsb): We could also use prefixes on the keys
|
||||||
|
xmlns = metadata.get('xmlns', None)
|
||||||
|
if xmlns:
|
||||||
|
result.setAttribute('xmlns', xmlns)
|
||||||
|
|
||||||
if type(data) is list:
|
if type(data) is list:
|
||||||
singular = metadata.get('plurals', {}).get(nodename, None)
|
singular = metadata.get('plurals', {}).get(nodename, None)
|
||||||
if singular is None:
|
if singular is None:
|
||||||
@@ -532,6 +550,7 @@ def paste_config_file(basename):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
configfiles = [basename,
|
configfiles = [basename,
|
||||||
|
os.path.join(FLAGS.state_path, 'etc', 'nova', basename),
|
||||||
os.path.join(FLAGS.state_path, 'etc', basename),
|
os.path.join(FLAGS.state_path, 'etc', basename),
|
||||||
os.path.join(FLAGS.state_path, basename),
|
os.path.join(FLAGS.state_path, basename),
|
||||||
'/etc/nova/%s' % basename]
|
'/etc/nova/%s' % basename]
|
||||||
|
|||||||
15
setup.py
15
setup.py
@@ -16,6 +16,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -86,6 +87,19 @@ try:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def find_data_files(destdir, srcdir):
|
||||||
|
package_data = []
|
||||||
|
files = []
|
||||||
|
for d in glob.glob('%s/*' % (srcdir, )):
|
||||||
|
if os.path.isdir(d):
|
||||||
|
package_data += find_data_files(
|
||||||
|
os.path.join(destdir, os.path.basename(d)), d)
|
||||||
|
else:
|
||||||
|
files += [d]
|
||||||
|
package_data += [(destdir, files)]
|
||||||
|
return package_data
|
||||||
|
|
||||||
DistUtilsExtra.auto.setup(name='nova',
|
DistUtilsExtra.auto.setup(name='nova',
|
||||||
version=version.canonical_version_string(),
|
version=version.canonical_version_string(),
|
||||||
description='cloud computing fabric controller',
|
description='cloud computing fabric controller',
|
||||||
@@ -96,6 +110,7 @@ DistUtilsExtra.auto.setup(name='nova',
|
|||||||
packages=find_packages(exclude=['bin', 'smoketests']),
|
packages=find_packages(exclude=['bin', 'smoketests']),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
test_suite='nose.collector',
|
test_suite='nose.collector',
|
||||||
|
data_files=find_data_files('share/nova', 'tools'),
|
||||||
scripts=['bin/nova-ajax-console-proxy',
|
scripts=['bin/nova-ajax-console-proxy',
|
||||||
'bin/nova-api',
|
'bin/nova-api',
|
||||||
'bin/nova-compute',
|
'bin/nova-compute',
|
||||||
|
|||||||
Reference in New Issue
Block a user