diff options
90 files changed, 3780 insertions, 8832 deletions
diff --git a/.functests b/.functests new file mode 100755 index 0000000..6b13b69 --- /dev/null +++ b/.functests @@ -0,0 +1,35 @@ +#!/bin/bash + +# Copyright (c) 2013 Red Hat, Inc. +# +# 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 program expects to be run by tox in a virtual python environment +# so that it does not pollute the host development system + +EXIT_STATUS=0 + +# Run functional tests with tempauth as auth middleware +bash tools/tempauth_functional_tests.sh || EXIT_STATUS=$? + +# Run functional tests with gswauth as auth middleware +bash tools/gswauth_functional_tests.sh || EXIT_STATUS=$? + +# Run object expirer functional tests if gsexpiring volume is found. +if mount | grep "gsexpiring on /mnt/gluster-object/gsexpiring type fuse.glusterfs"; then + echo "Running object expirer functional tests" + bash tools/object_expirer_functional.sh || EXIT_STATUS=$? +fi + +exit $EXIT_STATUS @@ -1,9 +1,13 @@ -/.tox -gluster_swift.egg-info -/test/unit/.coverage -/test/unit/nosetests.xml -/test/unit/coverage.xml -/test/unit/cover -/build -/swift -*.pyc +*.py[co] +*.sw? +*~ +dist +build +cover +functional_tests_result +.coverage +*.egg +*.egg-info +.tox +pycscope.* +cscope.* @@ -1,2 +1,2 @@ [gerrit] -defaultbranch=master +defaultbranch=icehouse diff --git a/unittests.sh b/.unittests index 940ada7..fcd066a 100755 --- a/unittests.sh +++ b/.unittests @@ -15,10 +15,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +TOP_DIR=$(python -c "import os; print os.path.dirname(os.path.realpath('$0'))") -cd $(dirname $0)/test/unit -nosetests -v --exe --with-coverage --cover-package gluster --cover-erase --cover-html --cover-branches $@ - -saved_status=$? +python -c 'from distutils.version import LooseVersion as Ver; import nose, sys; sys.exit(0 if Ver(nose.__version__) >= Ver("1.2.0") else 1)' +if [ $? != 0 ]; then + cover_branches="" +else + # Having the HTML reports is REALLY useful for achieving 100% branch + # coverage. + cover_branches="--cover-branches --cover-html --cover-html-dir=$TOP_DIR/cover" +fi +cd $TOP_DIR/test/unit +nosetests -v --exe --with-coverage --cover-package gluster. --cover-erase $cover_branches $@ +rvalue=$? rm -f .coverage -exit $saved_status +cd - +exit $rvalue @@ -1,9 +1,3 @@ -# Gluster-Swift has moved to Swift-On-File -The Gluster-Swift project is being moved to a new project named [Swift-On-File][]. -This new name better represents its nature as a Swift backend that supports -multiple Posix Filesystems, not just GlusterFS. The last released version of -Gluster-Swift was the [Icehouse release][]. - -[Swift-On-File][https://github.com/swiftonfile/swiftonfile] -[Icehouse release][https://github.com/swiftonfile/swiftonfile/releases/tag/v1.13.1-2] - +# Gluster-Swift +Gluster-Swift enables files and directories created on GlusterFS +to be accessed as objects via the Swift API. diff --git a/bin/gluster-swift-gen-builders b/bin/gluster-swift-gen-builders index 2e5fe1f..4cf757d 100755 --- a/bin/gluster-swift-gen-builders +++ b/bin/gluster-swift-gen-builders @@ -68,7 +68,10 @@ if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then fi -cd /etc/swift +if ! cd "/etc/swift"; then + echo "The path /etc/swift not accessible. Please check if it exists." + exit 1 +fi for builder_file in $builder_files do diff --git a/doc/markdown/auth_guide.md b/doc/markdown/auth_guide.md index 274dd32..86c3650 100644 --- a/doc/markdown/auth_guide.md +++ b/doc/markdown/auth_guide.md @@ -12,13 +12,6 @@ * [User roles](#gswauth_user_roles) * [GSwauth Tools](#gswauth_tools) * [Authenticating a user](#gswauth_authenticate) -* [Swiftkerbauth](#swiftkerbauth) - * [Architecture](swiftkerbauth/architecture.md) - * [RHEL IPA Server Guide](swiftkerbauth/ipa_server.md) - * [RHEL IPA Client Guide](swiftkerbauth/ipa_client.md) - * [Windows AD Server Guide](swiftkerbauth/AD_server.md) - * [Windows AD Client Guide](swiftkerbauth/AD_client.md) - * [Swiftkerbauth Guide](swiftkerbauth/swiftkerbauth_guide.md) ## <a name="keystone" />Keystone ## The Standard Openstack authentication service @@ -468,12 +461,3 @@ bash-4.2$ swift --os-auth-token=AUTH_tk7e68ef4698f14c7f95af07ab7b298610 --os-sto README.md ~~~ **Note:** Reseller admins must always use the second method to acquire a token, in order to be given access to other accounts different than his own. The first method of using the username and password will give them access only to their own accounts. - -## <a name="swiftkerbauth" />Swiftkerbauth ## -Kerberos authentication filter - -Carsten Clasohm implemented a new authentication filter for swift -that uses Kerberos tickets for single sign on authentication, and -grants administrator permissions based on the users group membership -in a directory service like Red Hat Enterprise Linux Identity Management -or Microsoft Active Directory. diff --git a/doc/markdown/swiftkerbauth/AD_client.md b/doc/markdown/swiftkerbauth/AD_client.md deleted file mode 100644 index 0947a1e..0000000 --- a/doc/markdown/swiftkerbauth/AD_client.md +++ /dev/null @@ -1,206 +0,0 @@ -#AD client setup guide - -###Contents -* [Setup Overview] (#setup) -* [Configure Network] (#network) -* [Installing AD Client] (#AD-client) - -<a name="setup" /> -###Setup Overview - -This guide talks about adding fedora linux client to windows domain. -The test setup included a client machine with Fedora 19 installed -on it with all the latest packages updated. The crux is to add this linux -machine to Windows Domain. This linux box is expected to act as RHS node and on which swiftkerbauth, -apachekerbauth code would run. - -Set hostname (FQDN) to fcclient.winad.com - - # hostnamectl set-hostname "fcclient.winad.com" - - # hostname "fcclient.winad.com" - - -<a name="network" /> -### Configure client - -* Deploy Fedora linux 19. - -* Update the system with latest packages. - -* Configure SELinux security parameters. - -* Install & configure samba - -* Configure DNS - -* Synchronize the time services - -* Join Domain - -* Install / Configure Kerberos Client - - -The document assumes the installing Fedora Linux and configuring SELinux -parameters to 'permissive' is known already. - -###Install & Configure Samba: - # yum -y install samba samba-client samba-common samba-winbind - samba-winbind-clients - - # service start smb - - # ps -aef | grep smb - # chkconfig smb on - -###Synchronize time services -The kerberos authentication and most of the DNS functionality could fail with -clock skew if times are not synchronized. - - # cat /etc/ntp.conf - server ns1.bos.redhat.com - server 10.5.26.10 - - # service ntpd stop - - # ntpdate 10.16.255.2 - - # service ntpd start - - #chkconfig ntpd on - -Check if Windows server in the whole environment is also time synchronized with -same source. - - # C:\Users\Administrator>w32tm /query /status | find "Source" - - Source: ns1.xxx.xxx.com - -###Configure DNS on client -Improperly resolved hostname is the leading cause in authentication failures. -Best practice is to configure fedora client to use Windows DNS. -'nameserver' below is the IP address of the windows server. - # cat /etc/resolve.conf - domain server.winad.com - search server.winad.com - nameserver 10.nn.nnn.3 - -###Set the hostname of the client properly (FQDN) - # cat /etc/sysconfig/network - HOSTNAME=fcclient.winad.com - - -###Install & Configure kerberos client - - # yum -y install krb5-workstation - -Edit the /etc/krb5.conf as follows: - - # cat /etc/krb5.conf - [logging] - default = FILE:/var/log/krb5libs.log - kdc = FILE:/var/log/krb5kdc.log - admin_server = FILE:/var/log/kadmind.log - - [libdefaults] - default_realm = WINAD.COM - dns_lookup_realm = false - dns_lookup_kdc = false - ticket_lifetime = 24h - renew_lifetime = 7d - forwardable = true - - [realms] - WINAD.COM = { - kdc = server.winad.com - admin_server = server.winad.com - } - [domain_realm] - .demo = server.winad.com - demo = server.winad.com - -###Join Domain -Fire command 'system-config-authentication' on client. This should display a -graphical wizard. Below inputs would help configure this wizard. - - - User account data base = winbind - - winbind domain = winad - - security model = ads - - winbind ads realm = winad.com - - winbind controller = server.winad.com - - template shell = /bin/bash - - let the other options be as is to default. - - Perform Join domain and appy settings and quit. Please note this join should - not see any errors. This makes the client fedora box to join the windows - domain. - -###Configure the kerberos client -This would bring the users/groups from Windows Active directory to this -fedora client. - -Edit /etc/samba/smb.conf file to have below parameters in the global section. - - # cat /etc/samba/smb.conf - [global] - workgroup = winad - realm = winad.com - server string = Samba Server Version %v - security = ADS - allow trusted domains = No - password server = server.winad.com - log file = /var/log/samba/log.%m - max log size = 50 - idmap uid = 10000Â19999 - idmap gid = 10000Â19999 - template shell = /bin/bash - winbind separator = + - winbind use default domain = Yes - idmap config REFARCHÂAD:range = 10000000Â19999999 - idmap config REFARCHÂAD:backend = rid - cups options = raw - - - # service smb stop - - # service winbind stop - - # tar -cvf /var/tmp/samba-cache-backup.tar /var/lib/samba - - # ls -la /var/tmp/samba-cache-backup.tar - - # rm Â-f /var/lib/samba/* - - -Verify that no kerberos ticket available and cached. - - # kdestroy - - # klist - -Rejoin the domain. - - # net join -S server -U Administrstor - -Test that client rejoined the domain. - - # net ads info - -Restart smb and winbind service. - - # wbinfo --domain-users - -Perform kinit for the domain users prepared on active directory. This is obtain -the kerberos ticket for user 'auth_admin' - - # kinit auth_admin - - # id -Gn auth_admin - -###Notes -Obtaining the HTTP service principal & keytab file and installing it with -swiftkerbauth is added to swiftkerbauth_guide - -###References -Reference Document for adding Linux box to windows domain : -Integrating Red Hat Enterprise Linux 6 -with Active Directory diff --git a/doc/markdown/swiftkerbauth/AD_server.md b/doc/markdown/swiftkerbauth/AD_server.md deleted file mode 100644 index 66d90f2..0000000 --- a/doc/markdown/swiftkerbauth/AD_server.md +++ /dev/null @@ -1,119 +0,0 @@ -#Windows Active Directory & Domain Controller Server Guide - -###Contents -* [Setup Overview] (#Setup) -* [Installing Active Directory Services] (#AD-server) -* [Configuring DNS] (#DNS) -* [Adding Users and Groups] (#users-groups) - - -<a name="Setup" /> -###Setup Overview - -The setup includes a server machine installed with Windows 2008 R2 Server, with -Domain Controller, Active Directory services & DNS server installed alongwith. -The steps to install windows operating system and above servers can be found -on MicroSoft Documentation. This windows Active Directory server would act as an -authentication server in the whole setup. This would provide the access control -and permissions for users on certain data objects. - - -Windows 2008 R2 deployment: - -http://technet.microsoft.com/en-us/library/dd283085.aspx - - -Configuring Active Directory, Domain Services, DNS server: - -http://technet.microsoft.com/en-us/library/cc770946.aspx - - -<a name="AD-server" /> -###Installing AD Server - -Administrators need to follow simple instructions in Server Manager on Windows -2008, and should add Active Directory Domain Services & DNS server. It is -recommended to use static IP for DNS server. Preferred Hostname(FQDN) for -Windows server could be of format hostname 'server.winad.com' where -'winad.com' is a domain name. - -Following tips would help prepare a test setup neatly. - - - Select Active Directory Domain services wizard in Server Manager - - Move on to install it with all the pre-requisits, e.g. .NET framework etc. - - Configure Active directory after installtion via exapanding the 'Roles' - section in the server manager. - - Create a new Domain in the New Forest. - - Type the FQDN, winad.com - - Set Forest functional level Windows 2008 R2. - - Selct additional options for this domain controller as DNS server. - - Leave the log locations to default provided by wizard. - - Set the Administrator Password carefully. - - Thats it. You are done configuring active directory. - - -<a name="dns" /> -###Configuring DNS - -This section explains configuring the DNS server installed on Windows 2008 R2 -server. You must know know about - - - Forward lookup zone - - - Reverse lookup zone - - - Zone type - -A forward lookup zone is simply a way to resolve hostnames to IP address. -A reverse lookup zone is to lookup DNS hostname of the host IP. - -Following tips would help configure the Zones on DNS server. - - - Create a Forward lookup zone. - - Create it a primary zone. - - Add the Clients using their ip addresses and FQDN to this forward lookup - zones. - - This would add type 'A' record for that host on DNS server. - - Similarly create a Reverser lookup zone. - - Add clients 'PTR' record to this zone via browsing through the forward - zones clients. - -The above setup can be tested on client once it joins the domain using 'dig' -command as mentioned below. - - -On client: - - # dig fcclient.winad.com - This should yield you a Answer section mentioning its IP address. - - Reverse lookup can be tested using - - # 'dig -t ptr 101.56.168.192.in-addr.arpa.' - The answer section should state the FQDN of the client. - - Repeat the above steps on client for Windows AD server as well. - - -<a name="users-groups" /> -###Adding users and groups - -The following convention is to be followed in creating group names: - - <reseller-prefix>\_<volume-name> - - <reseller-prefix>\_<account-name> - -As of now, account=volume=group - -For example: - - AUTH\_test - -Adding groups and users to the Windows domain is easy task. - - - Start -> Administrative Tools -> Active Directory Users & Computers - - Expand the domain name which was prepared earlier. e.g winad.com - - Add groups with appropreate access rights. - - Add users to the group with appropreate permissions. - - Make sure you set password for users prepared on AD server. diff --git a/doc/markdown/swiftkerbauth/architecture.md b/doc/markdown/swiftkerbauth/architecture.md deleted file mode 100644 index fc6d764..0000000 --- a/doc/markdown/swiftkerbauth/architecture.md +++ /dev/null @@ -1,105 +0,0 @@ -# Architecture - -The Swift API is HTTP-based. As described in the Swift documentation -[1], clients first make a request to an authentication URL, providing -a username and password. The reply contains a token which is used in -all subsequent requests. - -Swift has a chain of filters through which all client requests go. The -filters to use are configured with the pipeline parameter in -/etc/swift/proxy-server.conf: - - [pipeline:main] - pipeline = healthcheck cache tempauth proxy-server - -For the single sign authentication, we added a new filter called -"kerbauth" and put it into the filter pipeline in place of tempauth. - -The filter checks the URL for each client request. If it matches the -authentication URL, the client is redirected to a URL on a different -server (on the same machine). The URL is handled by a CGI script, which -is set up to authenticate the client with Kerberos negotiation, retrieve -the user's system groups [2], store them in a memcache ring shared with -the Swift server, and return the authentication token to the client. - -When the client provides the token as part of a resource request, the -kerbauth filter checks it against its memcache, grants administrator -rights based on the group membership retrieved from memcache, and -either grants or denies the resource access. - -[1] http://docs.openstack.org/api/openstack-object-storage/1.0/content/authentication-object-dev-guide.html - -[2] The user data and system groups are usually provided by Red Hat - Enterprise Linux identity Management or Microsoft Active - Directory. The script relies on the system configuration to be set - accordingly (/etc/nsswitch.conf). - -***** - -## kerbauth.py - -The script kerbauth.py began as a copy of the tempauth.py script from -from tempauth middleware. It contains the following modifications, among -others: - -In the __init__ method, we read the ext_authentication_url parameter -from /etc/swift/proxy-server.conf. This is the URL that clients are -redirected to when they access either the Swift authentication URL, or -when they request a resource without a valid authentication token. - -The configuration in proxy-server.conf looks like this: - - [filter:kerbauth] - use = egg:swiftkerbauth#kerbauth - ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth - -The authorize method was changed so that global administrator rights -are granted if the user is a member of the auth_reseller_admin -group. Administrator rights for a specific account like vol1 are -granted if the user is a member of the auth_vol1 group. [3] - -The denied_response method was changed to return a HTTP redirect to -the external authentication URL if no valid token was provided by the -client. - -Most of the handle_get_token method was moved to the external -authentication script. This method now returns a HTTP redirect. - -In the __call__ and get_groups method, we removed support for the -HTTP_AUTHORIZATION header, which is only needed when Amazon S3 is -used. - -Like tempauth.py, kerbauth.py uses a Swift wrapper to access -memcache. This wrapper converts the key to an MD5 hash and uses the -hash value to determine on which of a pre-defined list of servers to -store the data. - -[3] "auth" is the default reseller prefix, and would be different if - the reseller_prefix parameter in proxy-server.conf was set. - -## swift-auth CGI script - -swift-auth resides on an Apache server and assumes that Apache is -configured to authenticate the user before this script is -executed. The script retrieves the username from the REMOTE_USER -environment variable, and checks if there already is a token for this -user in the memcache ring. If not, it generates a new one, retrieves -the user's system groups with "id -Gn USERNAME", stores this -information in the memcache ring, and returns the token to the client. - -To allow the CGI script to connect to memcache, the SELinux booleans -httpd_can_network_connect and httpd_can_network_memcache had to be -set. - -The tempauth filter uses the uuid module to generate token -strings. This module creates and runs temporary files, which leads to -AVC denial messages in /var/log/audit/audit.log when used from an -Apache CGI script. While the module still works, the audit log would -grow quickly. Instead of writing an SELinux policy module to allow or -to silently ignore these accesses, the swift-auth script uses the -"random" module for generating token strings. - -Red Hat Enterprise Linux 6 comes with Python 2.6 which only provides -method to list the locally defined user groups. To include groups from -Red Hat Enterprise Linux Identity Management and in the future from -Active Directory, the "id" command is run in a subprocess. diff --git a/doc/markdown/swiftkerbauth/ipa_client.md b/doc/markdown/swiftkerbauth/ipa_client.md deleted file mode 100644 index f6afc42..0000000 --- a/doc/markdown/swiftkerbauth/ipa_client.md +++ /dev/null @@ -1,80 +0,0 @@ -#IPA Client Guide - -##Contents -* [Setup Overview] (#setup) -* [Configure Network] (#network) -* [Installing IPA Client] (#ipa-client) - -<a name="setup" /> -##Setup Overview -We have used a F18 box as IPA client machine and used FreeIPA client. -This document borrows instructions from the following more detailed guide. -[RHEL 6 Identity Management Guide][] - - -<a name="network" /> -## Configure network - -Set hostname (FQDN) to client.rhelbox.com -> hostnamectl set-hostname "client.rhelbox.com" -> -> hostname "client.rhelbox.com" - -Add following to /etc/sysconfig/network: - - HOSTNAME=client.rhelbox.com - -Add the following to /etc/hostname - - client.rhelbox.com - -Add the following to /etc/hosts - - 192.168.56.110 server.rhelbox.com server - 192.168.56.101 client.rhelbox.com client - -Logout and login again and verify hostname : -> hostname --fqdn - -Edit */etc/resolv.conf* to add this at beginning of file - - nameserver 192.168.56.110 - -Warning: NetworkManager changes resolv.conf on restart - -Turn off firewall -> service iptables stop -> -> chkconfig iptables off - -<a name="ipa-client" /> -## Installing IPA Client - -Install IPA client packages: - -For RHEL: -> yum install ipa-client ipa-admintools - -For Fedora: -> yum install freeipa-client freeipa-admintools - -Install IPA client and add to domain: ->ipa-client-install --enable-dns-updates - - Discovery was successful! - Hostname: client.rhelbox.com - Realm: RHELBOX.COM - DNS Domain: rhelbox.com - IPA Server: server.rhelbox.com - BaseDN: dc=rhelbox,dc=com - - Continue to configure the system with these values? [no]: yes - User authorized to enroll computers: admin - -Check if client is configured correctly: -> kinit admin -> -> getent passwd admin - - -[RHEL 6 Identity Management Guide]: https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Identity_Management_Guide/ diff --git a/doc/markdown/swiftkerbauth/ipa_server.md b/doc/markdown/swiftkerbauth/ipa_server.md deleted file mode 100644 index 55e654e..0000000 --- a/doc/markdown/swiftkerbauth/ipa_server.md +++ /dev/null @@ -1,146 +0,0 @@ -#IPA Server Guide - -##Contents -* [Setup Overview] (#setup) -* [Configure Network] (#network) -* [Installing IPA Server] (#ipa-server) -* [Configuring DNS] (#dns) -* [Adding Users and Groups] (#users-groups) - - -<a name="setup" /> -##Setup Overview -We have used a RHEL 6.4 box as IPA and DNS server. This document borrows -instructions from the following more detailed guide. -[RHEL 6 Identity Management Guide][] - - -<a name="network" /> -## Configure network - -Change hostname (FQDN) to server.rhelbox.com -> hostname "server.rhelbox.com" - -Add following to */etc/sysconfig/network* file - - HOSTNAME=server.rhelbox.com - -Add the following to */etc/hosts* file - - 192.168.56.110 server.rhelbox.com server - 192.168.56.101 client.rhelbox.com client - -Logout and login again and verify new hostname -> hostname --fqdn - -Turn off firewall -> service iptables stop -> -> chkconfig iptables off - - -<a name="ipa-server" /> -## Installing IPA Server - -Install IPA server packages and DNS dependencies -> yum install ipa-server bind bind-dyndb-ldap - -Run the following interactive setup to install IPA server with DNS -> ipa-server-install --setup-dns - - The IPA Master Server will be configured with: - Hostname: server.rhelbox.com - IP address: 192.168.56.110 - Domain name: rhelbox.com - Realm name: RHELBOX.COM - - BIND DNS server will be configured to serve IPA domain with: - Forwarders: No forwarders - Reverse zone: 56.168.192.in-addr.arpa. - -The installation may take some time. - -Check if IPA is installed correctly : -> kinit admin -> -> ipa user-find admin - - -<a name="dns" /> -## Configuring DNS - -Edit */etc/resolv.conf* to add this at beginning of file : - - nameserver 192.168.56.110 - -Warning: NetworkManager changes resolv.conf on restart - -Add a DNS A record and PTR record for the client under rhelbox.com zone -> ipa dnsrecord-add rhelbox.com client --a-rec=192.168.56.101 --a-create-reverse - -Check if DNS resolution is working by running : - -> dig server.rhelbox.com - - ;; ANSWER SECTION: - server.rhelbox.com. 1200 IN A 192.168.56.110 - -> dig client.rhelbox.com - - ;; ANSWER SECTION: - client.rhelbox.com. 86400 IN A 192.168.56.101 - -Check if reverse resolution works : - -> dig -t ptr 101.56.168.192.in-addr.arpa. - - ;; ANSWER SECTION: - 101.56.168.192.in-addr.arpa. 86400 IN PTR client.rhelbox.com. - - -> dig -t ptr 110.56.168.192.in-addr.arpa. - - ;; ANSWER SECTION: - 110.56.168.192.in-addr.arpa. 86400 IN PTR server.rhelbox.com. - - -<a name="users-groups" /> -## Adding users and groups - -The following convention is to be followed in creating group names: - - <reseller-prefix>\_<volume-name> - - <reseller-prefix>\_<account-name> - -As of now, account=volume=group - -For example: - - AUTH\_test - -Create *auth_reseller_admin* user group -> ipa group-add auth_reseller_admin --desc="Full access to all Swift accounts" - -Create *auth_rhs_test* user group -> ipa group-add auth_rhs_test --desc="Full access to rhs_test account" - -Create user *auth_admin* user as member of *auth_reseller_admin* user group -> ipa user-add auth_admin --first=Auth --last=Admin --password -> -> ipa group-add-member auth_reseller_admin --users=auth_admin - -Create user *rhs_test_admin* as member of *auth_rhs_test* user group -> ipa user-add rhs_test_admin --first=RHS --last=Admin --password -> -> ipa group-add-member auth_rhs_test --users=rhs_test_admin - -Create user *jsmith* with no relevant group membership -> ipa user-add rhs_test_admin --first=RHS --last=Admin --password - -You can verify users have been added by running ->ipa user-find admin - -NOTE: Every user has to change password on first login. - -[RHEL 6 Identity Management Guide]: https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Identity_Management_Guide/ diff --git a/doc/markdown/swiftkerbauth/swiftkerbauth_guide.md b/doc/markdown/swiftkerbauth/swiftkerbauth_guide.md deleted file mode 100644 index 5da1827..0000000 --- a/doc/markdown/swiftkerbauth/swiftkerbauth_guide.md +++ /dev/null @@ -1,517 +0,0 @@ -#swiftkerbauth - -* [Installing Kerberos module for Apache] (#httpd-kerb-install) -* [Creating HTTP Service Principal] (#http-principal) -* [Installing and configuring swiftkerbauth] (#install-swiftkerbauth) -* [Using swiftkerbauth] (#use-swiftkerbauth) -* [Configurable Parameters] (#config-swiftkerbauth) -* [Functional tests] (#swfunctest) - -<a name="httpd-kerb-install" /> -## Installing Kerberos module for Apache on IPA client - -Install httpd server with kerberos module: -> yum install httpd mod_auth_kerb -> -> service httpd restart - -Check if auth_kerb_module is loaded : -> httpd -M | grep kerb - -Change httpd log level to debug by adding/changing the following in -*/etc/httpd/conf/httpd.conf* file - - LogLevel debug - -httpd logs are at */var/log/httpd/error_log* for troubleshooting - -If SELinux is enabled, allow Apache to connect to memcache and -activate the changes by running ->setsebool -P httpd_can_network_connect 1 -> ->setsebool -P httpd_can_network_memcache 1 - -***** - -<a name="http-principal" /> -## Creating HTTP Service Principal on IPA server - -Add a HTTP Kerberos service principal : -> ipa service-add HTTP/client.rhelbox.com@RHELBOX.COM - -Retrieve the HTTP service principal to a keytab file: -> ipa-getkeytab -s server.rhelbox.com -p HTTP/client.rhelbox.com@RHELBOX.COM -k /tmp/http.keytab - -Copy keytab file to client: -> scp /tmp/http.keytab root@192.168.56.101:/etc/httpd/conf/http.keytab - -## Creating HTTP Service Principal on Windows AD server - -Add a HTTP Kerberos service principal: -> c:\>ktpass.exe -princ HTTP/fcclient.winad.com@WINAD.COM -mapuser -> auth_admin@WINAD.COM -pass Redhat*123 -out c:\HTTP.keytab -crypto DES-CBC-CRC -> -kvno 0 - -Use winscp to copy HTTP.ketab file to /etc/httpd/conf/http.keytab - -***** - -<a name="install-swiftkerbauth" /> -##Installing and configuring swiftkerbauth on IPA client - -Prerequisites for installing swiftkerbauth -* swift (havana) -* gluster-swift (optional) - -You can install swiftkerbauth using one of these three ways: - -Installing swiftkerbauth from source: -> python setup.py install - -Installing swiftkerbauth using pip: -> pip install swiftkerbauth - -Installing swiftkerbauth from RPMs: -> ./makerpm.sh -> -> rpm -ivh dist/swiftkerbauth-1.0.0-1.noarch.rpm - -Edit */etc/httpd/conf.d/swift-auth.conf* and change KrbServiceName, KrbAuthRealms and Krb5KeyTab parameters accordingly. -More detail on configuring kerberos for apache can be found at: -[auth_kerb_module Configuration][] - -Make /etc/httpd/conf/http.keytab readable by any user : -> chmod 644 /etc/httpd/conf/http.keytab - -And preferably change owner of keytab file to apache : -> chown apache:apache /etc/httpd/conf/http.keytab - -Reload httpd -> service httpd reload - -Make authentication script executable: -> chmod +x /var/www/cgi-bin/swift-auth - -***** - -<a name="#use-swiftkerbauth" /> -##Using swiftkerbauth - -### Adding kerbauth filter in swift pipeline - -Edit */etc/swift/proxy-server.conf* and add a new filter section as follows: - - [filter:kerbauth] - use = egg:swiftkerbauth#kerbauth - ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth - auth_mode=passive - -Add kerbauth to pipeline - - [pipeline:main] - pipeline = catch_errors healthcheck proxy-logging cache proxy-logging kerbauth proxy-server - -If the Swift server is not one of your Gluster nodes, edit -*/etc/swift/fs.conf* and change the following lines in the DEFAULT -section: - - mount_ip = RHS_NODE_HOSTNAME - remote_cluster = yes - -Restart swift to activate kerbauth filer -> swift-init main restart - - -###Examples - -####Authenticate user and get Kerberos ticket - -> kinit auth_admin - -NOTE: curl ignores user specified in -u option. All further curl commands -will use the currently authenticated auth_admin user. - -####Get an authentication token: -> curl -v -u : --negotiate --location-trusted http://client.rhelbox.com:8080/auth/v1.0 - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > GET /auth/v1.0 HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > - < HTTP/1.1 303 See Other - < Content-Type: text/html; charset=UTF-8 - < Location: http://client.rhelbox.com/cgi-bin/swift-auth - < Content-Length: 0 - < X-Trans-Id: txecd415aae89b4320b6145-0052417ea5 - < Date: Tue, 24 Sep 2013 11:59:33 GMT - < - * Connection #0 to host client.rhelbox.com left intact - * Issue another request to this URL: 'http://client.rhelbox.com/cgi-bin/swift-auth' - * About to connect() to client.rhelbox.com port 80 (#1) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 80 (#1) - > GET /cgi-bin/swift-auth HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com - > Accept: */* - > - < HTTP/1.1 401 Unauthorized - < Date: Tue, 24 Sep 2013 11:59:33 GMT - < Server: Apache/2.4.6 (Fedora) mod_auth_kerb/5.4 - < WWW-Authenticate: Negotiate - < WWW-Authenticate: Basic realm="Swift Authentication" - < Content-Length: 381 - < Content-Type: text/html; charset=iso-8859-1 - < - * Ignoring the response-body - * Connection #1 to host client.rhelbox.com left intact - * Issue another request to this URL: 'http://client.rhelbox.com/cgi-bin/swift-auth' - * Re-using existing connection! (#1) with host (nil) - * Connected to (nil) (192.168.56.101) port 80 (#1) - * Server auth using GSS-Negotiate with user '' - > GET /cgi-bin/swift-auth HTTP/1.1 - > Authorization: Negotiate YIICYgYJKoZIhvcSAQICAQBuggJRMIICTaADAgEFoQMCAQ6iBwMFACAAAACjggFgYYIBXDCCAVigAwIBBaENGwtSSEVMQk9YLkNPTaIlMCOgAwIBA6EcMBobBEhUVFAbEmNsaWVudC5yaGVsYm94LmNvbaOCARkwggEVoAMCARKhAwIBAaKCAQcEggEDx9SH2R90RO4eAkhsNKow/DYfjv1rWhgxNRqj/My3yslASSgefls48VdDNHVVWqr1Kd6mB/9BIoumpA+of+KSAg2QfPtcWiVFj5n5Fa8fyCHyQPvV8c92KzUdrBPc8OVn0aldFp0I4P1MsYZbnddDRSH3kjVA5oSucHF59DhZWiGJV/F6sVimBSeoTBHQD38Cs5RhyDHNyUad9v3gZERVGCJXC76i7+yyaoIDA+N9s0hasHajhTnjs3XQBYfZFwp8lWl3Ub+sOtPO1Ng7mFlSAYXCM6ljlKTEaxRwaYoXUC1EoIqEOG/8pC9SJThS2M1G7MW1c5xm4lksNss72OH4gtPns6SB0zCB0KADAgESooHIBIHFrLtai5U8ajEWo1J9B26PnIUqLd+uA0KPd2Y2FjrH6rx4xT8qG2p8i36SVGubvwBVmfQ7lSJcXt6wUvb43qyPs/fMiSY7QxHxt7/btMgxQl6JWMagvXMhCNXnhEHNNaTdBcG5KFERDGeo0txaAD1bzZ4mnxCQmoqusGzZ6wdDw6+5wq1tK/hQTQUgk2NwxfXAg2J5K02/3fKjFR2h7zewI1pEyhhpeONRkkRETcyojkK2EbVzZ8kc3RsuwzFYsJ+9u5Qj3E4= - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com - > Accept: */* - > - < HTTP/1.1 200 OK - < Date: Tue, 24 Sep 2013 11:59:33 GMT - < Server: Apache/2.4.6 (Fedora) mod_auth_kerb/5.4 - < WWW-Authenticate: Negotiate YIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKicQRveeZTV/QRJSIOoOWPbZkEmtdug9V5ZcMGXWqAJvCAnrvw9gHbklMyLl8f8jU2e0wU3ehtchLEL4dVeAYgKsnUgw4wGhHu59AZBwSbHRKSpv3I6gWEZqC4NAEuZJFW9ipdUHOiclBQniVXXCsRF/5Y - < X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a - < X-Debug-Remote-User: auth_admin - < X-Debug-Groups: auth_admin,auth_reseller_admin - < X-Debug-Token-Life: 86400s - < X-Debug-Token-Expires: Wed Sep 25 17:29:33 2013 - < Content-Length: 0 - < Content-Type: text/html; charset=UTF-8 - < - * Connection #1 to host (nil) left intact - * Closing connection #0 - * Closing connection #1 - -The header *X-Auth-Token* in response contains the token *AUTH_tk083b8abc92f4a514f34224a181ed568a*. - -####PUT a container ->curl -v -X PUT -H 'X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a' http://client.rhelbox.com:8080/v1/AUTH_myvolume/c1 - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > PUT /v1/AUTH_myvolume/c1 HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a - > - < HTTP/1.1 201 Created - < Content-Length: 0 - < Content-Type: text/html; charset=UTF-8 - < X-Trans-Id: txc420b0ebf9714445900e8-0052418863 - < Date: Tue, 24 Sep 2013 12:41:07 GMT - < - * Connection #0 to host client.rhelbox.com left intact - * Closing connection #0 - -####GET a container listing -> curl -v -X GET -H 'X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a' http://client.rhelbox.com:8080/v1/AUTH_myvolume - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > GET /v1/AUTH_myvolume HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a - > - < HTTP/1.1 200 OK - < Content-Length: 3 - < X-Account-Container-Count: 0 - < Accept-Ranges: bytes - < X-Account-Object-Count: 0 - < X-Bytes-Used: 0 - < X-Timestamp: 1379997117.09468 - < X-Object-Count: 0 - < X-Account-Bytes-Used: 0 - < X-Type: Account - < Content-Type: text/plain; charset=utf-8 - < X-Container-Count: 0 - < X-Trans-Id: tx89826736a1ab4d6aae6e3-00524188dc - < Date: Tue, 24 Sep 2013 12:43:08 GMT - < - c1 - * Connection #0 to host client.rhelbox.com left intact - * Closing connection #0 - -####PUT an object in container -> curl -v -X PUT -H 'X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a' http://client.rhelbox.com:8080/v1/AUTH_myvolume/c1/object1 -d'Hello world' - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > PUT /v1/AUTH_myvolume/c1/object1 HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a - > Content-Length: 11 - > Content-Type: application/x-www-form-urlencoded - > - * upload completely sent off: 11 out of 11 bytes - < HTTP/1.1 201 Created - < Last-Modified: Wed, 25 Sep 2013 06:08:00 GMT - < Content-Length: 0 - < Etag: 3e25960a79dbc69b674cd4ec67a72c62 - < Content-Type: text/html; charset=UTF-8 - < X-Trans-Id: tx01f1b5a430cf4af3897be-0052427dc0 - < Date: Wed, 25 Sep 2013 06:08:01 GMT - < - * Connection #0 to host client.rhelbox.com left intact - * Closing connection #0 - -####Give permission to jsmith to list and download objects from c1 container -> curl -v -X POST -H 'X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a' -H 'X-Container-Read: jsmith' http://client.rhelbox.com:8080/v1/AUTH_myvolume/c1 - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > POST /v1/AUTH_myvolume/c1 HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > X-Auth-Token: AUTH_tk083b8abc92f4a514f34224a181ed568a - > X-Container-Read: jsmith - > - < HTTP/1.1 204 No Content - < Content-Length: 0 - < Content-Type: text/html; charset=UTF-8 - < X-Trans-Id: txcedea3e2557d463eb591d-0052427f60 - < Date: Wed, 25 Sep 2013 06:14:56 GMT - < - * Connection #0 to host client.rhelbox.com left intact - * Closing connection #0 - -####Access container as jsmith - -> kinit jsmith - -Get token for jsmith -> curl -v -u : --negotiate --location-trusted http://client.rhelbox.com:8080/auth/v1.0 - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > GET /auth/v1.0 HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > - < HTTP/1.1 303 See Other - < Content-Type: text/html; charset=UTF-8 - < Location: http://client.rhelbox.com/cgi-bin/swift-auth - < Content-Length: 0 - < X-Trans-Id: txf51e1bf7f8c5496f8cc93-005242800b - < Date: Wed, 25 Sep 2013 06:17:47 GMT - < - * Connection #0 to host client.rhelbox.com left intact - * Issue another request to this URL: 'http://client.rhelbox.com/cgi-bin/swift-auth' - * About to connect() to client.rhelbox.com port 80 (#1) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 80 (#1) - > GET /cgi-bin/swift-auth HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com - > Accept: */* - > - < HTTP/1.1 401 Unauthorized - < Date: Wed, 25 Sep 2013 06:17:47 GMT - < Server: Apache/2.4.6 (Fedora) mod_auth_kerb/5.4 - < WWW-Authenticate: Negotiate - < WWW-Authenticate: Basic realm="Swift Authentication" - < Content-Length: 381 - < Content-Type: text/html; charset=iso-8859-1 - < - * Ignoring the response-body - * Connection #1 to host client.rhelbox.com left intact - * Issue another request to this URL: 'http://client.rhelbox.com/cgi-bin/swift-auth' - * Re-using existing connection! (#1) with host (nil) - * Connected to (nil) (192.168.56.101) port 80 (#1) - * Server auth using GSS-Negotiate with user '' - > GET /cgi-bin/swift-auth HTTP/1.1 - > Authorization: Negotiate YIICWAYJKoZIhvcSAQICAQBuggJHMIICQ6ADAgEFoQMCAQ6iBwMFACAAAACjggFbYYIBVzCCAVOgAwIBBaENGwtSSEVMQk9YLkNPTaIlMCOgAwIBA6EcMBobBEhUVFAbEmNsaWVudC5yaGVsYm94LmNvbaOCARQwggEQoAMCARKhAwIBAaKCAQIEgf/+3OaXYCSEjcsjU3t3lOLcYG84GBP9Kj9YTHc7yVMlcam4ivCwMqCkzxgvNo2E3a5KSWyFwngeX4b/QFbCKPXA4sfBibZRkeMk5gr2f0MLI3gWEAIYq7bJLre04bnkD2F0MzijPJrOLIx1KmFe08UGWCEmnG2uj07lvIR1RwV/7dMM4J1B+KKvDVKA0LxahwPIpx8oOON2yMGcstrBAHBBk5pmpt1Gg9Lh7xdNPsjP0IfI5Q0zkGCRBKpvpXymP1lQpQXlHbqkdBYOmG4+p/R+vIosO4ui1G6GWE9t71h3AqW61CcCj3/oOjZsG56k8HMSNk/+3mfUTP86nzLRGkekgc4wgcugAwIBEqKBwwSBwPsG9nGloEnOsA1abP4R1/yUDcikjjwKiacvZ+cu7bWEzu3L376k08U8C2YIClyUJy3Grt68LxhnfZ65VCZ5J5IOLiXOJnHBIoJ1L4GMYp4EgZzHvI7R3U3DApMzNWZwc1MsSF5UGhmLwxSevDLetJHjgKzKNteRyVN/8CFgjSBEjGSN1Qgy1RZHuQR9d3JHPczONZ4+ZgStfy+I1m2IUIgW3+4JGFVafHiBQVwSWRNfdXFgI3wBz7slntd7r3qMWA== - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com - > Accept: */* - > - < HTTP/1.1 200 OK - < Date: Wed, 25 Sep 2013 06:17:47 GMT - < Server: Apache/2.4.6 (Fedora) mod_auth_kerb/5.4 - < WWW-Authenticate: Negotiate YIGYBgkqhkiG9xIBAgICAG+BiDCBhaADAgEFoQMCAQ+ieTB3oAMCARKicARuH2YpjFrtgIhGr5nO7gh/21EvGH9tayRo5A3pw5pxD1B1036ePLG/x98OdMrSflse5s8ttz8FmvRphCFJa8kfYtnWULgoFLF2F2a1zBdSo2oCA0R05YFwArNhkg6ou5o7wWZkERHK33CKlhudSj8= - < X-Auth-Token: AUTH_tkb5a20eb8207a819e76619431c8410447 - < X-Debug-Remote-User: jsmith - < X-Debug-Groups: jsmith - < X-Debug-Token-Life: 86400s - < X-Debug-Token-Expires: Thu Sep 26 11:47:47 2013 - < Content-Length: 0 - < Content-Type: text/html; charset=UTF-8 - < - * Connection #1 to host (nil) left intact - * Closing connection #0 - * Closing connection #1 - -List the container using authentication token for jsmith: -> curl -v -X GET -H 'X-Auth-Token: AUTH_tkb5a20eb8207a819e76619431c8410447' http://client.rhelbox.com:8080/v1/AUTH_myvolume/c1 - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > GET /v1/AUTH_myvolume/c1 HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > X-Auth-Token: AUTH_tkb5a20eb8207a819e76619431c8410447 - > - < HTTP/1.1 200 OK - < Content-Length: 8 - < X-Container-Object-Count: 0 - < Accept-Ranges: bytes - < X-Timestamp: 1 - < X-Container-Bytes-Used: 0 - < Content-Type: text/plain; charset=utf-8 - < X-Trans-Id: tx575215929c654d9f9f284-00524280a4 - < Date: Wed, 25 Sep 2013 06:20:20 GMT - < - object1 - * Connection #0 to host client.rhelbox.com left intact - * Closing connection #0 - -Downloading the object as jsmith: -> curl -v -X GET -H 'X-Auth-Token: AUTH_tkb5a20eb8207a819e76619431c8410447' http://client.rhelbox.com:8080/v1/AUTH_myvolume/c1/object1 - - * About to connect() to client.rhelbox.com port 8080 (#0) - * Trying 192.168.56.101... - * connected - * Connected to client.rhelbox.com (192.168.56.101) port 8080 (#0) - > GET /v1/AUTH_myvolume/c1/object1 HTTP/1.1 - > User-Agent: curl/7.27.0 - > Host: client.rhelbox.com:8080 - > Accept: */* - > X-Auth-Token: AUTH_tkb5a20eb8207a819e76619431c8410447 - > - < HTTP/1.1 200 OK - < Content-Length: 11 - < Accept-Ranges: bytes - < Last-Modified: Wed, 25 Sep 2013 06:08:00 GMT - < Etag: 3e25960a79dbc69b674cd4ec67a72c62 - < X-Timestamp: 1380089280.98829 - < Content-Type: application/x-www-form-urlencoded - < X-Trans-Id: tx19b5cc3847854f40a6ca8-00524281aa - < Date: Wed, 25 Sep 2013 06:24:42 GMT - < - * Connection #0 to host client.rhelbox.com left intact - Hello world* Closing connection #0 - -For curl to follow the redirect, you need to specify additional -options. With these, and with a current Kerberos ticket, you should -get the Kerberos user's cached authentication token, or a new one if -the previous token has expired. - -> curl -v -u : --negotiate --location-trusted -X GET http://client.rhelbox.com:8080/v1/AUTH_myvolume/c1/object1 - -The --negotiate option is for curl to perform Kerberos authentication and ---location-trusted is for curl to follow the redirect. - -[auth_kerb_module Configuration]: http://modauthkerb.sourceforge.net/configure.html - - -#### Get an authentication token when auth_mode=passive: -> curl -v -H 'X-Auth-User: test:auth_admin' -H 'X-Auth-Key: Redhat*123' http://127.0.0.1:8080/auth/v1.0 - -**NOTE**: X-Storage-Url response header can be returned only in passive mode. - -<a name="config-swiftkerbauth" /> -##Configurable Parameters - -The kerbauth filter section in **/etc/swift/proxy-server.conf** looks something -like this: - - [filter:kerbauth] - use = egg:swiftkerbauth#kerbauth - ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth - auth_method = active - token_life = 86400 - debug_headers = yes - realm_name = RHELBOX.COM - -Of all the options listed above, specifying **ext\_authentication\_url** is -mandatory. The rest of the options are optional and have default values. - -#### ext\_authentication\_url -A URL specifying location of the swift-auth CGI script. Avoid using IP address. -Default value: None - -#### token_life -After how many seconds the cached information about an authentication token is -discarded. -Default value: 86400 - -#### debug_headers -When turned on, the response headers sent to the user will contain additional -debug information apart from the auth token. -Default value: yes - -#### auth_method -Set this to **"active"** when you want to allow access **only to clients -residing inside the domain**. In this mode, authentication is performed by -mod\_auth\_kerb using the Kerberos ticket bundled with the client request. -No username and password have to be specified to get a token. -Set this to **"passive"** when you want to allow access to clients residing -outside the domain. In this mode, authentication is performed by gleaning -username and password from request headers (X-Auth-User and X-Auth-Key) and -running kinit command against it. -Default value: passive - -#### realm_name -This is applicable only when the auth_method=passive. This option specifies -realm name if storage server belongs to more than one realm and realm name is not -part of the username specified in X-Auth-User header. - -<a name="swfunctest" /> -##Functional tests for SwiftkerbAuth - -Functional tests to be run on the storage node after SwiftKerbAuth is setup using -either IPA server or Windows AD. The gluster-swift/doc/markdown/swiftkerbauth -directory contains the SwiftkerbAuth setup documents. There are two modes of -working with SwiftKerbAuth. 'PASSIVE' mode indicates the client is outside the -domain configured using SwiftKerbAuth. Client provides the 'Username' and -'Password' while invoking a command. SwiftKerbAuth auth filter code then -would get the ticket granting ticket from AD server or IPA server. -In 'ACTIVE' mode of SwiftKerbAuth, User is already logged into storage node using -its kerberos credentials. That user is authenticated across AD/IPA server. - -In PASSIVE mode all the generic functional tests are run. ACTIVE mode has a -different way of acquiring Ticket Granting Ticket. And hence the different -framework of functional tests there. - -The accounts, users, passwords must be prepared on AD/IPA server as per -mentioned in test/functional_auth/swiftkerbauth/conf/test.conf - -Command to invoke SwiftKerbAuth functional tests is -> $tox -e swfunctest - -This would run both ACTIVE and PASSIVE mode functional test cases. diff --git a/etc/object-expirer.conf-gluster b/etc/object-expirer.conf-gluster index 4449ee2..55c4fbf 100644 --- a/etc/object-expirer.conf-gluster +++ b/etc/object-expirer.conf-gluster @@ -1,18 +1,41 @@ -#TODO: Add documentation to explain various options -#For now, refer: https://github.com/openstack/swift/blob/master/etc/object-expirer.conf-sample - [DEFAULT] +user = root [object-expirer] user = root log_facility = LOG_LOCAL2 -log_level = DEBUG +log_level = INFO + # The following parameters are used by object-expirer and needs to be same # across all conf files! auto_create_account_prefix = gs expiring_objects_account_name = expiring -interval = 30 +# The swift-object-expirer daemon will run every 'interval' number of seconds +# interval = 300 + +# Emit a log line report of the progress so far every 'report_interval' +# number of seconds. +# report_interval = 300 + +# concurrency is the level of concurrency to use to do the work, this value +# must be set to at least 1 +# concurrency = 1 + +# processes is how many parts to divide the work into, one part per process +# that will be doing the work +# processes set 0 means that a single process will be doing all the work +# processes can also be specified on the command line and will override the +# config value +# processes = 0 + +# process is which of the parts a particular process will work on +# process can also be specified on the command line and will overide the config +# value +# process is "zero based", if you want to use 3 processes, you should run +# processes with process set to 0, 1, and 2 +# process = 0 + [pipeline:main] pipeline = catch_errors cache proxy-server diff --git a/functests.sh b/functests.sh deleted file mode 100755 index 9797f58..0000000 --- a/functests.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -SRC_DIR=$(dirname $0) - -cd ${SRC_DIR}/test/functional -nosetests --exe $@ -func1=$? -cd - - -cd ${SRC_DIR}/test/functionalnosetests -nosetests --exe $@ -func2=$? -cd - - -exit $((func1 + func2)) diff --git a/gluster/swift/__init__.py b/gluster/swift/__init__.py index 17f2fcf..555581a 100644 --- a/gluster/swift/__init__.py +++ b/gluster/swift/__init__.py @@ -42,9 +42,9 @@ class PkgInfo(object): return '%s-dev' % (self.canonical_version,) -### -### Change the Package version here -### -_pkginfo = PkgInfo('1.13.0', '0', 'gluster_swift', False) +# +# Change the Package version here +# +_pkginfo = PkgInfo('1.13.1', '2', 'gluster_swift', False) __version__ = _pkginfo.pretty_version __canonical_version__ = _pkginfo.canonical_version diff --git a/gluster/swift/common/DiskDir.py b/gluster/swift/common/DiskDir.py index 0a91009..6112709 100644 --- a/gluster/swift/common/DiskDir.py +++ b/gluster/swift/common/DiskDir.py @@ -26,7 +26,10 @@ from gluster.swift.common.utils import validate_account, validate_container, \ X_CONTENT_LENGTH, X_TIMESTAMP, X_PUT_TIMESTAMP, X_ETAG, X_OBJECTS_COUNT, \ X_BYTES_USED, X_CONTAINER_COUNT, DIR_TYPE, rmobjdir, dir_is_object from gluster.swift.common import Glusterfs -from gluster.swift.common.exceptions import FileOrDirNotFoundError +from gluster.swift.common.exceptions import FileOrDirNotFoundError, \ + GlusterFileSystemIOError +from swift.common.constraints import MAX_META_COUNT, MAX_META_OVERALL_SIZE +from swift.common.swob import HTTPBadRequest DATADIR = 'containers' @@ -176,7 +179,12 @@ class DiskCommon(object): def _dir_exists_read_metadata(self): self._dir_exists = do_exists(self.datadir) if self._dir_exists: - self.metadata = _read_metadata(self.datadir) + try: + self.metadata = _read_metadata(self.datadir) + except GlusterFileSystemIOError as err: + if err.errno in (errno.ENOENT, errno.ESTALE): + return False + raise return self._dir_exists def is_deleted(self): @@ -195,12 +203,41 @@ class DiskCommon(object): except FileOrDirNotFoundError: return True - def update_metadata(self, metadata): + def validate_metadata(self, metadata): + """ + Validates that metadata falls within acceptable limits. + + :param metadata: to be validated + :raises: HTTPBadRequest if MAX_META_COUNT or MAX_META_OVERALL_SIZE + is exceeded + """ + meta_count = 0 + meta_size = 0 + for key, (value, timestamp) in metadata.iteritems(): + key = key.lower() + if value != '' and (key.startswith('x-account-meta') or + key.startswith('x-container-meta')): + prefix = 'x-account-meta-' + if key.startswith('x-container-meta-'): + prefix = 'x-container-meta-' + key = key[len(prefix):] + meta_count = meta_count + 1 + meta_size = meta_size + len(key) + len(value) + if meta_count > MAX_META_COUNT: + raise HTTPBadRequest('Too many metadata items; max %d' + % MAX_META_COUNT) + if meta_size > MAX_META_OVERALL_SIZE: + raise HTTPBadRequest('Total metadata too large; max %d' + % MAX_META_OVERALL_SIZE) + + def update_metadata(self, metadata, validate_metadata=False): assert self.metadata, "Valid container/account metadata should have " \ "been created by now" if metadata: new_metadata = self.metadata.copy() new_metadata.update(metadata) + if validate_metadata: + self.validate_metadata(new_metadata) if new_metadata != self.metadata: write_metadata(self.datadir, new_metadata) self.metadata = new_metadata @@ -362,7 +399,15 @@ class DiskDir(DiskCommon): count = 0 for obj in objects: obj_path = os.path.join(self.datadir, obj) - metadata = read_metadata(obj_path) + try: + metadata = read_metadata(obj_path) + except GlusterFileSystemIOError as err: + if err.errno in (errno.ENOENT, errno.ESTALE): + # obj might have been deleted by another process + # since the objects list was originally built + continue + else: + raise err if not metadata or not validate_object(metadata): if delimiter == '/' and obj_path[-1] == delimiter: clean_obj_path = obj_path[:-1] @@ -373,7 +418,7 @@ class DiskDir(DiskCommon): except OSError as e: # FIXME - total hack to get upstream swift ported unit # test cases working for now. - if e.errno != errno.ENOENT: + if e.errno not in (errno.ENOENT, errno.ESTALE): raise if not Glusterfs._implicit_dir_objects and metadata \ and metadata[X_CONTENT_TYPE] == DIR_TYPE \ @@ -450,7 +495,7 @@ class DiskDir(DiskCommon): # If we create it, ensure we own it. do_chown(self.datadir, self.uid, self.gid) metadata = get_container_metadata(self.datadir) - metadata[X_TIMESTAMP] = timestamp + metadata[X_TIMESTAMP] = (timestamp, 0) write_metadata(self.datadir, metadata) self.metadata = metadata self._dir_exists = True @@ -562,7 +607,7 @@ class DiskAccount(DiskCommon): :param metadata: Metadata to write. """ metadata = get_account_metadata(self.datadir) - metadata[X_TIMESTAMP] = timestamp + metadata[X_TIMESTAMP] = (timestamp, 0) write_metadata(self.datadir, metadata) self.metadata = metadata @@ -672,7 +717,7 @@ class DiskAccount(DiskCommon): except OSError as e: # FIXME - total hack to get upstream swift ported unit # test cases working for now. - if e.errno != errno.ENOENT: + if e.errno not in (errno.ENOENT, errno.ESTALE): raise if metadata: list_item.append(metadata[X_OBJECTS_COUNT][0]) diff --git a/gluster/swift/common/Glusterfs.py b/gluster/swift/common/Glusterfs.py index 5d2cab1..0098bff 100644 --- a/gluster/swift/common/Glusterfs.py +++ b/gluster/swift/common/Glusterfs.py @@ -37,7 +37,6 @@ _allow_mount_per_server = False _implicit_dir_objects = False _container_update_object_count = False _account_update_container_count = False -_ignore_unsupported_headers = False if _fs_conf.read(os.path.join(SWIFT_DIR, 'fs.conf')): try: @@ -98,18 +97,6 @@ if _fs_conf.read(os.path.join(SWIFT_DIR, 'fs.conf')): except (NoSectionError, NoOptionError): pass - # -- Hidden configuration option -- - # Ignore unsupported headers and allow them in a request without - # returning a 400-BadRequest. This setting can be set to - # allow unsupported headers such as X-Delete-At and - # X-Delete-After even though they will not be used. - try: - _ignore_unsupported_headers = \ - _fs_conf.get('DEFAULT', - 'ignore_unsupported_headers', - "no") in TRUE_VALUES - except (NoSectionError, NoOptionError): - pass NAME = 'glusterfs' diff --git a/gluster/swift/common/constraints.py b/gluster/swift/common/constraints.py index 80616f2..7979b43 100644 --- a/gluster/swift/common/constraints.py +++ b/gluster/swift/common/constraints.py @@ -14,16 +14,12 @@ # limitations under the License. import os -try: - from webob.exc import HTTPBadRequest -except ImportError: - from swift.common.swob import HTTPBadRequest +from swift.common.swob import HTTPBadRequest import swift.common.constraints import swift.common.ring as _ring from gluster.swift.common import Glusterfs, ring MAX_OBJECT_NAME_COMPONENT_LENGTH = 255 -UNSUPPORTED_HEADERS = [] def set_object_name_component_length(len=None): @@ -56,40 +52,8 @@ def validate_obj_name_component(obj): return '' -def validate_headers(req): - """ - Validate client header requests - :param req: Http request - """ - if not Glusterfs._ignore_unsupported_headers: - for unsupported_header in UNSUPPORTED_HEADERS: - if unsupported_header in req.headers: - return '%s headers are not supported' \ - % ','.join(UNSUPPORTED_HEADERS) - return '' - # Save the original check object creation __check_object_creation = swift.common.constraints.check_object_creation -__check_metadata = swift.common.constraints.check_metadata - - -def gluster_check_metadata(req, target_type, POST=True): - """ - :param req: HTTP request object - :param target_type: Value from POST passed to __check_metadata - :param POST: Only call __check_metadata on POST since Swift only - calls check_metadata on POSTs. - """ - ret = None - if POST: - ret = __check_metadata(req, target_type) - if ret is None: - bdy = validate_headers(req) - if bdy: - ret = HTTPBadRequest(body=bdy, - request=req, - content_type='text/plain') - return ret # Define our new one which invokes the original @@ -119,14 +83,11 @@ def gluster_check_object_creation(req, object_name): ret = HTTPBadRequest(body=bdy, request=req, content_type='text/plain') - if ret is None: - ret = gluster_check_metadata(req, 'object', POST=False) return ret # Replace the original checks with ours swift.common.constraints.check_object_creation = gluster_check_object_creation -swift.common.constraints.check_metadata = gluster_check_metadata # Replace the original check mount with ours swift.common.constraints.check_mount = Glusterfs.mount diff --git a/gluster/swift/common/fs_utils.py b/gluster/swift/common/fs_utils.py index 9f698df..e0fa7ce 100644 --- a/gluster/swift/common/fs_utils.py +++ b/gluster/swift/common/fs_utils.py @@ -162,7 +162,7 @@ def dir_empty(path): files = do_listdir(path) return not files except GlusterFileSystemOSError as err: - if err.errno == errno.ENOENT: + if err.errno in (errno.ENOENT, errno.ESTALE): raise FileOrDirNotFoundError() if err.errno == errno.ENOTDIR: raise NotDirectoryError() @@ -210,7 +210,7 @@ def do_stat(path): serr = err sleep(random.uniform(0.001, 0.005)) continue - if err.errno == errno.ENOENT: + if err.errno in (errno.ENOENT, errno.ESTALE): stats = None else: raise GlusterFileSystemOSError( @@ -268,10 +268,10 @@ def do_unlink(path, log=True): try: os.unlink(path) except OSError as err: - if err.errno != errno.ENOENT: + if err.errno not in (errno.ENOENT, errno.ESTALE): raise GlusterFileSystemOSError( err.errno, '%s, os.unlink("%s")' % (err.strerror, path)) - elif log: + else: logging.warn("fs_utils: os.unlink failed on non-existent path: %s", path) diff --git a/gluster/swift/common/middleware/gswauth/swauth/middleware.py b/gluster/swift/common/middleware/gswauth/swauth/middleware.py index 314eedb..cdcc638 100644 --- a/gluster/swift/common/middleware/gswauth/swauth/middleware.py +++ b/gluster/swift/common/middleware/gswauth/swauth/middleware.py @@ -241,7 +241,7 @@ class Swauth(object): version, rest = split_path(env.get('PATH_INFO', ''), 1, 2, True) except ValueError: - version, rest = None, None + rest = None if rest and rest.startswith(self.reseller_prefix): # Handle anonymous access to accounts I'm the definitive # auth for. @@ -693,7 +693,7 @@ class Swauth(object): return HTTPBadRequest(request=req) try: new_services = json.loads(req.body) - except ValueError, err: + except ValueError as err: return HTTPBadRequest(body=str(err)) # Get the current services information path = quote('/v1/%s/%s/.services' % (self.auth_account, account)) @@ -1405,7 +1405,7 @@ class Swauth(object): memcache_key, (self.itoken_expires, '%s,.reseller_admin,%s' % (self.metadata_volume, - self.auth_account)), + self.auth_account)), timeout=self.token_life) return self.itoken @@ -1589,19 +1589,20 @@ class Swauth(object): if getattr(req, 'client_disconnect', False) or \ getattr(response, 'client_disconnect', False): status_int = 499 - self.logger.info( - ' '.join(quote(str(x)) for x in (client or '-', - req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', gmtime()), - req.method, the_request, req.environ['SERVER_PROTOCOL'], - status_int, req.referer or '-', req.user_agent or '-', - req.headers.get( - 'x-auth-token', - req.headers.get('x-auth-admin-user', '-')), - getattr(req, 'bytes_transferred', 0) or '-', - getattr(response, 'bytes_transferred', 0) or '-', - req.headers.get('etag', '-'), - req.headers.get('x-trans-id', '-'), logged_headers or '-', - trans_time))) + self.logger.info(' '.join(quote(str(x)) for x in + (client or '-', + req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', + gmtime()), + req.method, the_request, + req.environ['SERVER_PROTOCOL'], status_int, + req.referer or '-', req.user_agent or '-', + req.headers.get('x-auth-token', req.headers.get( + 'x-auth-admin-user', '-')), + getattr(req, 'bytes_transferred', 0) or '-', + getattr(response, 'bytes_transferred', 0) or '-', + req.headers.get('etag', '-'), + req.headers.get('x-trans-id', '-'), logged_headers + or '-', trans_time))) def filter_factory(global_conf, **local_conf): diff --git a/gluster/swift/common/middleware/gswauth/test_swauth/__init__.py b/gluster/swift/common/middleware/gswauth/test_swauth/__init__.py deleted file mode 100644 index f53bc5a..0000000 --- a/gluster/swift/common/middleware/gswauth/test_swauth/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# See http://code.google.com/p/python-nose/issues/detail?id=373 -# The code below enables nosetests to work with i18n _() blocks - -import __builtin__ - -setattr(__builtin__, '_', lambda x: x) diff --git a/gluster/swift/common/middleware/gswauth/test_swauth/unit/test_authtypes.py b/gluster/swift/common/middleware/gswauth/test_swauth/unit/test_authtypes.py deleted file mode 100644 index d9b7b55..0000000 --- a/gluster/swift/common/middleware/gswauth/test_swauth/unit/test_authtypes.py +++ /dev/null @@ -1,63 +0,0 @@ -# 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. -# -# Pablo Llopis 2011 - -import unittest -from swauth import authtypes - - -class TestPlaintext(unittest.TestCase): - - def setUp(self): - self.auth_encoder = authtypes.Plaintext() - - def test_plaintext_encode(self): - enc_key = self.auth_encoder.encode('keystring') - self.assertEquals('plaintext:keystring', enc_key) - - def test_plaintext_valid_match(self): - creds = 'plaintext:keystring' - match = self.auth_encoder.match('keystring', creds) - self.assertEquals(match, True) - - def test_plaintext_invalid_match(self): - creds = 'plaintext:other-keystring' - match = self.auth_encoder.match('keystring', creds) - self.assertEquals(match, False) - - -class TestSha1(unittest.TestCase): - - def setUp(self): - self.auth_encoder = authtypes.Sha1() - self.auth_encoder.salt = 'salt' - - def test_sha1_encode(self): - enc_key = self.auth_encoder.encode('keystring') - self.assertEquals('sha1:salt$d50dc700c296e23ce5b41f7431a0e01f69010f06', - enc_key) - - def test_sha1_valid_match(self): - creds = 'sha1:salt$d50dc700c296e23ce5b41f7431a0e01f69010f06' - match = self.auth_encoder.match('keystring', creds) - self.assertEquals(match, True) - - def test_sha1_invalid_match(self): - creds = 'sha1:salt$deadbabedeadbabedeadbabec0ffeebadc0ffeee' - match = self.auth_encoder.match('keystring', creds) - self.assertEquals(match, False) - - -if __name__ == '__main__': - unittest.main() diff --git a/gluster/swift/common/middleware/gswauth/test_swauth/unit/test_middleware.py b/gluster/swift/common/middleware/gswauth/test_swauth/unit/test_middleware.py deleted file mode 100644 index 62259ff..0000000 --- a/gluster/swift/common/middleware/gswauth/test_swauth/unit/test_middleware.py +++ /dev/null @@ -1,4519 +0,0 @@ -# Copyright (c) 2010-2011 OpenStack, LLC. -# -# 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. - -try: - import simplejson as json -except ImportError: - import json -import unittest -from contextlib import contextmanager -from time import time - -from swift.common.swob import Request, Response - -from swauth import middleware as auth -from swauth.authtypes import MAX_TOKEN_LENGTH - - -DEFAULT_TOKEN_LIFE = 86400 -MAX_TOKEN_LIFE = 100000 - - -class FakeMemcache(object): - - def __init__(self): - self.store = {} - - def get(self, key): - return self.store.get(key) - - def set(self, key, value, timeout=0, time=0): - self.store[key] = value - return True - - def incr(self, key, timeout=0, time=0): - self.store[key] = self.store.setdefault(key, 0) + 1 - return self.store[key] - - @contextmanager - def soft_lock(self, key, timeout=0, retries=5, time=0): - yield True - - def delete(self, key): - try: - del self.store[key] - except Exception: - pass - return True - - -class FakeApp(object): - - def __init__( - self, status_headers_body_iter=None, acl=None, sync_key=None): - self.calls = 0 - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter( - [('404 Not Found', {}, '')]) - self.acl = acl - self.sync_key = sync_key - - def __call__(self, env, start_response): - self.calls += 1 - self.request = Request.blank('', environ=env) - if self.acl: - self.request.acl = self.acl - if self.sync_key: - self.request.environ[ - 'swift_sync_key'] = self.sync_key - if 'swift.authorize' in env: - resp = env['swift.authorize'](self.request) - if resp: - return resp(env, start_response) - status, headers, body = self.status_headers_body_iter.next( - ) - return Response(status=status, headers=headers, - body=body)(env, start_response) - - -class FakeConn(object): - - def __init__(self, status_headers_body_iter=None): - self.calls = 0 - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter( - [('404 Not Found', {}, '')]) - - def request(self, method, path, headers): - self.calls += 1 - self.request_path = path - self.status, self.headers, self.body = \ - self.status_headers_body_iter.next() - self.status, self.reason = self.status.split(' ', 1) - self.status = int(self.status) - - def getresponse(self): - return self - - def read(self): - body = self.body - self.body = '' - return body - - -class TestAuth(unittest.TestCase): - - def setUp(self): - self.test_auth = \ - auth.filter_factory({ - 'super_admin_key': 'supertest', - 'token_life': str(DEFAULT_TOKEN_LIFE), - 'max_token_life': str(MAX_TOKEN_LIFE)})(FakeApp()) - - def test_super_admin_key_not_required(self): - auth.filter_factory({})(FakeApp()) - - def test_reseller_prefix_init(self): - app = FakeApp() - ath = auth.filter_factory( - {'super_admin_key': 'supertest'})(app) - self.assertEquals(ath.reseller_prefix, 'AUTH_') - ath = auth.filter_factory( - {'super_admin_key': 'supertest', - 'reseller_prefix': 'TEST'})(app) - self.assertEquals(ath.reseller_prefix, 'TEST_') - ath = auth.filter_factory( - {'super_admin_key': 'supertest', - 'reseller_prefix': 'TEST_'})(app) - self.assertEquals(ath.reseller_prefix, 'TEST_') - - def test_auth_prefix_init(self): - app = FakeApp() - ath = auth.filter_factory( - {'super_admin_key': 'supertest'})(app) - self.assertEquals(ath.auth_prefix, '/auth/') - ath = auth.filter_factory( - {'super_admin_key': 'supertest', - 'auth_prefix': ''})(app) - self.assertEquals(ath.auth_prefix, '/auth/') - ath = auth.filter_factory( - {'super_admin_key': 'supertest', - 'auth_prefix': '/test/'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - ath = auth.filter_factory( - {'super_admin_key': 'supertest', - 'auth_prefix': '/test'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - ath = auth.filter_factory( - {'super_admin_key': 'supertest', - 'auth_prefix': 'test/'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - ath = auth.filter_factory( - {'super_admin_key': 'supertest', - 'auth_prefix': 'test'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - - def test_no_auth_type_init(self): - app = FakeApp() - ath = auth.filter_factory({})(app) - self.assertEquals(ath.auth_type, 'Plaintext') - - def test_valid_auth_type_init(self): - app = FakeApp() - ath = auth.filter_factory( - {'auth_type': 'sha1'})(app) - self.assertEquals(ath.auth_type, 'Sha1') - ath = auth.filter_factory( - {'auth_type': 'plaintext'})(app) - self.assertEquals(ath.auth_type, 'Plaintext') - - def test_invalid_auth_type_init(self): - app = FakeApp() - exc = None - try: - auth.filter_factory( - {'auth_type': 'NONEXISTANT'})(app) - except Exception as err: - exc = err - self.assertEquals(str(exc), - 'Invalid auth_type in config file: %s' % - 'Nonexistant') - - def test_default_swift_cluster_init(self): - app = FakeApp() - self.assertRaises(Exception, auth.filter_factory({ - 'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#badscheme://host/path'}), app) - ath = auth.filter_factory( - {'super_admin_key': 'supertest'})(app) - self.assertEquals(ath.default_swift_cluster, - 'local#http://127.0.0.1:8080/v1') - ath = auth.filter_factory({ - 'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#http://host/path'})(app) - self.assertEquals(ath.default_swift_cluster, - 'local#http://host/path') - ath = auth.filter_factory({ - 'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#https://host/path/'})(app) - self.assertEquals(ath.dsc_url, 'https://host/path') - self.assertEquals(ath.dsc_url2, 'https://host/path') - ath = auth.filter_factory({ - 'super_admin_key': 'supertest', - 'default_swift_cluster': - 'local#https://host/path/#http://host2/path2/'})(app) - self.assertEquals(ath.dsc_url, 'https://host/path') - self.assertEquals( - ath.dsc_url2, - 'http://host2/path2') - - def test_top_level_denied(self): - resp = Request.blank( - '/').get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_anon(self): - resp = Request.blank( - '/v1/AUTH_account').get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - self.assertEquals(resp.environ['swift.authorize'], - self.test_auth.authorize) - - def test_auth_deny_non_reseller_prefix(self): - resp = Request.blank( - '/v1/BLAH_account', - headers={'X-Auth-Token': 'BLAH_t'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - self.assertEquals(resp.environ['swift.authorize'], - self.test_auth.denied_response) - - def test_auth_deny_non_reseller_prefix_no_override( - self): - fake_authorize = lambda x: Response( - status='500 Fake') - resp = Request.blank( - '/v1/BLAH_account', - headers={'X-Auth-Token': 'BLAH_t'}, - environ={'swift.authorize': fake_authorize}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(resp.environ['swift.authorize'], fake_authorize) - - def test_auth_no_reseller_prefix_deny(self): - # Ensures that when we have no reseller prefix, we don't deny a request - # outright but set up a denial swift.authorize and pass the request on - # down the chain. - local_app = FakeApp() - local_auth = auth.filter_factory( - {'super_admin_key': 'supertest', - 'reseller_prefix': ''})(local_app) - resp = Request.blank( - '/v1/account', - headers={'X-Auth-Token': 't'}).get_response(local_auth) - self.assertEquals(resp.status_int, 401) - # one for checking auth, two for request passed - # along - self.assertEquals(local_app.calls, 2) - self.assertEquals(resp.environ['swift.authorize'], - local_auth.denied_response) - - def test_auth_no_reseller_prefix_allow(self): - # Ensures that when we have no reseller prefix, we can still allow - # access if our auth server accepts requests - local_app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - local_auth = auth.filter_factory( - {'super_admin_key': 'supertest', - 'reseller_prefix': ''})(local_app) - resp = Request.blank( - '/v1/act', - headers={'X-Auth-Token': 't'}).get_response(local_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(local_app.calls, 2) - self.assertEquals(resp.environ['swift.authorize'], - local_auth.authorize) - - def test_auth_no_reseller_prefix_no_token(self): - # Check that normally we set up a call back to our - # authorize. - local_auth = \ - auth.filter_factory( - {'super_admin_key': 'supertest', - 'reseller_prefix': ''})(FakeApp(iter([]))) - resp = Request.blank( - '/v1/account').get_response( - local_auth) - self.assertEquals(resp.status_int, 401) - self.assertEquals( - resp.environ['swift.authorize'], local_auth.authorize) - # Now make sure we don't override an existing swift.authorize when we - # have no reseller prefix. - local_auth = \ - auth.filter_factory( - {'super_admin_key': 'supertest', - 'reseller_prefix': ''})(FakeApp()) - local_authorize = lambda req: Response('test') - resp = Request.blank( - '/v1/account', environ={'swift.authorize': - local_authorize}).get_response(local_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals( - resp.environ['swift.authorize'], - local_authorize) - - def test_auth_fail(self): - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_auth_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_auth_memcache(self): - # First run our test without memcache, showing we need to return the - # token contents twice. - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, ''), - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 4) - # Now run our test with memcache, showing we no longer need to return - # the token contents twice. - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, ''), - # Don't need a second token object returned if memcache is - # used - ('204 No Content', {}, '')])) - fake_memcache = FakeMemcache() - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}, - environ={'swift.cache': fake_memcache}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 204) - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}, - environ={'swift.cache': fake_memcache}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_auth_just_expired(self): - self.test_auth.app = FakeApp(iter([ - # Request for token (which will have expired) - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() - 1})), - # Request to delete token - ('204 No Content', {}, '')])) - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_middleware_storage_token(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - resp = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Storage-Token': 'AUTH_t'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_authorize_bad_path(self): - req = Request.blank('/badpath') - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 401) - req = Request.blank('/badpath') - req.remote_user = 'act:usr,act,AUTH_cfa' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_authorize_account_access(self): - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act,AUTH_cfa' - self.assertEquals( - self.test_auth.authorize(req), - None) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_authorize_acl_group_access(self): - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act' - self.assertEquals( - self.test_auth.authorize(req), - None) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act:usr' - self.assertEquals( - self.test_auth.authorize(req), - None) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act2' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act:usr2' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_deny_cross_reseller(self): - # Tests that cross-reseller is denied, even if ACLs/group - # names match - req = Request.blank('/v1/OTHER_cfa') - req.remote_user = 'act:usr,act,AUTH_cfa' - req.acl = 'act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_authorize_acl_referrer_access(self): - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:*,.rlistings' - self.assertEquals( - self.test_auth.authorize(req), - None) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:*' # No listings allowed - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:.example.com,.rlistings' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = Request.blank('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.referer = 'http://www.example.com/index.html' - req.acl = '.r:.example.com,.rlistings' - self.assertEquals( - self.test_auth.authorize(req), - None) - req = Request.blank('/v1/AUTH_cfa/c') - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 401) - req = Request.blank('/v1/AUTH_cfa/c') - req.acl = '.r:*,.rlistings' - self.assertEquals( - self.test_auth.authorize(req), - None) - req = Request.blank('/v1/AUTH_cfa/c') - req.acl = '.r:*' # No listings allowed - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 401) - req = Request.blank('/v1/AUTH_cfa/c') - req.acl = '.r:.example.com,.rlistings' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 401) - req = Request.blank('/v1/AUTH_cfa/c') - req.referer = 'http://www.example.com/index.html' - req.acl = '.r:.example.com,.rlistings' - self.assertEquals( - self.test_auth.authorize(req), - None) - - def test_detect_reseller_request(self): - req = self._make_request('/v1/AUTH_admin', - headers={'X-Auth-Token': 'AUTH_t'}) - cache_key = 'AUTH_/auth/AUTH_t' - cache_entry = (time() + 3600, '.reseller_admin') - req.environ['swift.cache'].set( - cache_key, cache_entry) - self.assertTrue(req.environ.get('reseller_request')) - - def test_account_put_permissions(self): - req = Request.blank( - '/v1/AUTH_new', - environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - req = Request.blank( - '/v1/AUTH_new', - environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,AUTH_other' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - # Even PUTs to your own account as account admin - # should fail - req = Request.blank( - '/v1/AUTH_old', - environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,AUTH_old' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - req = Request.blank( - '/v1/AUTH_new', - environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEquals(resp, None) - - # .super_admin is not something the middleware should ever see or care - # about - req = Request.blank( - '/v1/AUTH_new', - environ={'REQUEST_METHOD': 'PUT'}) - req.remote_user = 'act:usr,act,.super_admin' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_account_delete_permissions(self): - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,AUTH_other' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - # Even DELETEs to your own account as account admin should - # fail - req = Request.blank('/v1/AUTH_old', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,AUTH_old' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEquals(resp, None) - - # .super_admin is not something the middleware should ever see or care - # about - req = Request.blank('/v1/AUTH_new', - environ={'REQUEST_METHOD': 'DELETE'}) - req.remote_user = 'act:usr,act,.super_admin' - resp = self.test_auth.authorize(req) - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_get_token_fail(self): - resp = Request.blank( - '/auth/v1.0').get_response( - self.test_auth) - self.assertEquals(resp.status_int, 401) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_get_token_fail_invalid_key(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'invalid'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_token_fail_invalid_x_auth_user_format( - self): - resp = Request.blank( - '/auth/v1/act/auth', - headers={'X-Auth-User': 'usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_get_token_fail_non_matching_account_in_request( - self): - resp = Request.blank( - '/auth/v1/act/auth', - headers={'X-Auth-User': 'act2:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_get_token_fail_bad_path(self): - resp = Request.blank( - '/auth/v1/act/auth/invalid', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_get_token_fail_missing_key(self): - resp = Request.blank( - '/auth/v1/act/auth', - headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_get_token_fail_get_user_details(self): - self.test_auth.app = FakeApp(iter([ - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_token_fail_get_account(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_get_token_fail_put_new_token(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_get_token_fail_post_to_user(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 4) - - def test_get_token_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_fail_get_existing_token(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_get_token_success_v1_0(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assert_(resp.headers.get( - 'x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_0_with_user_token_life( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key', - 'X-Auth-Token-Lifetime': 10}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - left = int(resp.headers['x-auth-token-expires']) - self.assertTrue(left > 0, '%d > 0' % left) - self.assertTrue(left <= 10, '%d <= 10' % left) - self.assert_(resp.headers.get( - 'x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_0_with_user_token_life_past_max( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - req = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key', - 'X-Auth-Token-Lifetime': MAX_TOKEN_LIFE * 10}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - left = int(resp.headers['x-auth-token-expires']) - self.assertTrue(left > DEFAULT_TOKEN_LIFE, - '%d > %d' % (left, DEFAULT_TOKEN_LIFE)) - self.assertTrue(left <= MAX_TOKEN_LIFE, - '%d <= %d' % (left, MAX_TOKEN_LIFE)) - self.assert_(resp.headers.get( - 'x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_act_auth(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1/act/auth', - headers={'X-Storage-User': 'usr', - 'X-Storage-Pass': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assert_(resp.headers.get( - 'x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_success_storage_instead_of_auth( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Storage-User': 'act:usr', - 'X-Storage-Pass': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assert_( - resp.headers.get( - 'x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_act_auth_auth_instead_of_storage( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1/act/auth', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assert_(resp.headers.get( - 'x-auth-token', - '').startswith('AUTH_tk'), resp.headers.get('x-auth-token')) - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_success_existing_token(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('200 Ok', {}, json.dumps( - {"account": "act", "user": "usr", - "account_id": "AUTH_cfa", - "groups": [{'name': "act:usr"}, - {'name': "key"}, {'name': ".admin"}], - "expires": 9999999999.9999999})), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals( - resp.headers.get('x-auth-token'), - 'AUTH_tktest') - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_get_token_success_existing_token_but_request_new_one( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # DELETE of expired token - ('204 No Content', {}, ''), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key', - 'X-Auth-New-Token': 'true'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertNotEquals( - resp.headers.get('x-auth-token'), 'AUTH_tktest') - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 6) - - def test_get_token_success_existing_token_expired(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('200 Ok', {}, json.dumps( - {"account": "act", "user": "usr", - "account_id": "AUTH_cfa", - "groups": [{'name': "act:usr"}, - {'name': "key"}, {'name': ".admin"}], - "expires": 0.0})), - # DELETE of expired token - ('204 No Content', {}, ''), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertNotEquals( - resp.headers.get('x-auth-token'), - 'AUTH_tktest') - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 7) - - def test_get_token_success_existing_token_expired_fail_deleting_old( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {'X-Object-Meta-Auth-Token': 'AUTH_tktest'}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of token - ('200 Ok', {}, json.dumps({"account": "act", "user": "usr", - "account_id": "AUTH_cfa", - "groups": [{'name': "act:usr"}, - {'name': "key"}, {'name': ".admin"}], - "expires": 0.0})), - # DELETE of expired token - ('503 Service Unavailable', {}, ''), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': 'act:usr', - 'X-Auth-Key': 'key'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertNotEquals( - resp.headers.get('x-auth-token'), - 'AUTH_tktest') - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 7) - - def test_prep_success(self): - list_to_iter = [ - # PUT of .auth account - ('201 Created', {}, ''), - # PUT of .account_id container - ('201 Created', {}, '')] - # PUT of .token* containers - for x in xrange(16): - list_to_iter.append(('201 Created', {}, '')) - self.test_auth.app = FakeApp(iter(list_to_iter)) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 18) - - def test_prep_bad_method(self): - resp = Request.blank('/auth/v2/.prep', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'HEAD'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_prep_bad_creds(self): - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - 'super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'upertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': '.super_admin'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - resp = Request.blank( - '/auth/v2/.prep', - environ={'REQUEST_METHOD': 'POST'}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - - def test_prep_fail_account_create(self): - self.test_auth.app = FakeApp(iter([ - # PUT of .auth account - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_prep_fail_token_container_create(self): - self.test_auth.app = FakeApp(iter([ - # PUT of .auth account - ('201 Created', {}, ''), - # PUT of .token container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_prep_fail_account_id_container_create(self): - self.test_auth.app = FakeApp(iter([ - # PUT of .auth account - ('201 Created', {}, ''), - # PUT of .token container - ('201 Created', {}, ''), - # PUT of .account_id container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/.prep', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_get_reseller_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of .auth account (list containers) - ('200 Ok', {}, json.dumps([ - {"name": ".token", "count": 0, "bytes": 0}, - {"name": ".account_id", - "count": 0, "bytes": 0}, - {"name": "act", "count": 0, "bytes": 0}])), - # GET of .auth account (list containers - # continuation) - ('200 Ok', {}, '[]')])) - resp = Request.blank('/auth/v2', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(json.loads(resp.body), - {"accounts": [{"name": "act"}]}) - self.assertEquals(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}, - {"name": ".reseller_admin"}], "auth": "plaintext:key"})), - # GET of .auth account (list containers) - ('200 Ok', {}, json.dumps([ - {"name": ".token", "count": 0, "bytes": 0}, - {"name": ".account_id", - "count": 0, "bytes": 0}, - {"name": "act", "count": 0, "bytes": 0}])), - # GET of .auth account (list containers - # continuation) - ('200 Ok', {}, '[]')])) - resp = Request.blank('/auth/v2', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(json.loads(resp.body), - {"accounts": [{"name": "act"}]}) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_get_reseller_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2', - headers={ - 'X-Auth-Admin-User': - 'super:admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller - # admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2', - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_reseller_fail_listing(self): - self.test_auth.app = FakeApp(iter([ - # GET of .auth account (list containers) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of .auth account (list containers) - ('200 Ok', {}, json.dumps([ - {"name": ".token", "count": 0, "bytes": 0}, - {"name": ".account_id", - "count": 0, "bytes": 0}, - {"name": "act", "count": 0, "bytes": 0}])), - # GET of .auth account (list containers - # continuation) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_get_account_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, - json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of account container (list objects - # continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals( - json.loads(resp.body), - {'account_id': 'AUTH_cfa', - 'services': {'storage': - {'default': 'local', - 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}}, - 'users': [{'name': 'tester'}, {'name': 'tester3'}]}) - self.assertEquals(self.test_auth.app.calls, 3) - - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of .services object - ('200 Ok', {}, - json.dumps({"storage": - {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of account container (list objects - # continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals( - json.loads(resp.body), - {'account_id': 'AUTH_cfa', - 'services': {'storage': - {'default': 'local', - 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}}, - 'users': [{'name': 'tester'}, {'name': 'tester3'}]}) - self.assertEquals(self.test_auth.app.calls, 4) - - def test_get_account_fail_bad_account_name(self): - resp = Request.blank('/auth/v2/.token', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - resp = Request.blank('/auth/v2/.anything', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_get_account_fail_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - 'super:admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but wrong - # account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - 'act2:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_account_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_account_fail_listing(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 2) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of account container (list objects - # continuation) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_set_services_new_service(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, - json.dumps({"storage": - {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps( - {'new_service': - {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals( - json.loads(resp.body), - {'storage': {'default': 'local', - 'local': 'http://127.0.0.1:8080/v1/AUTH_cfa'}, - 'new_service': {'new_endpoint': 'new_value'}}) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_set_services_new_endpoint(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps( - {"storage": - {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps( - {'storage': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals( - json.loads(resp.body), - {'storage': {'default': 'local', - 'local': - 'http://127.0.0.1:8080/v1/AUTH_cfa', - 'new_endpoint': 'new_value'}}) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_set_services_update_endpoint(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps( - {'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(json.loads(resp.body), - {'storage': {'default': 'local', - 'local': 'new_value'}}) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_set_services_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - 'super:admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps( - {'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller - # admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'}, - body=json.dumps( - {'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'}, - body=json.dumps( - {'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_set_services_fail_bad_account_name(self): - resp = Request.blank('/auth/v2/.act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps( - {'storage': {'local': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_set_services_fail_bad_json(self): - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body='garbage' - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body='' - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_set_services_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('503 Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({ - 'new_service': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps({ - 'new_service': {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_set_services_fail_put_services(self): - self.test_auth.app = FakeApp(iter([ - # GET of .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # PUT of new .services object - ('503 Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.services', - environ={ - 'REQUEST_METHOD': 'POST'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}, - body=json.dumps( - {'new_service': - {'new_endpoint': 'new_value'}}) - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_put_account_success(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating - # X-Container-Meta-Account-Id - ('204 No Content', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 201) - self.assertEquals(self.test_auth.app.calls, 5) - self.assertEquals(conn.calls, 1) - - def test_put_account_success_preexist_but_not_completed( - self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - # We're going to show it as existing this time, but with no - # X-Container-Meta-Account-Id, indicating a failed - # previous attempt - ('200 Ok', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating - # X-Container-Meta-Account-Id - ('204 No Content', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 201) - self.assertEquals(self.test_auth.app.calls, 4) - self.assertEquals(conn.calls, 1) - - def test_put_account_success_preexist_and_completed( - self): - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for pre-existence - # We're going to show it as existing this time, and with an - # X-Container-Meta-Account-Id, indicating it already - # exists - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 202) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_put_account_success_with_given_suffix(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating - # X-Container-Meta-Account-Id - ('204 No Content', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest', - 'X-Account-Suffix': 'test-suffix'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 201) - self.assertEquals( - conn.request_path, - '/v1/AUTH_test-suffix') - self.assertEquals(self.test_auth.app.calls, 5) - self.assertEquals(conn.calls, 1) - - def test_put_account_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'super:admin', - 'X-Auth-Admin-Key': 'supertest'},).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller - # admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'act:adm', - 'X-Auth-Admin-Key': 'key'},).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': 'act:usr', - 'X-Auth-Admin-Key': 'key'},).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_put_account_fail_invalid_account_name(self): - resp = Request.blank( - '/auth/v2/.act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'},).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_put_account_fail_on_initial_account_head(self): - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_put_account_fail_on_account_marker_put(self): - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_put_account_fail_on_storage_account_put(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('503 Service Unavailable', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(conn.calls, 1) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_put_account_fail_on_account_id_mapping(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(conn.calls, 1) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_put_account_fail_on_services_object(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', - 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(conn.calls, 1) - self.assertEquals(self.test_auth.app.calls, 4) - - def test_put_account_fail_on_post_mapping(self): - conn = FakeConn(iter([ - # PUT of storage account itself - ('201 Created', {}, '')])) - self.test_auth.get_conn = lambda: conn - self.test_auth.app = FakeApp(iter([ - # Initial HEAD of account container to check for - # pre-existence - ('404 Not Found', {}, ''), - # PUT of account container - ('204 No Content', {}, ''), - # PUT of .account_id mapping object - ('204 No Content', {}, ''), - # PUT of .services object - ('204 No Content', {}, ''), - # POST to account container updating - # X-Container-Meta-Account-Id - ('503 Service Unavailable', {}, '')])) - resp = Request.blank( - '/auth/v2/act', - environ={'REQUEST_METHOD': 'PUT', 'swift.cache': FakeMemcache()}, - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(conn.calls, 1) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_delete_account_success(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 6) - self.assertEquals(conn.calls, 1) - - def test_delete_account_success_missing_services(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('404 Not Found', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_delete_account_success_missing_storage_account( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('404 Not Found', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 6) - self.assertEquals(conn.calls, 1) - - def test_delete_account_success_missing_account_id_mapping( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('404 Not Found', {}, ''), - # DELETE the account container - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 6) - self.assertEquals(conn.calls, 1) - - def test_delete_account_success_missing_account_container_at_end( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 6) - self.assertEquals(conn.calls, 1) - - def test_delete_account_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - 'super:admin', - 'X-Auth-Admin-Key': 'supertest'}, - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller - # admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_account_fail_invalid_account_name(self): - resp = Request.blank('/auth/v2/.act', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_delete_account_fail_not_found(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_account_fail_not_found_concurrency( - self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_delete_account_fail_list_account(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_account_fail_list_account_concurrency( - self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_delete_account_fail_has_users(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}]))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 409) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_account_fail_has_users2(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}]))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 409) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_delete_account_fail_get_services(self): - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_delete_account_fail_delete_storage_account( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('409 Conflict', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 409) - self.assertEquals(self.test_auth.app.calls, 3) - self.assertEquals(conn.calls, 1) - - def test_delete_account_fail_delete_storage_account2( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, ''), - # DELETE of storage account itself - ('409 Conflict', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa", - "other": "http://127.0.0.1:8080/v1/AUTH_cfa2"}}))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - self.assertEquals(conn.calls, 2) - - def test_delete_account_fail_delete_storage_account3( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('503 Service Unavailable', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - self.assertEquals(conn.calls, 1) - - def test_delete_account_fail_delete_storage_account4( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, ''), - # DELETE of storage account itself - ('503 Service Unavailable', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa", - "other": "http://127.0.0.1:8080/v1/AUTH_cfa2"}}))])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - self.assertEquals(conn.calls, 2) - - def test_delete_account_fail_delete_services(self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 4) - self.assertEquals(conn.calls, 1) - - def test_delete_account_fail_delete_account_id_mapping( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 5) - self.assertEquals(conn.calls, 1) - - def test_delete_account_fail_delete_account_container( - self): - conn = FakeConn(iter([ - # DELETE of storage account itself - ('204 No Content', {}, '')])) - self.test_auth.get_conn = lambda x: conn - self.test_auth.app = FakeApp(iter([ - # Account's container listing, checking for - # users - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}])), - # Account's container listing, checking for users - # (continuation) - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]'), - # GET the .services object - ('200 Ok', {}, json.dumps( - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}})), - # DELETE the .services object - ('204 No Content', {}, ''), - # DELETE the .account_id mapping object - ('204 No Content', {}, ''), - # DELETE the account container - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act', - environ={ - 'REQUEST_METHOD': 'DELETE', - 'swift.cache': FakeMemcache()}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 6) - self.assertEquals(conn.calls, 1) - - def test_get_user_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(resp.body, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_user_fail_no_super_admin_key(self): - local_auth = auth.filter_factory({})(FakeApp(iter([ - # GET of user object (but we should never get - # here) - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"}))]))) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(local_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(local_auth.app.calls, 0) - - def test_get_user_groups_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester3"}, {"name": "act"}], - "auth": "plaintext:key3"})), - # GET of account container (list objects - # continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(resp.body, json.dumps( - {"groups": [{"name": ".admin"}, {"name": "act"}, - {"name": "act:tester"}, {"name": "act:tester3"}]})) - self.assertEquals(self.test_auth.app.calls, 4) - - def test_get_user_groups_success2(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}])), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of account container (list objects - # continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:tester3"}, {"name": "act"}], - "auth": "plaintext:key3"})), - # GET of account container (list objects - # continuation) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, '[]')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(resp.body, json.dumps( - {"groups": [{"name": ".admin"}, {"name": "act"}, - {"name": "act:tester"}, {"name": "act:tester3"}]})) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_user_fail_invalid_account(self): - resp = Request.blank('/auth/v2/.invalid/usr', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_get_user_fail_invalid_user(self): - resp = Request.blank('/auth/v2/act/.invalid', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_get_user_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - 'super:admin', - 'X-Auth-Admin-Key': 'supertest'}, - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'}, - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_user_account_admin_success(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller - # admin) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of requested user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(resp.body, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}], - "auth": "plaintext:key"})) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_get_user_account_admin_fail_getting_account_admin( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin check) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of requested user object [who is an .admin - # as well] - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of user object (reseller admin check [and fail - # here]) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_get_user_account_admin_fail_getting_reseller_admin( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin check) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"})), - # GET of requested user object [who is a - # .reseller_admin] - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_get_user_reseller_admin_fail_getting_reseller_admin( - self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin check) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".reseller_admin"}], - "auth": "plaintext:key"})), - # GET of requested user object [who also is a - # .reseller_admin] - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_get_user_super_admin_succeed_getting_reseller_admin( - self): - self.test_auth.app = FakeApp(iter([ - # GET of requested user object - ('200 Ok', {}, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assertEquals(resp.body, json.dumps( - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".reseller_admin"}], - "auth": "plaintext:key"})) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_user_groups_not_found(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_user_groups_fail_listing(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_user_groups_fail_get_user(self): - self.test_auth.app = FakeApp(iter([ - # GET of account container (list objects) - ('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, - json.dumps([ - {"name": ".services", "hash": "etag", "bytes": 112, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.618110"}, - {"name": "tester", "hash": "etag", "bytes": 104, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:27.736680"}, - {"name": "tester3", "hash": "etag", "bytes": 86, - "content_type": - "application/octet-stream", - "last_modified": "2010-12-03T17:16:28.135530"}])), - # GET of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/.groups', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_get_user_not_found(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_user_fail(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_put_user_fail_invalid_account(self): - resp = Request.blank('/auth/v2/.invalid/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_put_user_fail_invalid_user(self): - resp = Request.blank('/auth/v2/act/.usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_put_user_fail_no_user_key(self): - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_put_user_reseller_admin_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (reseller admin) - # This shouldn't actually get called, checked - # below - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:rdm"}, - {"name": "test"}, {"name": ".admin"}, - {"name": ".reseller_admin"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - 'act:rdm', - 'X-Auth-Admin-Key': - 'key', - 'X-Auth-User-Key': - 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 0) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but not reseller admin) - # This shouldn't actually get called, checked - # below - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': - 'key', - 'X-Auth-User-Key': - 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 0) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - # This shouldn't actually get called, checked - # below - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': - 'key', - 'X-Auth-User-Key': - 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 0) - - def test_put_user_account_admin_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but wrong - # account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - 'act2:adm', - 'X-Auth-Admin-Key': - 'key', - 'X-Auth-User-Key': - 'key', - 'X-Auth-User-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': - 'key', - 'X-Auth-User-Key': - 'key', - 'X-Auth-User-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_put_user_regular_fail_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but wrong - # account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - 'act2:adm', - 'X-Auth-Admin-Key': - 'key', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': - 'key', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_put_user_regular_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 201) - self.assertEquals(self.test_auth.app.calls, 2) - self.assertEquals( - json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:usr"}, {"name": "act"}], - "auth": "plaintext:key"}) - - def test_put_user_special_chars_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/u_s-r', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 201) - self.assertEquals(self.test_auth.app.calls, 2) - self.assertEquals( - json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:u_s-r"}, {"name": "act"}], - "auth": "plaintext:key"}) - - def test_put_user_account_admin_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 201) - self.assertEquals(self.test_auth.app.calls, 2) - self.assertEquals( - json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}], - "auth": "plaintext:key"}) - - def test_put_user_reseller_admin_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('201 Created', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key', - 'X-Auth-User-Reseller-Admin': 'true'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 201) - self.assertEquals(self.test_auth.app.calls, 2) - self.assertEquals( - json.loads(self.test_auth.app.request.body), - {"groups": [{"name": "act:usr"}, {"name": "act"}, - {"name": ".admin"}, {"name": ".reseller_admin"}], - "auth": "plaintext:key"}) - - def test_put_user_fail_not_found(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_put_user_fail(self): - self.test_auth.app = FakeApp(iter([ - # PUT of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'PUT'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': - 'supertest', - 'X-Auth-User-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_user_bad_creds(self): - self.test_auth.app = FakeApp(iter([ - # GET of user object (account admin, but wrong - # account) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, - {"name": "test"}, {"name": ".admin"}], - "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - 'act2:adm', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - self.test_auth.app = FakeApp(iter([ - # GET of user object (regular user) - ('200 Ok', {}, json.dumps({"groups": [{"name": "act:usr"}, - {"name": "test"}], "auth": "plaintext:key"}))])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 403) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_user_invalid_account(self): - resp = Request.blank('/auth/v2/.invalid/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_delete_user_invalid_user(self): - resp = Request.blank('/auth/v2/act/.invalid', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_delete_user_not_found(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_user_fail_head_user(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_delete_user_fail_delete_token(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('200 Ok', - {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_delete_user_fail_delete_user(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('200 Ok', - {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('204 No Content', {}, ''), - # DELETE of user object - ('503 Service Unavailable', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_delete_user_success(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('200 Ok', - {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('204 No Content', {}, ''), - # DELETE of user object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_delete_user_success_missing_user_at_end(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('200 Ok', - {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('204 No Content', {}, ''), - # DELETE of user object - ('404 Not Found', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_delete_user_success_missing_token(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('200 Ok', - {'X-Object-Meta-Auth-Token': 'AUTH_tk'}, ''), - # DELETE of token - ('404 Not Found', {}, ''), - # DELETE of user object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 3) - - def test_delete_user_success_no_token(self): - self.test_auth.app = FakeApp(iter([ - # HEAD of user object - ('200 Ok', {}, ''), - # DELETE of user object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/act/usr', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'} - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_validate_token_bad_prefix(self): - resp = Request.blank('/auth/v2/.token/BAD_token' - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_validate_token_tmi(self): - resp = Request.blank( - '/auth/v2/.token/AUTH_token/tmi').get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - - def test_validate_token_bad_memcache(self): - fake_memcache = FakeMemcache() - fake_memcache.set('AUTH_/auth/AUTH_token', 'bogus') - resp = Request.blank( - '/auth/v2/.token/AUTH_token', - environ={'swift.cache': fake_memcache}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 500) - - def test_validate_token_from_memcache(self): - fake_memcache = FakeMemcache() - fake_memcache.set( - 'AUTH_/auth/AUTH_token', - (time() + 1, - 'act:usr,act')) - resp = Request.blank( - '/auth/v2/.token/AUTH_token', - environ={'swift.cache': fake_memcache}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals( - resp.headers.get('x-auth-groups'), - 'act:usr,act') - self.assert_(float(resp.headers['x-auth-ttl']) < 1, - resp.headers['x-auth-ttl']) - - def test_validate_token_from_memcache_expired(self): - fake_memcache = FakeMemcache() - fake_memcache.set( - 'AUTH_/auth/AUTH_token', - (time() - 1, - 'act:usr,act')) - resp = Request.blank( - '/auth/v2/.token/AUTH_token', - environ={'swift.cache': fake_memcache}).get_response( - self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assert_('x-auth-groups' not in resp.headers) - self.assert_('x-auth-ttl' not in resp.headers) - - def test_validate_token_from_object(self): - self.test_auth.app = FakeApp(iter([ - # GET of token object - ('200 Ok', {}, json.dumps({'groups': [{'name': 'act:usr'}, - {'name': 'act'}], 'expires': time() + 1}))])) - resp = Request.blank('/auth/v2/.token/AUTH_token' - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 1) - self.assertEquals( - resp.headers.get('x-auth-groups'), - 'act:usr,act') - self.assert_(float(resp.headers['x-auth-ttl']) < 1, - resp.headers['x-auth-ttl']) - - def test_validate_token_from_object_expired(self): - self.test_auth.app = FakeApp(iter([ - # GET of token object - ('200 Ok', {}, json.dumps({'groups': 'act:usr,act', - 'expires': time() - 1})), - # DELETE of expired token object - ('204 No Content', {}, '')])) - resp = Request.blank('/auth/v2/.token/AUTH_token' - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertEquals(self.test_auth.app.calls, 2) - - def test_validate_token_from_object_with_admin(self): - self.test_auth.app = FakeApp(iter([ - # GET of token object - ('200 Ok', {}, json.dumps({'account_id': 'AUTH_cfa', 'groups': - [{'name': 'act:usr'}, - {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 1}))])) - resp = Request.blank('/auth/v2/.token/AUTH_token' - ).get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(self.test_auth.app.calls, 1) - self.assertEquals(resp.headers.get('x-auth-groups'), - 'act:usr,act,AUTH_cfa') - self.assert_(float(resp.headers['x-auth-ttl']) < 1, - resp.headers['x-auth-ttl']) - - def test_get_conn_default(self): - conn = self.test_auth.get_conn() - self.assertEquals( - conn.__class__, - auth.HTTPConnection) - self.assertEquals(conn.host, '127.0.0.1') - self.assertEquals(conn.port, 8080) - - def test_get_conn_default_https(self): - local_auth = auth.filter_factory( - {'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#https://1.2.3.4/v1'})(FakeApp()) - conn = local_auth.get_conn() - self.assertEquals( - conn.__class__, - auth.HTTPSConnection) - self.assertEquals(conn.host, '1.2.3.4') - self.assertEquals(conn.port, 443) - - def test_get_conn_overridden(self): - local_auth = auth.filter_factory( - {'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#https://1.2.3.4/v1'})(FakeApp()) - conn = \ - local_auth.get_conn( - urlparsed=auth.urlparse('http://5.6.7.8/v1')) - self.assertEquals( - conn.__class__, - auth.HTTPConnection) - self.assertEquals(conn.host, '5.6.7.8') - self.assertEquals(conn.port, 80) - - def test_get_conn_overridden_https(self): - local_auth = auth.filter_factory( - {'super_admin_key': 'supertest', - 'default_swift_cluster': 'local#http://1.2.3.4/v1'})(FakeApp()) - conn = \ - local_auth.get_conn( - urlparsed=auth.urlparse( - 'https://5.6.7.8/v1')) - self.assertEquals( - conn.__class__, - auth.HTTPSConnection) - self.assertEquals(conn.host, '5.6.7.8') - self.assertEquals(conn.port, 443) - - def test_get_itoken_fail_no_memcache(self): - exc = None - try: - self.test_auth.get_itoken({}) - except Exception as err: - exc = err - self.assertEquals(str(exc), - 'No memcache set up; required for Swauth middleware') - - def test_get_itoken_success(self): - fmc = FakeMemcache() - itk = self.test_auth.get_itoken( - {'swift.cache': fmc}) - self.assert_(itk.startswith('AUTH_itk'), itk) - expires, groups = fmc.get('AUTH_/auth/%s' % itk) - self.assert_(expires > time(), expires) - self.assertEquals( - groups, - '.auth,.reseller_admin,AUTH_.auth') - - def test_get_admin_detail_fail_no_colon(self): - self.test_auth.app = FakeApp(iter([])) - self.assertEquals( - self.test_auth.get_admin_detail( - Request.blank('/')), - None) - self.assertEquals( - self.test_auth.get_admin_detail( - Request.blank('/', - headers={'X-Auth-Admin-User': 'usr'})), None) - self.assertRaises( - StopIteration, self.test_auth.get_admin_detail, - Request.blank('/', headers={'X-Auth-Admin-User': 'act:usr'})) - - def test_get_admin_detail_fail_user_not_found(self): - self.test_auth.app = FakeApp( - iter([('404 Not Found', {}, '')])) - self.assertEquals( - self.test_auth.get_admin_detail( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'})), None) - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_admin_detail_fail_get_user_error(self): - self.test_auth.app = FakeApp(iter([ - ('503 Service Unavailable', {}, '')])) - exc = None - try: - self.test_auth.get_admin_detail( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'})) - except Exception as err: - exc = err - self.assertEquals(str(exc), 'Could not get admin user object: ' - '/v1/AUTH_.auth/act/usr 503 Service Unavailable') - self.assertEquals(self.test_auth.app.calls, 1) - - def test_get_admin_detail_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({"auth": "plaintext:key", - "groups": [{'name': "act:usr"}, {'name': "act"}, - {'name': ".admin"}]}))])) - detail = self.test_auth.get_admin_detail( - Request.blank('/', - headers={'X-Auth-Admin-User': 'act:usr'})) - self.assertEquals(self.test_auth.app.calls, 1) - self.assertEquals( - detail, {'account': 'act', - 'auth': 'plaintext:key', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}]}) - - def test_credentials_match_success(self): - self.assert_(self.test_auth.credentials_match( - {'auth': 'plaintext:key'}, 'key')) - - def test_credentials_match_fail_no_details(self): - self.assert_( - not self.test_auth.credentials_match(None, 'notkey')) - - def test_credentials_match_fail_plaintext(self): - self.assert_(not self.test_auth.credentials_match( - {'auth': 'plaintext:key'}, 'notkey')) - - def test_is_super_admin_success(self): - self.assert_( - self.test_auth.is_super_admin( - Request.blank( - '/', - headers={'X-Auth-Admin-User': '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}))) - - def test_is_super_admin_fail_bad_key(self): - self.assert_( - not self.test_auth.is_super_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'bad'}))) - self.assert_( - not self.test_auth.is_super_admin( - Request.blank('/', - headers={'X-Auth-Admin-User': '.super_admin'}))) - self.assert_( - not self.test_auth.is_super_admin(Request.blank('/'))) - - def test_is_super_admin_fail_bad_user(self): - self.assert_( - not self.test_auth.is_super_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'bad', - 'X-Auth-Admin-Key': 'supertest'}))) - self.assert_( - not self.test_auth.is_super_admin( - Request.blank('/', - headers={'X-Auth-Admin-Key': 'supertest'}))) - self.assert_( - not self.test_auth.is_super_admin(Request.blank('/'))) - - def test_is_reseller_admin_success_is_super_admin(self): - self.assert_( - self.test_auth.is_reseller_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}))) - - def test_is_reseller_admin_success_called_get_admin_detail( - self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assert_( - self.test_auth.is_reseller_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:rdm', - 'X-Auth-Admin-Key': 'key'}))) - - def test_is_reseller_admin_fail_only_account_admin( - self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:adm'}, {'name': 'act'}, - {'name': '.admin'}]}))])) - self.assert_( - not self.test_auth.is_reseller_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'}))) - - def test_is_reseller_admin_fail_regular_user(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}]}))])) - self.assert_( - not self.test_auth.is_reseller_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'}))) - - def test_is_reseller_admin_fail_bad_key(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assert_( - not self.test_auth.is_reseller_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:rdm', - 'X-Auth-Admin-Key': 'bad'}))) - - def test_is_account_admin_success_is_super_admin(self): - self.assert_( - self.test_auth.is_account_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - '.super_admin', - 'X-Auth-Admin-Key': 'supertest'}), 'act')) - - def test_is_account_admin_success_is_reseller_admin( - self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assert_( - self.test_auth.is_account_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:rdm', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_success(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:adm'}, {'name': 'act'}, - {'name': '.admin'}]}))])) - self.assert_( - self.test_auth.is_account_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:adm', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_fail_account_admin_different_account( - self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act2:adm'}, {'name': 'act2'}, - {'name': '.admin'}]}))])) - self.assert_( - not self.test_auth.is_account_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act2:adm', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_fail_regular_user(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}]}))])) - self.assert_( - not self.test_auth.is_account_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:usr', - 'X-Auth-Admin-Key': 'key'}), 'act')) - - def test_is_account_admin_fail_bad_key(self): - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps({'auth': 'plaintext:key', - 'groups': [{'name': 'act:rdm'}, {'name': 'act'}, - {'name': '.admin'}, - {'name': '.reseller_admin'}]}))])) - self.assert_( - not self.test_auth.is_account_admin( - Request.blank('/', - headers={ - 'X-Auth-Admin-User': - 'act:rdm', - 'X-Auth-Admin-Key': 'bad'}), 'act')) - - def test_reseller_admin_but_account_is_internal_use_only( - self): - req = Request.blank('/v1/AUTH_.auth', - environ={'REQUEST_METHOD': 'GET'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_reseller_admin_but_account_is_exactly_reseller_prefix( - self): - req = Request.blank( - '/v1/AUTH_', - environ={'REQUEST_METHOD': 'GET'}) - req.remote_user = 'act:usr,act,.reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def _get_token_success_v1_0_encoded( - self, saved_user, saved_key, sent_user, - sent_key): - self.test_auth.app = FakeApp(iter([ - # GET of user object - ('200 Ok', {}, - json.dumps({"auth": "plaintext:%s" % saved_key, - "groups": [{'name': saved_user}, {'name': "act"}, - {'name': ".admin"}]})), - # GET of account - ('204 Ok', - {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''), - # PUT of new token - ('201 Created', {}, ''), - # POST of token to user object - ('204 No Content', {}, ''), - # GET of services object - ('200 Ok', {}, json.dumps({"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}))])) - resp = Request.blank( - '/auth/v1.0', - headers={'X-Auth-User': sent_user, - 'X-Auth-Key': sent_key}).get_response(self.test_auth) - self.assertEquals(resp.status_int, 200) - self.assert_( - resp.headers.get('x-auth-token', - '').startswith('AUTH_tk'), - resp.headers.get('x-auth-token')) - self.assertEquals(resp.headers.get('x-auth-token'), - resp.headers.get('x-storage-token')) - self.assertEquals(resp.headers.get('x-storage-url'), - 'http://127.0.0.1:8080/v1/AUTH_cfa') - self.assertEquals( - json.loads(resp.body), - {"storage": {"default": "local", - "local": "http://127.0.0.1:8080/v1/AUTH_cfa"}}) - self.assertEquals(self.test_auth.app.calls, 5) - - def test_get_token_success_v1_0_encoded1(self): - self._get_token_success_v1_0_encoded( - 'act:usr', 'key', 'act%3ausr', 'key') - - def test_get_token_success_v1_0_encoded2(self): - self._get_token_success_v1_0_encoded( - 'act:u s r', 'key', 'act%3au%20s%20r', 'key') - - def test_get_token_success_v1_0_encoded3(self): - self._get_token_success_v1_0_encoded( - 'act:u s r', 'k:e:y', 'act%3au%20s%20r', 'k%3Ae%3ay') - - def test_allowed_sync_hosts(self): - a = auth.filter_factory( - {'super_admin_key': 'supertest'})(FakeApp()) - self.assertEquals( - a.allowed_sync_hosts, - ['127.0.0.1']) - a = auth.filter_factory({ - 'super_admin_key': 'supertest', - 'allowed_sync_hosts': - '1.1.1.1,2.1.1.1, 3.1.1.1 , 4.1.1.1,, , 5.1.1.1'})(FakeApp()) - self.assertEquals( - a.allowed_sync_hosts, - ['1.1.1.1', '2.1.1.1', '3.1.1.1', '4.1.1.1', '5.1.1.1']) - - def test_reseller_admin_is_owner(self): - orig_authorize = self.test_auth.authorize - owner_values = [] - - def mitm_authorize(req): - rv = orig_authorize(req) - owner_values.append( - req.environ.get('swift_owner', False)) - return rv - - self.test_auth.authorize = mitm_authorize - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'other', 'user': 'other:usr', - 'account_id': 'AUTH_other', - 'groups': [{'name': 'other:usr'}, {'name': 'other'}, - {'name': '.reseller_admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - req = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(owner_values, [True]) - - def test_admin_is_owner(self): - orig_authorize = self.test_auth.authorize - owner_values = [] - - def mitm_authorize(req): - rv = orig_authorize(req) - owner_values.append( - req.environ.get('swift_owner', False)) - return rv - - self.test_auth.authorize = mitm_authorize - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': [{'name': 'act:usr'}, {'name': 'act'}, - {'name': '.admin'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')])) - req = Request.blank( - '/v1/AUTH_cfa', - headers={'X-Auth-Token': 'AUTH_t'}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(owner_values, [True]) - - def test_regular_is_not_owner(self): - orig_authorize = self.test_auth.authorize - owner_values = [] - - def mitm_authorize(req): - rv = orig_authorize(req) - owner_values.append( - req.environ.get('swift_owner', False)) - return rv - - self.test_auth.authorize = mitm_authorize - - self.test_auth.app = FakeApp(iter([ - ('200 Ok', {}, - json.dumps( - {'account': 'act', 'user': 'act:usr', - 'account_id': 'AUTH_cfa', - 'groups': - [{'name': 'act:usr'}, { - 'name': 'act'}], - 'expires': time() + 60})), - ('204 No Content', {}, '')]), acl='act:usr') - req = Request.blank('/v1/AUTH_cfa/c', - headers={'X-Auth-Token': 'AUTH_t'}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - self.assertEquals(owner_values, [False]) - - def test_sync_request_success(self): - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'x-container-sync-key': - 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - - def test_sync_request_fail_key(self): - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'x-container-sync-key': - 'wrongsecret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key='othersecret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'x-container-sync-key': - 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key=None) - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'x-container-sync-key': - 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_sync_request_fail_no_timestamp(self): - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={'x-container-sync-key': 'secret'}) - req.remote_addr = '127.0.0.1' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_sync_request_fail_sync_host(self): - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'x-container-sync-key': - 'secret', - 'x-timestamp': '123.456'}) - req.remote_addr = '127.0.0.2' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_sync_request_success_lb_sync_host(self): - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'x-container-sync-key': - 'secret', - 'x-timestamp': - '123.456', - 'x-forwarded-for': '127.0.0.1'}) - req.remote_addr = '127.0.0.2' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - - self.test_auth.app = FakeApp( - iter([('204 No Content', {}, '')]), - sync_key='secret') - req = Request.blank('/v1/AUTH_cfa/c/o', - environ={ - 'REQUEST_METHOD': 'DELETE'}, - headers={ - 'x-container-sync-key': - 'secret', - 'x-timestamp': - '123.456', - 'x-cluster-client-ip': '127.0.0.1'}) - req.remote_addr = '127.0.0.2' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 204) - - def _make_request(self, path, **kwargs): - req = Request.blank(path, **kwargs) - req.environ['swift.cache'] = FakeMemcache() - return req - - def test_override_asked_for_but_not_allowed(self): - self.test_auth = \ - auth.filter_factory( - {'allow_overrides': 'false'})(FakeApp()) - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - self.assertEquals(resp.environ['swift.authorize'], - self.test_auth.authorize) - - def test_override_asked_for_and_allowed(self): - self.test_auth = \ - auth.filter_factory( - {'allow_overrides': 'true'})(FakeApp()) - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertTrue( - 'swift.authorize' not in resp.environ) - - def test_override_default_allowed(self): - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertTrue( - 'swift.authorize' not in resp.environ) - - def test_token_too_long(self): - req = self._make_request('/v1/AUTH_account', headers={ - 'x-auth-token': 'a' * MAX_TOKEN_LENGTH}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - self.assertNotEquals( - resp.body, - 'Token exceeds maximum length.') - req = self._make_request('/v1/AUTH_account', headers={ - 'x-auth-token': 'a' * (MAX_TOKEN_LENGTH + 1)}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 400) - self.assertEquals( - resp.body, - 'Token exceeds maximum length.') - - def test_crazy_authorization(self): - req = self._make_request('/v1/AUTH_account', headers={ - 'authorization': 'somebody elses header value'}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - self.assertEquals(resp.environ['swift.authorize'], - self.test_auth.denied_response) - - -if __name__ == '__main__': - unittest.main() diff --git a/gluster/swift/common/middleware/swiftkerbauth/__init__.py b/gluster/swift/common/middleware/swiftkerbauth/__init__.py deleted file mode 100644 index c752df7..0000000 --- a/gluster/swift/common/middleware/swiftkerbauth/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2013 Red Hat, Inc. -# -# 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. - -from swift.common.utils import readconf, config_true_value - -config_file = {} -try: - config_file = readconf("/etc/swift/proxy-server.conf", - section_name="filter:cache") -except SystemExit: - pass - -MEMCACHE_SERVERS = config_file.get('memcache_servers', None) - -config_file = {} - -try: - config_file = readconf("/etc/swift/proxy-server.conf", - section_name="filter:kerbauth") -except SystemExit: - pass - -TOKEN_LIFE = int(config_file.get('token_life', 86400)) -RESELLER_PREFIX = config_file.get('reseller_prefix', "AUTH_") -DEBUG_HEADERS = config_true_value(config_file.get('debug_headers', 'yes')) diff --git a/gluster/swift/common/middleware/swiftkerbauth/apachekerbauth/etc/httpd/conf.d/swift-auth.conf b/gluster/swift/common/middleware/swiftkerbauth/apachekerbauth/etc/httpd/conf.d/swift-auth.conf deleted file mode 100644 index 68472d8..0000000 --- a/gluster/swift/common/middleware/swiftkerbauth/apachekerbauth/etc/httpd/conf.d/swift-auth.conf +++ /dev/null @@ -1,12 +0,0 @@ -<Location /cgi-bin/swift-auth> - AuthType Kerberos - AuthName "Swift Authentication" - KrbMethodNegotiate On - KrbMethodK5Passwd On - KrbSaveCredentials On - KrbServiceName HTTP/client.example.com - KrbAuthRealms EXAMPLE.COM - Krb5KeyTab /etc/httpd/conf/http.keytab - KrbVerifyKDC Off - Require valid-user -</Location> diff --git a/gluster/swift/common/middleware/swiftkerbauth/apachekerbauth/var/www/cgi-bin/swift-auth b/gluster/swift/common/middleware/swiftkerbauth/apachekerbauth/var/www/cgi-bin/swift-auth deleted file mode 100755 index 11fe0e2..0000000 --- a/gluster/swift/common/middleware/swiftkerbauth/apachekerbauth/var/www/cgi-bin/swift-auth +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/python - -# Copyright (c) 2013 Red Hat, Inc. -# -# 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. - -# Requires the following command to be run: -# setsebool -P httpd_can_network_connect 1 -# setsebool -P httpd_can_network_memcache 1 - -import os -import cgi -from swift.common.memcached import MemcacheRing -from time import time, ctime -from swiftkerbauth import MEMCACHE_SERVERS, TOKEN_LIFE, DEBUG_HEADERS -from swiftkerbauth.kerbauth_utils import get_remote_user, get_auth_data, \ - generate_token, set_auth_data, get_groups_from_username - - -def main(): - try: - username = get_remote_user(os.environ) - except RuntimeError: - print "Status: 401 Unauthorized\n" - print "Malformed REMOTE_USER" - return - - if not MEMCACHE_SERVERS: - print "Status: 500 Internal Server Error\n" - print "Memcache not configured in /etc/swift/proxy-server.conf" - return - - mc_servers = [s.strip() for s in MEMCACHE_SERVERS.split(',') if s.strip()] - mc = MemcacheRing(mc_servers) - - token, expires, groups = get_auth_data(mc, username) - - if not token: - token = generate_token() - expires = time() + TOKEN_LIFE - groups = get_groups_from_username(username) - set_auth_data(mc, username, token, expires, groups) - - print "X-Auth-Token: %s" % token - print "X-Storage-Token: %s" % token - - # For debugging. - if DEBUG_HEADERS: - print "X-Debug-Remote-User: %s" % username - print "X-Debug-Groups: %s" % groups - print "X-Debug-Token-Life: %ss" % TOKEN_LIFE - print "X-Debug-Token-Expires: %s" % ctime(expires) - - print "" - -try: - print("Content-Type: text/html") - main() -except: - cgi.print_exception() diff --git a/gluster/swift/common/middleware/swiftkerbauth/kerbauth.py b/gluster/swift/common/middleware/swiftkerbauth/kerbauth.py deleted file mode 100644 index 1a63a40..0000000 --- a/gluster/swift/common/middleware/swiftkerbauth/kerbauth.py +++ /dev/null @@ -1,463 +0,0 @@ -# Copyright (c) 2013 Red Hat, Inc. -# -# 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. - -import errno -from time import time, ctime -from traceback import format_exc -from eventlet import Timeout -from urllib import unquote - -from swift.common.swob import Request, Response -from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \ - HTTPSeeOther, HTTPUnauthorized, HTTPServerError - -from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed -from swift.common.utils import cache_from_env, get_logger, \ - split_path, config_true_value -from gluster.swift.common.middleware.swiftkerbauth.kerbauth_utils import \ - get_auth_data, generate_token, \ - set_auth_data, run_kinit, get_groups_from_username - - -class KerbAuth(object): - """ - Test authentication and authorization system. - - Add to your pipeline in proxy-server.conf, such as:: - - [pipeline:main] - pipeline = catch_errors cache kerbauth proxy-server - - Set account auto creation to true in proxy-server.conf:: - - [app:proxy-server] - account_autocreate = true - - And add a kerbauth filter section, such as:: - - [filter:kerbauth] - use = egg:swiftkerbauth#kerbauth - - See the proxy-server.conf-sample for more information. - - :param app: The next WSGI app in the pipeline - :param conf: The dict of configuration values - """ - - def __init__(self, app, conf): - self.app = app - self.conf = conf - self.logger = get_logger(conf, log_route='kerbauth') - self.log_headers = config_true_value(conf.get('log_headers', 'f')) - self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip() - if self.reseller_prefix and self.reseller_prefix[-1] != '_': - self.reseller_prefix += '_' - self.logger.set_statsd_prefix('kerbauth.%s' % ( - self.reseller_prefix if self.reseller_prefix else 'NONE',)) - self.auth_prefix = conf.get('auth_prefix', '/auth/') - if not self.auth_prefix or not self.auth_prefix.strip('/'): - self.logger.warning('Rewriting invalid auth prefix "%s" to ' - '"/auth/" (Non-empty auth prefix path ' - 'is required)' % self.auth_prefix) - self.auth_prefix = '/auth/' - if self.auth_prefix[0] != '/': - self.auth_prefix = '/' + self.auth_prefix - if self.auth_prefix[-1] != '/': - self.auth_prefix += '/' - self.token_life = int(conf.get('token_life', 86400)) - self.auth_method = conf.get('auth_method', 'passive') - self.debug_headers = config_true_value( - conf.get('debug_headers', 'yes')) - self.realm_name = conf.get('realm_name', None) - self.allow_overrides = config_true_value( - conf.get('allow_overrides', 't')) - self.storage_url_scheme = conf.get('storage_url_scheme', 'default') - self.ext_authentication_url = conf.get('ext_authentication_url') - if not self.ext_authentication_url: - raise RuntimeError("Missing filter parameter ext_authentication_" - "url in /etc/swift/proxy-server.conf") - - def __call__(self, env, start_response): - """ - Accepts a standard WSGI application call, authenticating the request - and installing callback hooks for authorization and ACL header - validation. For an authenticated request, REMOTE_USER will be set to a - comma separated list of the user's groups. - - If the request matches the self.auth_prefix, the request will be - routed through the internal auth request handler (self.handle). - This is to handle granting tokens, etc. - """ - if self.allow_overrides and env.get('swift.authorize_override', False): - return self.app(env, start_response) - if env.get('PATH_INFO', '').startswith(self.auth_prefix): - return self.handle(env, start_response) - token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) - if token and token.startswith(self.reseller_prefix): - groups = self.get_groups(env, token) - if groups: - user = groups and groups.split(',', 1)[0] or '' - trans_id = env.get('swift.trans_id') - self.logger.debug('User: %s uses token %s (trans_id %s)' % - (user, token, trans_id)) - env['REMOTE_USER'] = groups - env['swift.authorize'] = self.authorize - env['swift.clean_acl'] = clean_acl - if '.reseller_admin' in groups: - env['reseller_request'] = True - else: - # Invalid token (may be expired) - if self.auth_method == "active": - return HTTPSeeOther( - location=self.ext_authentication_url)(env, - start_response) - elif self.auth_method == "passive": - self.logger.increment('unauthorized') - return HTTPUnauthorized()(env, start_response) - else: - # With a non-empty reseller_prefix, I would like to be called - # back for anonymous access to accounts I know I'm the - # definitive auth for. - try: - version, rest = split_path(env.get('PATH_INFO', ''), - 1, 2, True) - except ValueError: - version, rest = None, None - self.logger.increment('errors') - # Not my token, not my account, I can't authorize this request, - # deny all is a good idea if not already set... - if 'swift.authorize' not in env: - env['swift.authorize'] = self.denied_response - - return self.app(env, start_response) - - def get_groups(self, env, token): - """ - Get groups for the given token. - - :param env: The current WSGI environment dictionary. - :param token: Token to validate and return a group string for. - - :returns: None if the token is invalid or a string containing a comma - separated list of groups the authenticated user is a member - of. The first group in the list is also considered a unique - identifier for that user. - """ - groups = None - memcache_client = cache_from_env(env) - if not memcache_client: - raise Exception('Memcache required') - memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token) - cached_auth_data = memcache_client.get(memcache_token_key) - if cached_auth_data: - expires, groups = cached_auth_data - if expires < time(): - groups = None - - return groups - - def authorize(self, req): - """ - Returns None if the request is authorized to continue or a standard - WSGI response callable if not. - - Assumes that user groups are all lower case, which is true when Red Hat - Enterprise Linux Identity Management is used. - """ - try: - version, account, container, obj = req.split_path(1, 4, True) - except ValueError: - self.logger.increment('errors') - return HTTPNotFound(request=req) - - if not account or not account.startswith(self.reseller_prefix): - self.logger.debug("Account name: %s doesn't start with " - "reseller_prefix: %s." - % (account, self.reseller_prefix)) - return self.denied_response(req) - - user_groups = (req.remote_user or '').split(',') - account_user = user_groups[1] if len(user_groups) > 1 else None - # If the user is in the reseller_admin group for our prefix, he gets - # full access to all accounts we manage. For the default reseller - # prefix, the group name is auth_reseller_admin. - admin_group = ("%sreseller_admin" % self.reseller_prefix).lower() - if admin_group in user_groups and \ - account != self.reseller_prefix and \ - account[len(self.reseller_prefix)] != '.': - req.environ['swift_owner'] = True - return None - - # The "account" is part of the request URL, and already contains the - # reseller prefix, like in "/v1/AUTH_vol1/pictures/pic1.png". - if account.lower() in user_groups and \ - (req.method not in ('DELETE', 'PUT') or container): - # If the user is admin for the account and is not trying to do an - # account DELETE or PUT... - req.environ['swift_owner'] = True - self.logger.debug("User %s has admin authorizing." - % account_user) - return None - - if (req.environ.get('swift_sync_key') - and (req.environ['swift_sync_key'] == - req.headers.get('x-container-sync-key', None)) - and 'x-timestamp' in req.headers): - self.logger.debug("Allow request with container sync-key: %s." - % req.environ['swift_sync_key']) - return None - - if req.method == 'OPTIONS': - #allow OPTIONS requests to proceed as normal - self.logger.debug("Allow OPTIONS request.") - return None - - referrers, groups = parse_acl(getattr(req, 'acl', None)) - - if referrer_allowed(req.referer, referrers): - if obj or '.rlistings' in groups: - self.logger.debug("Allow authorizing %s via referer ACL." - % req.referer) - return None - - for user_group in user_groups: - if user_group in groups: - self.logger.debug("User %s allowed in ACL: %s authorizing." - % (account_user, user_group)) - return None - - return self.denied_response(req) - - def denied_response(self, req): - """ - Returns a standard WSGI response callable with the status of 403 or 401 - depending on whether the REMOTE_USER is set or not. - """ - if req.remote_user: - self.logger.increment('forbidden') - return HTTPForbidden(request=req) - else: - if self.auth_method == "active": - return HTTPSeeOther(location=self.ext_authentication_url) - elif self.auth_method == "passive": - self.logger.increment('unauthorized') - return HTTPUnauthorized(request=req) - - def handle(self, env, start_response): - """ - WSGI entry point for auth requests (ones that match the - self.auth_prefix). - Wraps env in swob.Request object and passes it down. - - :param env: WSGI environment dictionary - :param start_response: WSGI callable - """ - try: - req = Request(env) - if self.auth_prefix: - req.path_info_pop() - req.bytes_transferred = '-' - req.client_disconnect = False - if 'x-storage-token' in req.headers and \ - 'x-auth-token' not in req.headers: - req.headers['x-auth-token'] = req.headers['x-storage-token'] - return self.handle_request(req)(env, start_response) - except (Exception, Timeout): - print "EXCEPTION IN handle: %s: %s" % (format_exc(), env) - self.logger.increment('errors') - start_response('500 Server Error', - [('Content-Type', 'text/plain')]) - return ['Internal server error.\n'] - - def handle_request(self, req): - """ - Entry point for auth requests (ones that match the self.auth_prefix). - Should return a WSGI-style callable (such as webob.Response). - - :param req: swob.Request object - """ - req.start_time = time() - handler = None - try: - version, account, user, _junk = req.split_path(1, 4, True) - except ValueError: - self.logger.increment('errors') - return HTTPNotFound(request=req) - if version in ('v1', 'v1.0', 'auth'): - if req.method == 'GET': - handler = self.handle_get_token - if not handler: - self.logger.increment('errors') - req.response = HTTPBadRequest(request=req) - else: - req.response = handler(req) - return req.response - - def handle_get_token(self, req): - """ - Handles the various `request for token and service end point(s)` calls. - There are various formats to support the various auth servers in the - past. - - "Active Mode" usage: - All formats require GSS (Kerberos) authentication. - - GET <auth-prefix>/v1/<act>/auth - GET <auth-prefix>/auth - GET <auth-prefix>/v1.0 - - On successful authentication, the response will have X-Auth-Token - and X-Storage-Token set to the token to use with Swift. - - "Passive Mode" usage:: - - GET <auth-prefix>/v1/<act>/auth - X-Auth-User: <act>:<usr> or X-Storage-User: <usr> - X-Auth-Key: <key> or X-Storage-Pass: <key> - GET <auth-prefix>/auth - X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr> - X-Auth-Key: <key> or X-Storage-Pass: <key> - GET <auth-prefix>/v1.0 - X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr> - X-Auth-Key: <key> or X-Storage-Pass: <key> - - Values should be url encoded, "act%3Ausr" instead of "act:usr" for - example; however, for backwards compatibility the colon may be - included unencoded. - - On successful authentication, the response will have X-Auth-Token - and X-Storage-Token set to the token to use with Swift and - X-Storage-URL set to the URL to the default Swift cluster to use. - - :param req: The swob.Request to process. - :returns: swob.Response, 2xx on success with data set as explained - above. - """ - # Validate the request info - try: - pathsegs = split_path(req.path_info, 1, 3, True) - except ValueError: - self.logger.increment('errors') - return HTTPNotFound(request=req) - if not ((pathsegs[0] == 'v1' and pathsegs[2] == 'auth') - or pathsegs[0] in ('auth', 'v1.0')): - return HTTPBadRequest(request=req) - - # Client is inside the domain - if self.auth_method == "active": - return HTTPSeeOther(location=self.ext_authentication_url) - - # Client is outside the domain - elif self.auth_method == "passive": - account, user, key = None, None, None - # Extract user, account and key from request - if pathsegs[0] == 'v1' and pathsegs[2] == 'auth': - account = pathsegs[1] - user = req.headers.get('x-storage-user') - if not user: - user = unquote(req.headers.get('x-auth-user', '')) - if user: - if ':' not in user: - return HTTPUnauthorized(request=req) - else: - account2, user = user.split(':', 1) - if account != account2: - return HTTPUnauthorized(request=req) - key = req.headers.get('x-storage-pass') - if not key: - key = unquote(req.headers.get('x-auth-key', '')) - elif pathsegs[0] in ('auth', 'v1.0'): - user = unquote(req.headers.get('x-auth-user', '')) - if not user: - user = req.headers.get('x-storage-user') - if user: - if ':' not in user: - return HTTPUnauthorized(request=req) - else: - account, user = user.split(':', 1) - key = unquote(req.headers.get('x-auth-key', '')) - if not key: - key = req.headers.get('x-storage-pass') - - if not (account or user or key): - # If all are not given, client may be part of the domain - return HTTPSeeOther(location=self.ext_authentication_url) - elif None in (key, user, account): - # If only one or two of them is given, but not all - return HTTPUnauthorized(request=req) - - # Run kinit on the user - if self.realm_name and "@" not in user: - user = user + "@" + self.realm_name - try: - ret = run_kinit(user, key) - except OSError as e: - if e.errno == errno.ENOENT: - return HTTPServerError("kinit command not found\n") - if ret != 0: - self.logger.warning("Failed: kinit %s", user) - if ret == -1: - self.logger.warning("Failed: kinit: Password has probably " - "expired.") - return HTTPServerError("Kinit is taking too long.\n") - return HTTPUnauthorized(request=req) - self.logger.debug("kinit succeeded") - - if "@" in user: - user = user.split("@")[0] - - # Check if user really belongs to the account - groups_list = get_groups_from_username(user).strip().split(",") - user_group = ("%s%s" % (self.reseller_prefix, account)).lower() - reseller_admin_group = \ - ("%sreseller_admin" % self.reseller_prefix).lower() - if user_group not in groups_list: - # Check if user is reseller_admin. If not, return Unauthorized. - # On AD/IdM server, auth_reseller_admin is a separate group - if reseller_admin_group not in groups_list: - return HTTPUnauthorized(request=req) - - mc = cache_from_env(req.environ) - if not mc: - raise Exception('Memcache required') - token, expires, groups = get_auth_data(mc, user) - if not token: - token = generate_token() - expires = time() + self.token_life - groups = get_groups_from_username(user) - set_auth_data(mc, user, token, expires, groups) - - headers = {'X-Auth-Token': token, - 'X-Storage-Token': token} - - if self.debug_headers: - headers.update({'X-Debug-Remote-User': user, - 'X-Debug-Groups:': groups, - 'X-Debug-Token-Life': self.token_life, - 'X-Debug-Token-Expires': ctime(expires)}) - - resp = Response(request=req, headers=headers) - resp.headers['X-Storage-Url'] = \ - '%s/v1/%s%s' % (resp.host_url, self.reseller_prefix, account) - return resp - - -def filter_factory(global_conf, **local_conf): - """Returns a WSGI filter app for use with paste.deploy.""" - conf = global_conf.copy() - conf.update(local_conf) - - def auth_filter(app): - return KerbAuth(app, conf) - return auth_filter diff --git a/gluster/swift/common/middleware/swiftkerbauth/kerbauth_utils.py b/gluster/swift/common/middleware/swiftkerbauth/kerbauth_utils.py deleted file mode 100644 index 599ef99..0000000 --- a/gluster/swift/common/middleware/swiftkerbauth/kerbauth_utils.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (c) 2013 Red Hat, Inc. -# -# 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. - -import re -import random -import grp -import signal -from subprocess import Popen, PIPE -from time import time -from gluster.swift.common.middleware.swiftkerbauth \ - import TOKEN_LIFE, RESELLER_PREFIX - - -def get_remote_user(env): - """Retrieve REMOTE_USER set by Apache from environment.""" - remote_user = env.get('REMOTE_USER', "") - matches = re.match('([^@]+)@.*', remote_user) - if not matches: - raise RuntimeError("Malformed REMOTE_USER \"%s\"" % remote_user) - return matches.group(1) - - -def get_auth_data(mc, username): - """ - Returns the token, expiry time and groups for the user if it already exists - on memcache. Returns None otherwise. - - :param mc: MemcacheRing object - :param username: swift user - """ - token, expires, groups = None, None, None - memcache_user_key = '%s/user/%s' % (RESELLER_PREFIX, username) - candidate_token = mc.get(memcache_user_key) - if candidate_token: - memcache_token_key = '%s/token/%s' % (RESELLER_PREFIX, candidate_token) - cached_auth_data = mc.get(memcache_token_key) - if cached_auth_data: - expires, groups = cached_auth_data - if expires > time(): - token = candidate_token - else: - expires, groups = None, None - return (token, expires, groups) - - -def set_auth_data(mc, username, token, expires, groups): - """ - Stores the following key value pairs on Memcache: - (token, expires+groups) - (user, token) - """ - auth_data = (expires, groups) - memcache_token_key = "%s/token/%s" % (RESELLER_PREFIX, token) - mc.set(memcache_token_key, auth_data, time=TOKEN_LIFE) - - # Record the token with the user info for future use. - memcache_user_key = '%s/user/%s' % (RESELLER_PREFIX, username) - mc.set(memcache_user_key, token, time=TOKEN_LIFE) - - -def generate_token(): - """Generates a random token.""" - # We don't use uuid.uuid4() here because importing the uuid module - # causes (harmless) SELinux denials in the audit log on RHEL 6. If this - # is a security concern, a custom SELinux policy module could be - # written to not log those denials. - r = random.SystemRandom() - token = '%stk%s' % \ - (RESELLER_PREFIX, - ''.join(r.choice('abcdef0123456789') for x in range(32))) - return token - - -def get_groups_from_username(username): - """Return a set of groups to which the user belongs to.""" - # Retrieve the numerical group IDs. We cannot list the group names - # because group names from Active Directory may contain spaces, and - # we wouldn't be able to split the list of group names into its - # elements. - p = Popen(['id', '-G', username], stdout=PIPE) - if p.wait() != 0: - raise RuntimeError("Failure running id -G for %s" % username) - (p_stdout, p_stderr) = p.communicate() - - # Convert the group numbers into group names. - groups = [] - for gid in p_stdout.strip().split(" "): - groups.append(grp.getgrgid(int(gid))[0]) - - # The first element of the list is considered a unique identifier - # for the user. We add the username to accomplish this. - if username in groups: - groups.remove(username) - groups = [username] + groups - groups = ','.join(groups) - return groups - - -def run_kinit(username, password): - """Runs kinit command as a child process and returns the status code.""" - kinit = Popen(['kinit', username], - stdin=PIPE, stdout=PIPE, stderr=PIPE) - kinit.stdin.write('%s\n' % password) - - # The following code handles a corner case where the Kerberos password - # has expired and a prompt is displayed to enter new password. Ideally, - # we would want to read from stdout but these are blocked reads. This is - # a hack to kill the process if it's taking too long! - - class Alarm(Exception): - pass - - def signal_handler(signum, frame): - raise Alarm - # Set the signal handler and a 1-second alarm - signal.signal(signal.SIGALRM, signal_handler) - signal.alarm(1) - try: - kinit.wait() # Wait for the child to exit - signal.alarm(0) # Reset the alarm - return kinit.returncode # Exit status of child on graceful exit - except Alarm: - # Taking too long, kill and return error - kinit.kill() - return -1 diff --git a/gluster/swift/common/utils.py b/gluster/swift/common/utils.py index 145add3..95c8739 100644 --- a/gluster/swift/common/utils.py +++ b/gluster/swift/common/utils.py @@ -47,6 +47,7 @@ DIR_NON_OBJECT = 'dir' DIR_OBJECT = 'marker_dir' TEMP_DIR = 'tmp' ASYNCDIR = 'async_pending' # Keep in sync with swift.obj.server.ASYNCDIR +TRASHCAN = '.trashcan' FILE = 'file' FILE_TYPE = 'application/octet-stream' OBJECT = 'Object' @@ -212,7 +213,7 @@ def validate_account(metadata): return False -def validate_object(metadata): +def validate_object(metadata, stat=None): if not metadata: return False @@ -224,6 +225,12 @@ def validate_object(metadata): X_OBJECT_TYPE not in metadata.keys(): return False + if stat and (int(metadata[X_CONTENT_LENGTH]) != stat.st_size): + # File length has changed. + # TODO: Handle case where file content has changed but the length + # remains the same. + return False + if metadata[X_TYPE] == OBJECT: return True @@ -242,8 +249,16 @@ def _update_list(path, cont_path, src_list, reg_file=True, object_count=0, if not reg_file and not Glusterfs._implicit_dir_objects: # Now check if this is a dir object or a gratuiously crated # directory - metadata = \ - read_metadata(os.path.join(cont_path, obj_path, obj_name)) + try: + metadata = \ + read_metadata(os.path.join(cont_path, obj_path, obj_name)) + except GlusterFileSystemIOError as err: + if err.errno in (errno.ENOENT, errno.ESTALE): + # object might have been deleted by another process + # since the src_list was originally built + continue + else: + raise err if not dir_is_object(metadata): continue @@ -304,6 +319,7 @@ def get_account_details(acc_path): for name in do_listdir(acc_path): if name.lower() == TEMP_DIR \ or name.lower() == ASYNCDIR \ + or name.lower() == TRASHCAN \ or not do_isdir(os.path.join(acc_path, name)): continue container_count += 1 @@ -476,7 +492,7 @@ def rmobjdir(dir_path): try: do_rmdir(dir_path) except OSError as err: - if err.errno == errno.ENOENT: + if err.errno in (errno.ENOENT, errno.ESTALE): # No such directory exists return False if err.errno != errno.ENOTEMPTY: @@ -494,8 +510,8 @@ def rmobjdir(dir_path): try: metadata = read_metadata(fullpath) - except OSError as err: - if err.errno == errno.ENOENT: + except GlusterFileSystemIOError as err: + if err.errno in (errno.ENOENT, errno.ESTALE): # Ignore removal from another entity. continue raise @@ -513,7 +529,7 @@ def rmobjdir(dir_path): if err.errno == errno.ENOTEMPTY: # Directory is not empty, it might have objects in it return False - if err.errno == errno.ENOENT: + if err.errno in (errno.ENOENT, errno.ESTALE): # No such directory exists, already removed, ignore continue raise @@ -524,7 +540,7 @@ def rmobjdir(dir_path): if err.errno == errno.ENOTEMPTY: # Directory is not empty, race with object creation return False - if err.errno == errno.ENOENT: + if err.errno in (errno.ENOENT, errno.ESTALE): # No such directory exists, already removed, ignore return True raise diff --git a/gluster/swift/obj/diskfile.py b/gluster/swift/obj/diskfile.py index 47cde89..eb180a2 100644 --- a/gluster/swift/obj/diskfile.py +++ b/gluster/swift/obj/diskfile.py @@ -447,9 +447,9 @@ class DiskFileWriter(object): df._threadpool.force_run_in_thread(self._finalize_put, metadata) - # Avoid the unlink() system call as part of the mkstemp context - # cleanup - self.tmppath = None + # Avoid the unlink() system call as part of the DiskFile.create() + # context cleanup + self._tmppath = None class DiskFileReader(object): @@ -509,7 +509,7 @@ class DiskFileReader(object): bytes_read += len(chunk) diff = bytes_read - dropped_cache if diff > (1024 * 1024): - self._drop_cache(self._fd, dropped_cache, diff) + self._drop_cache(dropped_cache, diff) dropped_cache = bytes_read yield chunk if self._iter_hook: @@ -604,6 +604,7 @@ class DiskFile(object): self._logger = mgr.logger self._metadata = None self._fd = None + self._stat = None # Don't store a value for data_file until we know it exists. self._data_file = None @@ -644,37 +645,49 @@ class DiskFile(object): """ # Writes are always performed to a temporary file try: - fd = do_open(self._data_file, os.O_RDONLY | O_CLOEXEC) + self._fd = do_open(self._data_file, os.O_RDONLY | O_CLOEXEC) except GlusterFileSystemOSError as err: if err.errno in (errno.ENOENT, errno.ENOTDIR): # If the file does exist, or some part of the path does not # exist, raise the expected DiskFileNotExist raise DiskFileNotExist raise - else: - stats = do_fstat(fd) - if not stats: - return - self._is_dir = stat.S_ISDIR(stats.st_mode) - obj_size = stats.st_size - - self._metadata = read_metadata(fd) - if not validate_object(self._metadata): - create_object_metadata(fd) - self._metadata = read_metadata(fd) - assert self._metadata is not None - self._filter_metadata() - - if self._is_dir: - do_close(fd) - obj_size = 0 - self._fd = -1 - else: - if self._is_object_expired(self._metadata): - raise DiskFileExpired(metadata=self._metadata) - self._fd = fd - - self._obj_size = obj_size + try: + self._stat = do_fstat(self._fd) + self._is_dir = stat.S_ISDIR(self._stat.st_mode) + obj_size = self._stat.st_size + + self._metadata = read_metadata(self._fd) + if not validate_object(self._metadata, self._stat): + create_object_metadata(self._fd) + self._metadata = read_metadata(self._fd) + assert self._metadata is not None + self._filter_metadata() + + if self._is_dir: + do_close(self._fd) + obj_size = 0 + self._fd = -1 + else: + if self._is_object_expired(self._metadata): + raise DiskFileExpired(metadata=self._metadata) + self._obj_size = obj_size + except (OSError, IOError, DiskFileExpired) as err: + # Something went wrong. Context manager will not call + # __exit__. So we close the fd manually here. + self._close_fd() + if hasattr(err, 'errno') and \ + err.errno in (errno.ENOENT, errno.ESTALE): + # Handle races: ENOENT/ESTALE can be raised by read_metadata() + # call in GlusterFS if file gets deleted by another + # client after do_open() succeeds + logging.warn("open(%s) succeeded but one of the subsequent " + "syscalls failed with ENOENT/ESTALE. Raising " + "DiskFileNotExist." % (self._data_file)) + raise DiskFileNotExist + else: + # Re-raise the original exception after fd has been closed + raise err return self def _is_object_expired(self, metadata): @@ -712,6 +725,12 @@ class DiskFile(object): raise DiskFileNotOpen() return self + def _close_fd(self): + if self._fd is not None: + fd, self._fd = self._fd, None + if fd > -1: + do_close(fd) + def __exit__(self, t, v, tb): """ Context exit. @@ -723,10 +742,7 @@ class DiskFile(object): responsibility of the implementation to properly handle that. """ self._metadata = None - if self._fd is not None: - fd, self._fd = self._fd, None - if fd > -1: - do_close(fd) + self._close_fd() def get_metadata(self): """ @@ -833,10 +849,6 @@ class DiskFile(object): " on subpath: %s" % (full_path, cur_path)) child = stack.pop() if stack else None return True, newmd - # Exists, but as a file - #raise DiskFileError('DiskFile.put(): directory creation failed' - # ' since the target, %s, already exists as' - # ' a file' % df._data_file) @contextmanager def create(self, size=None): @@ -893,7 +905,7 @@ class DiskFile(object): if attempts >= MAX_OPEN_ATTEMPTS: # We failed after N attempts to create the temporary # file. - raise DiskFileError('DiskFile.mkstemp(): failed to' + raise DiskFileError('DiskFile.create(): failed to' ' successfully create a temporary file' ' without running into a name conflict' ' after %d of %d attempts for: %s' % ( @@ -906,7 +918,7 @@ class DiskFile(object): # FIXME: Possible FUSE issue or race condition, let's # sleep on it and retry the operation. _random_sleep() - logging.warn("DiskFile.mkstemp(): %s ... retrying in" + logging.warn("DiskFile.create(): %s ... retrying in" " 0.1 secs", gerr) attempts += 1 elif not self._obj_path: @@ -915,7 +927,7 @@ class DiskFile(object): # could be a FUSE issue or some race condition, so let's # sleep a bit and retry. _random_sleep() - logging.warn("DiskFile.mkstemp(): %s ... retrying in" + logging.warn("DiskFile.create(): %s ... retrying in" " 0.1 secs", gerr) attempts += 1 elif attempts > 1: @@ -923,7 +935,7 @@ class DiskFile(object): # also be a FUSE issue or some race condition, nap and # retry. _random_sleep() - logging.warn("DiskFile.mkstemp(): %s ... retrying in" + logging.warn("DiskFile.create(): %s ... retrying in" " 0.1 secs" % gerr) attempts += 1 else: diff --git a/glusterfs-openstack-swift.spec b/glusterfs-openstack-swift.spec index bca64c3..79defc4 100644 --- a/glusterfs-openstack-swift.spec +++ b/glusterfs-openstack-swift.spec @@ -24,11 +24,11 @@ Requires : memcached Requires : openssl Requires : python Requires : python-prettytable -Requires : openstack-swift = 1.10.0 -Requires : openstack-swift-account = 1.10.0 -Requires : openstack-swift-container = 1.10.0 -Requires : openstack-swift-object = 1.10.0 -Requires : openstack-swift-proxy = 1.10.0 +Requires : openstack-swift = 1.13.1 +Requires : openstack-swift-account = 1.13.1 +Requires : openstack-swift-container = 1.13.1 +Requires : openstack-swift-object = 1.13.1 +Requires : openstack-swift-proxy = 1.13.1 Requires : glusterfs-api >= 3.4.1 Obsoletes: glusterfs-swift-plugin Obsoletes: glusterfs-swift @@ -39,11 +39,11 @@ Obsoletes: glusterfs-swift-proxy Obsoletes: glusterfs-swift-account %description -Gluster-For-Swift (G4S, pronounced "gee-force") integrates GlusterFS as an -alternative back end for OpenStack Object Storage (Swift) leveraging the -existing front end OpenStack Swift code. Gluster volumes are used to store -objects in files, containers are maintained as top-level directories of volumes, -where accounts are mapped one-to-one to gluster volumes. +SwiftOnFile integrates GlusterFS as an alternative back end for OpenStack +Object Storage (Swift) leveraging the existing front end OpenStack Swift code. +Gluster volumes are used to store objects in files, containers are maintained +as top-level directories of volumes, where accounts are mapped one-to-one to +gluster volumes. %prep %setup -q -n gluster_swift-%{_version} @@ -94,10 +94,14 @@ done %config(noreplace) %{_confdir}/swift.conf-gluster %config(noreplace) %{_confdir}/proxy-server.conf-gluster %config(noreplace) %{_confdir}/fs.conf-gluster +%config(noreplace) %{_confdir}/object-expirer.conf-gluster %changelog +* Fri May 23 2014 Thiago da Silva <thiago@redhat.com> - 1.13.1-1 +- Update to Icehouse release + * Mon Oct 28 2013 Luis Pabon <lpabon@redhat.com> - 1.10.1-0 -- IceHouse Release +- Havana Release * Wed Aug 21 2013 Luis Pabon <lpabon@redhat.com> - 1.8.0-7 - Update RPM spec file to support SRPMS diff --git a/tools/requirements.txt b/requirements.txt index bbac51a..bbac51a 100644 --- a/tools/requirements.txt +++ b/requirements.txt @@ -64,8 +64,6 @@ setup( 'paste.filter_factory': [ 'gswauth=gluster.swift.common.middleware.gswauth.swauth.' 'middleware:filter_factory', - 'kerbauth=gluster.swift.common.middleware.' - 'swiftkerbauth.kerbauth:filter_factory', ], }, ) diff --git a/tools/test-requires b/test-requirements.txt index 63d499e..63d499e 100644 --- a/tools/test-requires +++ b/test-requirements.txt diff --git a/test/functional/gluster_swift_tests.py b/test/functional/gluster_swift_tests.py index 2768f9d..b4514c9 100644 --- a/test/functional/gluster_swift_tests.py +++ b/test/functional/gluster_swift_tests.py @@ -58,44 +58,13 @@ class TestFile(Base): data_read = file.read() self.assertEquals(data,data_read) - def testInvalidHeadersPUT(self): - #TODO: Although we now support x-delete-at and x-delete-after, - #retained this test case as we may add some other header to - #unsupported list in future - raise SkipTest() - file = self.env.container.file(Utils.create_name()) - self.assertRaises(ResponseError, - file.write_random, - self.env.file_size, - hdrs={'X-Delete-At': '9876545321'}) - self.assert_status(400) - self.assertRaises(ResponseError, - file.write_random, - self.env.file_size, - hdrs={'X-Delete-After': '60'}) - self.assert_status(400) - - def testInvalidHeadersPOST(self): - #TODO: Although we now support x-delete-at and x-delete-after, - #retained this test case as we may add some other header to - #unsupported list in future - raise SkipTest() - file = self.env.container.file(Utils.create_name()) - file.write_random(self.env.file_size) - headers = file.make_headers(cfg={}) - headers.update({ 'X-Delete-At' : '987654321'}) - # Need to call conn.make_request instead of file.sync_metadata - # because sync_metadata calls make_headers. make_headers() - # overwrites any headers in file.metadata as 'user' metadata - # by appending 'X-Object-Meta-' to any of the headers - # in file.metadata. - file.conn.make_request('POST', file.path, hdrs=headers, cfg={}) - self.assertEqual(400, file.conn.response.status) - - headers = file.make_headers(cfg={}) - headers.update({ 'X-Delete-After' : '60'}) - file.conn.make_request('POST', file.path, hdrs=headers, cfg={}) - self.assertEqual(400, file.conn.response.status) + def test_PUT_large_object(self): + file_item = self.env.container.file(Utils.create_name()) + data = File.random_data(1024 * 1024 * 2) + self.assertTrue(file_item.write(data)) + self.assert_status(201) + self.assertTrue(data == file_item.read()) + self.assert_status(200) class TestFileUTF8(Base2, TestFile): @@ -375,3 +344,49 @@ class TestMultiProtocolAccess(Base): md5_returned = hashlib.md5(data_read_from_mountP).hexdigest() self.assertEquals(md5_returned,file_info['etag']) fhOnMountPoint.close() + + def testObjectMetadataWhenFileModified(self): + data = "I'm whatever Gotham needs me to be " + data_hash = hashlib.md5(data).hexdigest() + # Create an object through object interface + object_name = Utils.create_name() + object_item = self.env.container.file(object_name) + object_item.write(data) + # Make sure GET works + self.assertEqual(data, object_item.read()) + self.assert_status(200) + # Check Etag is right + self.assertEqual(data_hash, object_item.info()['etag']) + self.assert_status(200) + + # Extend/append more data to file from filesystem interface + file_path = os.path.join(self.env.root_dir, + self.env.container.name, + object_name) + more_data = "- Batman" + with open(file_path, 'a') as f: + f.write(more_data) + total_data = data + more_data + total_data_hash = hashlib.md5(total_data).hexdigest() + # Make sure GET works + self.assertEqual(total_data, object_item.read()) + self.assert_status(200) + # Check Etag and content-length is right + metadata = object_item.info() + self.assert_status(200) + self.assertEqual(total_data_hash, metadata['etag']) + self.assertEqual(len(total_data), int(metadata['content_length'])) + + # Re-write the file to be shorter + new_data = "I am Batman" + new_data_hash = hashlib.md5(new_data).hexdigest() + with open(file_path, 'w') as f: + f.write(new_data) + # Make sure GET works + self.assertEqual(new_data, object_item.read()) + self.assert_status(200) + # Check Etag and content-length is right + metadata = object_item.info() + self.assert_status(200) + self.assertEqual(new_data_hash, metadata['etag']) + self.assertEqual(len(new_data), int(metadata['content_length'])) diff --git a/test/functional/swift_test_client.py b/test/functional/swift_test_client.py index a7c7c96..27e025b 100644 --- a/test/functional/swift_test_client.py +++ b/test/functional/swift_test_client.py @@ -144,6 +144,7 @@ class Connection(object): auth_scheme = 'https://' if self.auth_ssl else 'http://' auth_netloc = "%s:%d" % (self.auth_host, self.auth_port) auth_url = auth_scheme + auth_netloc + auth_path + (storage_url, storage_token) = get_auth( auth_url, auth_user, self.password, snet=False, tenant_name=self.account, auth_version=self.auth_version, @@ -166,17 +167,29 @@ class Connection(object): self.storage_host = x[2].split(':')[0] if ':' in x[2]: self.storage_port = int(x[2].split(':')[1]) - # Make sure storage_url and the storage_token are - # strings and not unicode, since + # Make sure storage_url is a string and not unicode, since # keystoneclient (called by swiftclient) returns them in # unicode and this would cause troubles when doing # no_safe_quote query. self.storage_url = str('/%s/%s' % (x[3], x[4])) - self.storage_token = str(storage_token) + + self.storage_token = storage_token self.http_connect() return self.storage_url, self.storage_token + def cluster_info(self): + """ + Retrieve the data in /info, or {} on 404 + """ + status = self.make_request('GET', '/info', + cfg={'absolute_path': True}) + if status == 404: + return {} + if not 200 <= status <= 299: + raise ResponseError(self.response, 'GET', '/info') + return json.loads(self.response.read()) + def http_connect(self): self.connection = self.conn_class(self.storage_host, port=self.storage_port) @@ -207,8 +220,8 @@ class Connection(object): def make_request(self, method, path=[], data='', hdrs={}, parms={}, cfg={}): - if not cfg.get('verbatim_path'): - # Set verbatim_path=True to make a request to exactly the given + if not cfg.get('absolute_path'): + # Set absolute_path=True to make a request to exactly the given # path, not storage path + given path. Useful for # non-account/container/object requests. path = self.make_path(path, cfg=cfg) @@ -305,7 +318,7 @@ class Connection(object): return self.response.status -class Base: +class Base(object): def __str__(self): return self.name @@ -339,6 +352,16 @@ class Account(Base): self.conn = conn self.name = str(name) + def update_metadata(self, metadata={}, cfg={}): + headers = dict(("X-Account-Meta-%s" % k, v) + for k, v in metadata.items()) + + self.conn.make_request('POST', self.path, hdrs=headers, cfg=cfg) + if not 200 <= self.conn.response.status <= 299: + raise ResponseError(self.conn.response, 'POST', + self.conn.make_path(self.path)) + return True + def container(self, container_name): return Container(self.conn, self.name, container_name) @@ -532,6 +555,11 @@ class File(Base): else: headers['Content-Type'] = 'application/octet-stream' + if cfg.get('x_delete_at'): + headers['X-Delete-At'] = cfg.get('x_delete_at') + if cfg.get('x_delete_after'): + headers['X-Delete-After'] = cfg.get('x_delete_after') + for key in self.metadata: headers['X-Object-Meta-' + key] = self.metadata[key] @@ -588,7 +616,11 @@ class File(Base): ['last_modified', 'last-modified'], ['etag', 'etag']] - header_fields = self.header_fields(fields) + optional_fields = [['x_delete_at', 'x-delete-at'], + ['x_delete_after', 'x-delete-after']] + + header_fields = self.header_fields(fields, + optional_fields=optional_fields) header_fields['etag'] = header_fields['etag'].strip('"') return header_fields @@ -705,7 +737,6 @@ class File(Base): cfg.get('set_content_length') else: headers['Content-Length'] = 0 - self.conn.make_request('POST', self.path, hdrs=headers, cfg=cfg) if self.conn.response.status not in (201, 202): diff --git a/test/functional/swift_testing.py b/test/functional/swift_testing.py index f05cb48..2a1e1fa 100644 --- a/test/functional/swift_testing.py +++ b/test/functional/swift_testing.py @@ -19,10 +19,13 @@ import socket import sys from time import sleep from urlparse import urlparse +import functools +from nose import SkipTest from test import get_config from swiftclient import get_auth, http_connection +from test.functional.swift_test_client import Connection conf = get_config('func_test') web_front_end = conf.get('web_front_end', 'integral') @@ -184,3 +187,45 @@ def check_response(conn): resp.read() raise InternalServerError() return resp + +cluster_info = {} + + +def get_cluster_info(): + conn = Connection(conf) + conn.authenticate() + global cluster_info + cluster_info = conn.cluster_info() + + +def reset_acl(): + def post(url, token, parsed, conn): + conn.request('POST', parsed.path, '', { + 'X-Auth-Token': token, + 'X-Account-Access-Control': '{}' + }) + return check_response(conn) + resp = retry(post, use_account=1) + resp.read() + + +def requires_acls(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + if skip: + raise SkipTest + if not cluster_info: + get_cluster_info() + # Determine whether this cluster has account ACLs; if not, skip test + if not cluster_info.get('tempauth', {}).get('account_acls'): + raise SkipTest + if 'keystoneauth' in cluster_info: + # remove when keystoneauth supports account acls + raise SkipTest + reset_acl() + try: + rv = f(*args, **kwargs) + finally: + reset_acl() + return rv + return wrapper diff --git a/test/functional/test_account.py b/test/functional/test_account.py index d456090..1cc61bc 100755 --- a/test/functional/test_account.py +++ b/test/functional/test_account.py @@ -17,19 +17,57 @@ import unittest import json +from uuid import uuid4 from nose import SkipTest +from string import letters from swift.common.constraints import MAX_META_COUNT, MAX_META_NAME_LENGTH, \ MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH from swift.common.middleware.acl import format_acl -from test.functional.swift_test_client import Connection -from test import get_config -from swift_testing import check_response, retry, skip, web_front_end +from swift_testing import (check_response, retry, skip, skip2, skip3, + web_front_end, requires_acls) import swift_testing +from test.functional.tests import load_constraint class TestAccount(unittest.TestCase): + def setUp(self): + self.max_meta_count = load_constraint('max_meta_count') + self.max_meta_name_length = load_constraint('max_meta_name_length') + self.max_meta_overall_size = load_constraint('max_meta_overall_size') + self.max_meta_value_length = load_constraint('max_meta_value_length') + + def head(url, token, parsed, conn): + conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(head) + self.existing_metadata = set([ + k for k, v in resp.getheaders() if + k.lower().startswith('x-account-meta')]) + + def tearDown(self): + def head(url, token, parsed, conn): + conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + resp = retry(head) + resp.read() + new_metadata = set( + [k for k, v in resp.getheaders() if + k.lower().startswith('x-account-meta')]) + + def clear_meta(url, token, parsed, conn, remove_metadata_keys): + headers = {'X-Auth-Token': token} + headers.update((k, '') for k in remove_metadata_keys) + conn.request('POST', parsed.path, '', headers) + return check_response(conn) + extra_metadata = list(self.existing_metadata ^ new_metadata) + for i in range(0, len(extra_metadata), 90): + batch = extra_metadata[i:i + 90] + resp = retry(clear_meta, batch) + resp.read() + self.assertEqual(resp.status // 100, 2) + def test_metadata(self): if skip: raise SkipTest @@ -49,49 +87,338 @@ class TestAccount(unittest.TestCase): resp = retry(post, '') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-account-meta-test'), None) + self.assertEqual(resp.getheader('x-account-meta-test'), None) resp = retry(get) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-account-meta-test'), None) + self.assertEqual(resp.getheader('x-account-meta-test'), None) resp = retry(post, 'Value') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-account-meta-test'), 'Value') + self.assertEqual(resp.getheader('x-account-meta-test'), 'Value') resp = retry(get) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-account-meta-test'), 'Value') + self.assertEqual(resp.getheader('x-account-meta-test'), 'Value') - def test_tempauth_account_acls(self): - if skip: + def test_invalid_acls(self): + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + # needs to be an acceptable header size + num_keys = 8 + max_key_size = load_constraint('max_header_size') / num_keys + acl = {'admin': [c * max_key_size for c in letters[:num_keys]]} + headers = {'x-account-access-control': format_acl( + version=2, acl_dict=acl)} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 400) + + # and again a touch smaller + acl = {'admin': [c * max_key_size for c in letters[:num_keys - 1]]} + headers = {'x-account-access-control': format_acl( + version=2, acl_dict=acl)} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + @requires_acls + def test_invalid_acl_keys(self): + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + # needs to be json + resp = retry(post, headers={'X-Account-Access-Control': 'invalid'}, + use_account=1) + resp.read() + self.assertEqual(resp.status, 400) + + acl_user = swift_testing.swift_test_user[1] + acl = {'admin': [acl_user], 'invalid_key': 'invalid_value'} + headers = {'x-account-access-control': format_acl( + version=2, acl_dict=acl)} + + resp = retry(post, headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 400) + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + @requires_acls + def test_invalid_acl_values(self): + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + acl = {'admin': 'invalid_value'} + headers = {'x-account-access-control': format_acl( + version=2, acl_dict=acl)} + + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 400) + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + @requires_acls + def test_read_only_acl(self): + if skip3: raise SkipTest - # Determine whether this cluster has account ACLs; if not, skip test - conn = Connection(get_config('func_test')) - conn.authenticate() - status = conn.make_request( - 'GET', '/info', cfg={'verbatim_path': True}) - if status // 100 != 2: - # Can't tell if account ACLs are enabled; skip tests proactively. + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + # cannot read account + resp = retry(get, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read access + acl_user = swift_testing.swift_test_user[2] + acl = {'read-only': [acl_user]} + headers = {'x-account-access-control': format_acl( + version=2, acl_dict=acl)} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # read-only can read account headers + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204)) + # but not acls + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + # read-only can not write metadata + headers = {'x-account-meta-test': 'value'} + resp = retry(post, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 403) + + # but they can read it + headers = {'x-account-meta-test': 'value'} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204)) + self.assertEqual(resp.getheader('X-Account-Meta-Test'), 'value') + + @requires_acls + def test_read_write_acl(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + # cannot read account + resp = retry(get, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read-write access + acl_user = swift_testing.swift_test_user[2] + acl = {'read-write': [acl_user]} + headers = {'x-account-access-control': format_acl( + version=2, acl_dict=acl)} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # read-write can read account headers + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204)) + # but not acls + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + # read-write can not write account metadata + headers = {'x-account-meta-test': 'value'} + resp = retry(post, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 403) + + @requires_acls + def test_admin_acl(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + # cannot read account + resp = retry(get, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant admin access + acl_user = swift_testing.swift_test_user[2] + acl = {'admin': [acl_user]} + acl_json_str = format_acl(version=2, acl_dict=acl) + headers = {'x-account-access-control': acl_json_str} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # admin can read account headers + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204)) + # including acls + self.assertEqual(resp.getheader('X-Account-Access-Control'), + acl_json_str) + + # admin can write account metadata + value = str(uuid4()) + headers = {'x-account-meta-test': value} + resp = retry(post, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204)) + self.assertEqual(resp.getheader('X-Account-Meta-Test'), value) + + # admin can even revoke their own access + headers = {'x-account-access-control': '{}'} + resp = retry(post, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 204) + + # and again, cannot read account + resp = retry(get, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + @requires_acls + def test_protected_tempurl(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + # add a account metadata, and temp-url-key to account + value = str(uuid4()) + headers = { + 'x-account-meta-temp-url-key': 'secret', + 'x-account-meta-test': value, + } + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # grant read-only access to tester3 + acl_user = swift_testing.swift_test_user[2] + acl = {'read-only': [acl_user]} + acl_json_str = format_acl(version=2, acl_dict=acl) + headers = {'x-account-access-control': acl_json_str} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # read-only tester3 can read account metadata + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204), + 'Expected status in (200, 204), got %s' % resp.status) + self.assertEqual(resp.getheader('X-Account-Meta-Test'), value) + # but not temp-url-key + self.assertEqual(resp.getheader('X-Account-Meta-Temp-Url-Key'), None) + + # grant read-write access to tester3 + acl_user = swift_testing.swift_test_user[2] + acl = {'read-write': [acl_user]} + acl_json_str = format_acl(version=2, acl_dict=acl) + headers = {'x-account-access-control': acl_json_str} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # read-write tester3 can read account metadata + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204), + 'Expected status in (200, 204), got %s' % resp.status) + self.assertEqual(resp.getheader('X-Account-Meta-Test'), value) + # but not temp-url-key + self.assertEqual(resp.getheader('X-Account-Meta-Temp-Url-Key'), None) + + # grant admin access to tester3 + acl_user = swift_testing.swift_test_user[2] + acl = {'admin': [acl_user]} + acl_json_str = format_acl(version=2, acl_dict=acl) + headers = {'x-account-access-control': acl_json_str} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # admin tester3 can read account metadata + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204), + 'Expected status in (200, 204), got %s' % resp.status) + self.assertEqual(resp.getheader('X-Account-Meta-Test'), value) + # including temp-url-key + self.assertEqual(resp.getheader('X-Account-Meta-Temp-Url-Key'), + 'secret') + + # admin tester3 can even change temp-url-key + secret = str(uuid4()) + headers = { + 'x-account-meta-temp-url-key': secret, + } + resp = retry(post, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, use_account=3) + resp.read() + self.assert_(resp.status in (200, 204), + 'Expected status in (200, 204), got %s' % resp.status) + self.assertEqual(resp.getheader('X-Account-Meta-Temp-Url-Key'), + secret) + + @requires_acls + def test_account_acls(self): + if skip2: raise SkipTest - else: - cluster_info = json.loads(conn.response.read()) - if not cluster_info.get('tempauth', {}).get('account_acls'): - raise SkipTest - if 'keystoneauth' in cluster_info: - # Unfortunate hack -- tempauth (with account ACLs) is expected - # to play nice with Keystone (without account ACLs), but Zuul - # functest framework doesn't give us an easy way to get a - # tempauth user. - raise SkipTest def post(url, token, parsed, conn, headers): new_headers = dict({'X-Auth-Token': token}, **headers) @@ -212,6 +539,137 @@ class TestAccount(unittest.TestCase): use_account=1) resp.read() + @requires_acls + def test_swift_account_acls(self): + if skip: + raise SkipTest + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def head(url, token, parsed, conn): + conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + try: + # User1 can POST to their own account + resp = retry(post, headers={'X-Account-Access-Control': '{}'}) + resp.read() + self.assertEqual(resp.status, 204) + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + # User1 can GET their own empty account + resp = retry(get) + resp.read() + self.assertEqual(resp.status // 100, 2) + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + # User1 can POST non-empty data + acl_json = '{"admin":["bob"]}' + resp = retry(post, headers={'X-Account-Access-Control': acl_json}) + resp.read() + self.assertEqual(resp.status, 204) + + # User1 can GET the non-empty data + resp = retry(get) + resp.read() + self.assertEqual(resp.status // 100, 2) + self.assertEqual(resp.getheader('X-Account-Access-Control'), + acl_json) + + # POST non-JSON ACL should fail + resp = retry(post, headers={'X-Account-Access-Control': 'yuck'}) + resp.read() + # resp.status will be 400 if tempauth or some other ACL-aware + # auth middleware rejects it, or 200 (but silently swallowed by + # core Swift) if ACL-unaware auth middleware approves it. + + # A subsequent GET should show the old, valid data, not the garbage + resp = retry(get) + resp.read() + self.assertEqual(resp.status // 100, 2) + self.assertEqual(resp.getheader('X-Account-Access-Control'), + acl_json) + + finally: + # Make sure to clean up even if tests fail -- User2 should not + # have access to User1's account in other functional tests! + resp = retry(post, headers={'X-Account-Access-Control': '{}'}) + resp.read() + + def test_swift_prohibits_garbage_account_acls(self): + if skip: + raise SkipTest + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + try: + # User1 can POST to their own account + resp = retry(post, headers={'X-Account-Access-Control': '{}'}) + resp.read() + self.assertEqual(resp.status, 204) + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + # User1 can GET their own empty account + resp = retry(get) + resp.read() + self.assertEqual(resp.status // 100, 2) + self.assertEqual(resp.getheader('X-Account-Access-Control'), None) + + # User1 can POST non-empty data + acl_json = '{"admin":["bob"]}' + resp = retry(post, headers={'X-Account-Access-Control': acl_json}) + resp.read() + self.assertEqual(resp.status, 204) + # If this request is handled by ACL-aware auth middleware, then the + # ACL will be persisted. If it is handled by ACL-unaware auth + # middleware, then the header will be thrown out. But the request + # should return successfully in any case. + + # User1 can GET the non-empty data + resp = retry(get) + resp.read() + self.assertEqual(resp.status // 100, 2) + # ACL will be set if some ACL-aware auth middleware (e.g. tempauth) + # propagates it to sysmeta; if no ACL-aware auth middleware does, + # then X-Account-Access-Control will still be empty. + + # POST non-JSON ACL should fail + resp = retry(post, headers={'X-Account-Access-Control': 'yuck'}) + resp.read() + # resp.status will be 400 if tempauth or some other ACL-aware + # auth middleware rejects it, or 200 (but silently swallowed by + # core Swift) if ACL-unaware auth middleware approves it. + + # A subsequent GET should either show the old, valid data (if + # ACL-aware auth middleware is propagating it) or show nothing + # (if no auth middleware in the pipeline is ACL-aware), but should + # never return the garbage ACL. + resp = retry(get) + resp.read() + self.assertEqual(resp.status // 100, 2) + self.assertNotEqual(resp.getheader('X-Account-Access-Control'), + 'yuck') + + finally: + # Make sure to clean up even if tests fail -- User2 should not + # have access to User1's account in other functional tests! + resp = retry(post, headers={'X-Account-Access-Control': '{}'}) + resp.read() + def test_unicode_metadata(self): if skip: raise SkipTest @@ -233,24 +691,24 @@ class TestAccount(unittest.TestCase): resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader(uni_key.encode('utf-8')), '1') + self.assertEqual(resp.getheader(uni_key.encode('utf-8')), '1') resp = retry(post, 'X-Account-Meta-uni', uni_value) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('X-Account-Meta-uni'), - uni_value.encode('utf-8')) + self.assertEqual(resp.getheader('X-Account-Meta-uni'), + uni_value.encode('utf-8')) if (web_front_end == 'integral'): resp = retry(post, uni_key, uni_value) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader(uni_key.encode('utf-8')), - uni_value.encode('utf-8')) + self.assertEqual(resp.getheader(uni_key.encode('utf-8')), + uni_value.encode('utf-8')) def test_multi_metadata(self): if skip: @@ -267,19 +725,19 @@ class TestAccount(unittest.TestCase): resp = retry(post, 'X-Account-Meta-One', '1') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-account-meta-one'), '1') + self.assertEqual(resp.getheader('x-account-meta-one'), '1') resp = retry(post, 'X-Account-Meta-Two', '2') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-account-meta-one'), '1') - self.assertEquals(resp.getheader('x-account-meta-two'), '2') + self.assertEqual(resp.getheader('x-account-meta-one'), '1') + self.assertEqual(resp.getheader('x-account-meta-two'), '2') def test_bad_metadata(self): if skip: @@ -294,35 +752,65 @@ class TestAccount(unittest.TestCase): resp = retry(post, {'X-Account-Meta-' + ('k' * MAX_META_NAME_LENGTH): 'v'}) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry( post, {'X-Account-Meta-' + ('k' * (MAX_META_NAME_LENGTH + 1)): 'v'}) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) resp = retry(post, {'X-Account-Meta-Too-Long': 'k' * MAX_META_VALUE_LENGTH}) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry( post, {'X-Account-Meta-Too-Long': 'k' * (MAX_META_VALUE_LENGTH + 1)}) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) + + def test_bad_metadata2(self): + if skip: + raise SkipTest + + def post(url, token, parsed, conn, extra_headers): + headers = {'X-Auth-Token': token} + headers.update(extra_headers) + conn.request('POST', parsed.path, '', headers) + return check_response(conn) + + # TODO: Find the test that adds these and remove them. + headers = {'x-remove-account-meta-temp-url-key': 'remove', + 'x-remove-account-meta-temp-url-key-2': 'remove'} + resp = retry(post, headers) headers = {} for x in xrange(MAX_META_COUNT): headers['X-Account-Meta-%d' % x] = 'v' resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) headers = {} for x in xrange(MAX_META_COUNT + 1): headers['X-Account-Meta-%d' % x] = 'v' resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) + + def test_bad_metadata3(self): + if skip: + raise SkipTest + + def post(url, token, parsed, conn, extra_headers): + headers = {'X-Auth-Token': token} + headers.update(extra_headers) + conn.request('POST', parsed.path, '', headers) + return check_response(conn) + + # TODO: Find the test that adds these and remove them. + headers = {'x-remove-account-meta-temp-url-key': 'remove', + 'x-remove-account-meta-temp-url-key-2': 'remove'} + resp = retry(post, headers) headers = {} header_value = 'k' * MAX_META_VALUE_LENGTH @@ -337,12 +825,12 @@ class TestAccount(unittest.TestCase): 'v' * (MAX_META_OVERALL_SIZE - size - 1) resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) headers['X-Account-Meta-k'] = \ 'v' * (MAX_META_OVERALL_SIZE - size) resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) if __name__ == '__main__': diff --git a/test/functional/test_container.py b/test/functional/test_container.py index 15f7fc1..91702e9 100755 --- a/test/functional/test_container.py +++ b/test/functional/test_container.py @@ -24,7 +24,7 @@ from swift.common.constraints import MAX_META_COUNT, MAX_META_NAME_LENGTH, \ MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH from swift_testing import check_response, retry, skip, skip2, skip3, \ - swift_test_perm, web_front_end + swift_test_perm, web_front_end, requires_acls, swift_test_user class TestContainer(unittest.TestCase): @@ -41,7 +41,7 @@ class TestContainer(unittest.TestCase): resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) def tearDown(self): if skip: @@ -68,7 +68,7 @@ class TestContainer(unittest.TestCase): for obj in objs: resp = retry(delete, obj) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) def delete(url, token, parsed, conn): conn.request('DELETE', parsed.path + '/' + self.name, '', @@ -77,7 +77,7 @@ class TestContainer(unittest.TestCase): resp = retry(delete) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) def test_multi_metadata(self): if skip: @@ -95,19 +95,19 @@ class TestContainer(unittest.TestCase): resp = retry(post, 'X-Container-Meta-One', '1') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-one'), '1') + self.assertEqual(resp.getheader('x-container-meta-one'), '1') resp = retry(post, 'X-Container-Meta-Two', '2') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-one'), '1') - self.assertEquals(resp.getheader('x-container-meta-two'), '2') + self.assertEqual(resp.getheader('x-container-meta-one'), '1') + self.assertEqual(resp.getheader('x-container-meta-two'), '2') def test_unicode_metadata(self): if skip: @@ -128,28 +128,28 @@ class TestContainer(unittest.TestCase): if (web_front_end == 'integral'): resp = retry(post, uni_key, '1') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader(uni_key.encode('utf-8')), '1') + self.assertEqual(resp.getheader(uni_key.encode('utf-8')), '1') resp = retry(post, 'X-Container-Meta-uni', uni_value) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('X-Container-Meta-uni'), - uni_value.encode('utf-8')) + self.assertEqual(resp.getheader('X-Container-Meta-uni'), + uni_value.encode('utf-8')) if (web_front_end == 'integral'): resp = retry(post, uni_key, uni_value) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader(uni_key.encode('utf-8')), - uni_value.encode('utf-8')) + self.assertEqual(resp.getheader(uni_key.encode('utf-8')), + uni_value.encode('utf-8')) def test_PUT_metadata(self): if skip: @@ -179,34 +179,34 @@ class TestContainer(unittest.TestCase): name = uuid4().hex resp = retry(put, name, 'Value') resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) resp = retry(head, name) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), 'Value') + self.assertEqual(resp.getheader('x-container-meta-test'), 'Value') resp = retry(get, name) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), 'Value') + self.assertEqual(resp.getheader('x-container-meta-test'), 'Value') resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) name = uuid4().hex resp = retry(put, name, '') resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) resp = retry(head, name) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), None) + self.assertEqual(resp.getheader('x-container-meta-test'), None) resp = retry(get, name) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), None) + self.assertEqual(resp.getheader('x-container-meta-test'), None) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) def test_POST_metadata(self): if skip: @@ -231,22 +231,22 @@ class TestContainer(unittest.TestCase): resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), None) + self.assertEqual(resp.getheader('x-container-meta-test'), None) resp = retry(get) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), None) + self.assertEqual(resp.getheader('x-container-meta-test'), None) resp = retry(post, 'Value') resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(head) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), 'Value') + self.assertEqual(resp.getheader('x-container-meta-test'), 'Value') resp = retry(get) resp.read() self.assert_(resp.status in (200, 204), resp.status) - self.assertEquals(resp.getheader('x-container-meta-test'), 'Value') + self.assertEqual(resp.getheader('x-container-meta-test'), 'Value') def test_PUT_bad_metadata(self): if skip: @@ -268,38 +268,38 @@ class TestContainer(unittest.TestCase): put, name, {'X-Container-Meta-' + ('k' * MAX_META_NAME_LENGTH): 'v'}) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) name = uuid4().hex resp = retry( put, name, {'X-Container-Meta-' + ('k' * (MAX_META_NAME_LENGTH + 1)): 'v'}) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 404) + self.assertEqual(resp.status, 404) name = uuid4().hex resp = retry( put, name, {'X-Container-Meta-Too-Long': 'k' * MAX_META_VALUE_LENGTH}) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) name = uuid4().hex resp = retry( put, name, {'X-Container-Meta-Too-Long': 'k' * (MAX_META_VALUE_LENGTH + 1)}) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 404) + self.assertEqual(resp.status, 404) name = uuid4().hex headers = {} @@ -307,20 +307,20 @@ class TestContainer(unittest.TestCase): headers['X-Container-Meta-%d' % x] = 'v' resp = retry(put, name, headers) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) name = uuid4().hex headers = {} for x in xrange(MAX_META_COUNT + 1): headers['X-Container-Meta-%d' % x] = 'v' resp = retry(put, name, headers) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 404) + self.assertEqual(resp.status, 404) name = uuid4().hex headers = {} @@ -336,19 +336,19 @@ class TestContainer(unittest.TestCase): 'v' * (MAX_META_OVERALL_SIZE - size - 1) resp = retry(put, name, headers) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) name = uuid4().hex headers['X-Container-Meta-k'] = \ 'v' * (MAX_META_OVERALL_SIZE - size) resp = retry(put, name, headers) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) resp = retry(delete, name) resp.read() - self.assertEquals(resp.status, 404) + self.assertEqual(resp.status, 404) def test_POST_bad_metadata(self): if skip: @@ -364,36 +364,56 @@ class TestContainer(unittest.TestCase): post, {'X-Container-Meta-' + ('k' * MAX_META_NAME_LENGTH): 'v'}) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry( post, {'X-Container-Meta-' + ('k' * (MAX_META_NAME_LENGTH + 1)): 'v'}) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) resp = retry( post, {'X-Container-Meta-Too-Long': 'k' * MAX_META_VALUE_LENGTH}) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry( post, {'X-Container-Meta-Too-Long': 'k' * (MAX_META_VALUE_LENGTH + 1)}) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) + + def test_POST_bad_metadata2(self): + if skip: + raise SkipTest + + def post(url, token, parsed, conn, extra_headers): + headers = {'X-Auth-Token': token} + headers.update(extra_headers) + conn.request('POST', parsed.path + '/' + self.name, '', headers) + return check_response(conn) headers = {} for x in xrange(MAX_META_COUNT): headers['X-Container-Meta-%d' % x] = 'v' resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) headers = {} for x in xrange(MAX_META_COUNT + 1): headers['X-Container-Meta-%d' % x] = 'v' resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) + + def test_POST_bad_metadata3(self): + if skip: + raise SkipTest + + def post(url, token, parsed, conn, extra_headers): + headers = {'X-Auth-Token': token} + headers.update(extra_headers) + conn.request('POST', parsed.path + '/' + self.name, '', headers) + return check_response(conn) headers = {} header_value = 'k' * MAX_META_VALUE_LENGTH @@ -408,12 +428,12 @@ class TestContainer(unittest.TestCase): 'v' * (MAX_META_OVERALL_SIZE - size - 1) resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) headers['X-Container-Meta-k'] = \ 'v' * (MAX_META_OVERALL_SIZE - size) resp = retry(post, headers) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) def test_public_container(self): if skip: @@ -437,10 +457,10 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(get) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) def post(url, token, parsed, conn): conn.request('POST', parsed.path + '/' + self.name, '', @@ -449,7 +469,7 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) try: resp = retry(get) raise Exception('Should not have been able to GET') @@ -479,7 +499,7 @@ class TestContainer(unittest.TestCase): resp = retry(get2, use_account=2) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # Make the container accessible by the second account def post(url, token, parsed, conn): @@ -491,11 +511,11 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Ensure we can now use the container with the second account resp = retry(get2, use_account=2) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Make the container private again def post(url, token, parsed, conn): @@ -506,11 +526,11 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Ensure we can't access the container with the second account again resp = retry(get2, use_account=2) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) def test_cross_account_public_container(self): if skip or skip2: @@ -535,7 +555,7 @@ class TestContainer(unittest.TestCase): resp = retry(get2, use_account=2) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # Make the container completely public def post(url, token, parsed, conn): @@ -546,11 +566,11 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Ensure we can now read the container with the second account resp = retry(get2, use_account=2) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # But we shouldn't be able to write with the second account def put2(url, token, parsed, conn): @@ -560,7 +580,7 @@ class TestContainer(unittest.TestCase): resp = retry(put2, use_account=2) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # Now make the container also writeable by the second account def post(url, token, parsed, conn): @@ -571,15 +591,15 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Ensure we can still read the container with the second account resp = retry(get2, use_account=2) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # And that we can now write with the second account resp = retry(put2, use_account=2) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) def test_nonadmin_user(self): if skip or skip3: @@ -604,7 +624,7 @@ class TestContainer(unittest.TestCase): resp = retry(get3, use_account=3) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # Make the container accessible by the third account def post(url, token, parsed, conn): @@ -615,11 +635,11 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Ensure we can now read the container with the third account resp = retry(get3, use_account=3) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # But we shouldn't be able to write with the third account def put3(url, token, parsed, conn): @@ -629,7 +649,7 @@ class TestContainer(unittest.TestCase): resp = retry(put3, use_account=3) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # Now make the container also writeable by the third account def post(url, token, parsed, conn): @@ -640,15 +660,666 @@ class TestContainer(unittest.TestCase): resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Ensure we can still read the container with the third account resp = retry(get3, use_account=3) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # And that we can now write with the third account resp = retry(put3, use_account=3) resp.read() + self.assertEqual(resp.status, 201) + + @requires_acls + def test_read_only_acl_listings(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def put(url, token, parsed, conn, name): + conn.request('PUT', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + # cannot list containers + resp = retry(get, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read-only access + acl_user = swift_test_user[2] + acl = {'read-only': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # read-only can list containers + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(self.name in listing) + + # read-only can not create containers + new_container_name = str(uuid4()) + resp = retry(put, new_container_name, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # but it can see newly created ones + resp = retry(put, new_container_name, use_account=1) + resp.read() self.assertEquals(resp.status, 201) + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(new_container_name in listing) + + @requires_acls + def test_read_only_acl_metadata(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn, name): + conn.request('GET', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def post(url, token, parsed, conn, name, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path + '/%s' % name, '', new_headers) + return check_response(conn) + + # add some metadata + value = str(uuid4()) + headers = {'x-container-meta-test': value} + resp = retry(post, self.name, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=1) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + # cannot see metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read-only access + acl_user = swift_test_user[2] + acl = {'read-only': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # read-only can NOT write container metadata + new_value = str(uuid4()) + headers = {'x-container-meta-test': new_value} + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 403) + + # read-only can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + @requires_acls + def test_read_write_acl_listings(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def put(url, token, parsed, conn, name): + conn.request('PUT', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + def delete(url, token, parsed, conn, name): + conn.request('DELETE', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + # cannot list containers + resp = retry(get, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read-write access + acl_user = swift_test_user[2] + acl = {'read-write': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can list containers + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(self.name in listing) + + # can create new containers + new_container_name = str(uuid4()) + resp = retry(put, new_container_name, use_account=3) + resp.read() + self.assertEquals(resp.status, 201) + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(new_container_name in listing) + + # can also delete them + resp = retry(delete, new_container_name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(new_container_name not in listing) + + # even if they didn't create them + empty_container_name = str(uuid4()) + resp = retry(put, empty_container_name, use_account=1) + resp.read() + self.assertEquals(resp.status, 201) + resp = retry(delete, empty_container_name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + + @requires_acls + def test_read_write_acl_metadata(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn, name): + conn.request('GET', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def post(url, token, parsed, conn, name, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path + '/%s' % name, '', new_headers) + return check_response(conn) + + # add some metadata + value = str(uuid4()) + headers = {'x-container-meta-test': value} + resp = retry(post, self.name, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=1) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + # cannot see metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read-write access + acl_user = swift_test_user[2] + acl = {'read-write': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # read-write can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + # read-write can also write container metadata + new_value = str(uuid4()) + headers = {'x-container-meta-test': new_value} + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), new_value) + + # and remove it + headers = {'x-remove-container-meta-test': 'true'} + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), None) + + @requires_acls + def test_admin_acl_listing(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn): + conn.request('GET', parsed.path, '', {'X-Auth-Token': token}) + return check_response(conn) + + def post(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def put(url, token, parsed, conn, name): + conn.request('PUT', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + def delete(url, token, parsed, conn, name): + conn.request('DELETE', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + # cannot list containers + resp = retry(get, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant admin access + acl_user = swift_test_user[2] + acl = {'admin': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can list containers + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(self.name in listing) + + # can create new containers + new_container_name = str(uuid4()) + resp = retry(put, new_container_name, use_account=3) + resp.read() + self.assertEquals(resp.status, 201) + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(new_container_name in listing) + + # can also delete them + resp = retry(delete, new_container_name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(new_container_name not in listing) + + # even if they didn't create them + empty_container_name = str(uuid4()) + resp = retry(put, empty_container_name, use_account=1) + resp.read() + self.assertEquals(resp.status, 201) + resp = retry(delete, empty_container_name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + + @requires_acls + def test_admin_acl_metadata(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn, name): + conn.request('GET', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def post(url, token, parsed, conn, name, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path + '/%s' % name, '', new_headers) + return check_response(conn) + + # add some metadata + value = str(uuid4()) + headers = {'x-container-meta-test': value} + resp = retry(post, self.name, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=1) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + # cannot see metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant access + acl_user = swift_test_user[2] + acl = {'admin': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + # can also write container metadata + new_value = str(uuid4()) + headers = {'x-container-meta-test': new_value} + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), new_value) + + # and remove it + headers = {'x-remove-container-meta-test': 'true'} + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), None) + + @requires_acls + def test_protected_container_sync(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn, name): + conn.request('GET', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def post(url, token, parsed, conn, name, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path + '/%s' % name, '', new_headers) + return check_response(conn) + + # add some metadata + value = str(uuid4()) + headers = { + 'x-container-sync-key': 'secret', + 'x-container-meta-test': value, + } + resp = retry(post, self.name, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=1) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Sync-Key'), 'secret') + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + # grant read-only access + acl_user = swift_test_user[2] + acl = {'read-only': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + # but not sync-key + self.assertEqual(resp.getheader('X-Container-Sync-Key'), None) + + # and can not write + headers = {'x-container-sync-key': str(uuid4())} + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 403) + + # grant read-write access + acl_user = swift_test_user[2] + acl = {'read-write': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + # but not sync-key + self.assertEqual(resp.getheader('X-Container-Sync-Key'), None) + + # sanity check sync-key w/ account1 + resp = retry(get, self.name, use_account=1) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Sync-Key'), 'secret') + + # and can write + new_value = str(uuid4()) + headers = { + 'x-container-sync-key': str(uuid4()), + 'x-container-meta-test': new_value, + } + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=1) # validate w/ account1 + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), new_value) + # but can not write sync-key + self.assertEqual(resp.getheader('X-Container-Sync-Key'), 'secret') + + # grant admin access + acl_user = swift_test_user[2] + acl = {'admin': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # admin can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), new_value) + # and ALSO sync-key + self.assertEqual(resp.getheader('X-Container-Sync-Key'), 'secret') + + # admin tester3 can even change sync-key + new_secret = str(uuid4()) + headers = {'x-container-sync-key': new_secret} + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Sync-Key'), new_secret) + + @requires_acls + def test_protected_container_acl(self): + if skip3: + raise SkipTest + + def get(url, token, parsed, conn, name): + conn.request('GET', parsed.path + '/%s' % name, '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def post(url, token, parsed, conn, name, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path + '/%s' % name, '', new_headers) + return check_response(conn) + + # add some container acls + value = str(uuid4()) + headers = { + 'x-container-read': 'jdoe', + 'x-container-write': 'jdoe', + 'x-container-meta-test': value, + } + resp = retry(post, self.name, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=1) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Read'), 'jdoe') + self.assertEqual(resp.getheader('X-Container-Write'), 'jdoe') + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + + # grant read-only access + acl_user = swift_test_user[2] + acl = {'read-only': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + # but not container acl + self.assertEqual(resp.getheader('X-Container-Read'), None) + self.assertEqual(resp.getheader('X-Container-Write'), None) + + # and can not write + headers = { + 'x-container-read': 'frank', + 'x-container-write': 'frank', + } + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 403) + + # grant read-write access + acl_user = swift_test_user[2] + acl = {'read-write': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), value) + # but not container acl + self.assertEqual(resp.getheader('X-Container-Read'), None) + self.assertEqual(resp.getheader('X-Container-Write'), None) + + # sanity check container acls with account1 + resp = retry(get, self.name, use_account=1) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Read'), 'jdoe') + self.assertEqual(resp.getheader('X-Container-Write'), 'jdoe') + + # and can write + new_value = str(uuid4()) + headers = { + 'x-container-read': 'frank', + 'x-container-write': 'frank', + 'x-container-meta-test': new_value, + } + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=1) # validate w/ account1 + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), new_value) + # but can not write container acls + self.assertEqual(resp.getheader('X-Container-Read'), 'jdoe') + self.assertEqual(resp.getheader('X-Container-Write'), 'jdoe') + + # grant admin access + acl_user = swift_test_user[2] + acl = {'admin': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # admin can read container metadata + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Meta-Test'), new_value) + # and ALSO container acls + self.assertEqual(resp.getheader('X-Container-Read'), 'jdoe') + self.assertEqual(resp.getheader('X-Container-Write'), 'jdoe') + + # admin tester3 can even change container acls + new_value = str(uuid4()) + headers = { + 'x-container-read': '.r:*', + } + resp = retry(post, self.name, headers=headers, use_account=3) + resp.read() + self.assertEqual(resp.status, 204) + resp = retry(get, self.name, use_account=3) + resp.read() + self.assertEquals(resp.status, 204) + self.assertEqual(resp.getheader('X-Container-Read'), '.r:*') def test_long_name_content_type(self): if skip: @@ -662,9 +1333,9 @@ class TestContainer(unittest.TestCase): resp = retry(put) resp.read() - self.assertEquals(resp.status, 400) - self.assertEquals(resp.getheader('Content-Type'), - 'text/html; charset=UTF-8') + self.assertEqual(resp.status, 400) + self.assertEqual(resp.getheader('Content-Type'), + 'text/html; charset=UTF-8') def test_null_name(self): if skip: @@ -677,10 +1348,10 @@ class TestContainer(unittest.TestCase): resp = retry(put) if (web_front_end == 'apache2'): - self.assertEquals(resp.status, 404) + self.assertEqual(resp.status, 404) else: - self.assertEquals(resp.read(), 'Invalid UTF8 or contains NULL') - self.assertEquals(resp.status, 412) + self.assertEqual(resp.read(), 'Invalid UTF8 or contains NULL') + self.assertEqual(resp.status, 412) if __name__ == '__main__': diff --git a/test/functional/test_object.py b/test/functional/test_object.py index dad8635..675de30 100755 --- a/test/functional/test_object.py +++ b/test/functional/test_object.py @@ -19,8 +19,10 @@ import unittest from nose import SkipTest from uuid import uuid4 +from swift.common.utils import json + from swift_testing import check_response, retry, skip, skip3, \ - swift_test_perm, web_front_end + swift_test_perm, web_front_end, requires_acls, swift_test_user class TestObject(unittest.TestCase): @@ -36,7 +38,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) self.obj = uuid4().hex def put(url, token, parsed, conn): @@ -46,7 +48,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) def tearDown(self): if skip: @@ -66,13 +68,13 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(list) object_listing = resp.read() - self.assertEquals(resp.status, 200) + self.assertEqual(resp.status, 200) # iterate over object listing and delete all objects for obj in object_listing.splitlines(): resp = retry(delete, obj) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # delete the container def delete(url, token, parsed, conn): @@ -81,7 +83,33 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) + + def test_if_none_match(self): + def put(url, token, parsed, conn): + conn.request('PUT', '%s/%s/%s' % ( + parsed.path, self.container, 'if_none_match_test'), '', + {'X-Auth-Token': token, + 'Content-Length': '0', + 'If-None-Match': '*'}) + return check_response(conn) + resp = retry(put) + resp.read() + self.assertEquals(resp.status, 201) + resp = retry(put) + resp.read() + self.assertEquals(resp.status, 412) + + def put(url, token, parsed, conn): + conn.request('PUT', '%s/%s/%s' % ( + parsed.path, self.container, 'if_none_match_test'), '', + {'X-Auth-Token': token, + 'Content-Length': '0', + 'If-None-Match': 'somethingelse'}) + return check_response(conn) + resp = retry(put) + resp.read() + self.assertEquals(resp.status, 400) def test_copy_object(self): if skip: @@ -98,8 +126,8 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(get_source) source_contents = resp.read() - self.assertEquals(resp.status, 200) - self.assertEquals(source_contents, 'test') + self.assertEqual(resp.status, 200) + self.assertEqual(source_contents, 'test') # copy source to dest with X-Copy-From def put(url, token, parsed, conn): @@ -110,7 +138,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # contents of dest should be the same as source def get_dest(url, token, parsed, conn): @@ -120,8 +148,8 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(get_dest) dest_contents = resp.read() - self.assertEquals(resp.status, 200) - self.assertEquals(dest_contents, source_contents) + self.assertEqual(resp.status, 200) + self.assertEqual(dest_contents, source_contents) # delete the copy def delete(url, token, parsed, conn): @@ -130,11 +158,11 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # verify dest does not exist resp = retry(get_dest) resp.read() - self.assertEquals(resp.status, 404) + self.assertEqual(resp.status, 404) # copy source to dest with COPY def copy(url, token, parsed, conn): @@ -144,18 +172,18 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(copy) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # contents of dest should be the same as source resp = retry(get_dest) dest_contents = resp.read() - self.assertEquals(resp.status, 200) - self.assertEquals(dest_contents, source_contents) + self.assertEqual(resp.status, 200) + self.assertEqual(dest_contents, source_contents) # delete the copy resp = retry(delete) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) def test_public_object(self): if skip: @@ -178,10 +206,10 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) resp = retry(get) resp.read() - self.assertEquals(resp.status, 200) + self.assertEqual(resp.status, 200) def post(url, token, parsed, conn): conn.request('POST', parsed.path + '/' + self.container, '', @@ -189,7 +217,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) try: resp = retry(get) raise Exception('Should not have been able to GET') @@ -208,7 +236,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(get, use_account=3) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # create a shared container writable by account3 shared_container = uuid4().hex @@ -222,7 +250,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # verify third account can not copy from private container def copy(url, token, parsed, conn): @@ -234,7 +262,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(copy, use_account=3) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # verify third account can write "obj1" to shared container def put(url, token, parsed, conn): @@ -244,7 +272,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put, use_account=3) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # verify third account can copy "obj1" to shared container def copy2(url, token, parsed, conn): @@ -255,7 +283,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(copy2, use_account=3) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # verify third account STILL can not copy from private container def copy3(url, token, parsed, conn): @@ -267,7 +295,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(copy3, use_account=3) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # clean up "obj1" def delete(url, token, parsed, conn): @@ -277,7 +305,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # clean up shared_container def delete(url, token, parsed, conn): @@ -287,8 +315,251 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete) resp.read() + self.assertEqual(resp.status, 204) + + @requires_acls + def test_read_only(self): + if skip3: + raise SkipTest + + def get_listing(url, token, parsed, conn): + conn.request('GET', '%s/%s' % (parsed.path, self.container), '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def get(url, token, parsed, conn, name): + conn.request('GET', '%s/%s/%s' % ( + parsed.path, self.container, name), '', + {'X-Auth-Token': token}) + return check_response(conn) + + def put(url, token, parsed, conn, name): + conn.request('PUT', '%s/%s/%s' % ( + parsed.path, self.container, name), 'test', + {'X-Auth-Token': token}) + return check_response(conn) + + def delete(url, token, parsed, conn, name): + conn.request('PUT', '%s/%s/%s' % ( + parsed.path, self.container, name), '', + {'X-Auth-Token': token}) + return check_response(conn) + + # cannot list objects + resp = retry(get_listing, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # cannot get object + resp = retry(get, self.obj, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read-only access + acl_user = swift_test_user[2] + acl = {'read-only': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can list objects + resp = retry(get_listing, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(self.obj in listing) + + # can get object + resp = retry(get, self.obj, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 200) + self.assertEquals(body, 'test') + + # can not put an object + obj_name = str(uuid4()) + resp = retry(put, obj_name, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 403) + + # can not delete an object + resp = retry(delete, self.obj, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 403) + + # sanity with account1 + resp = retry(get_listing, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(obj_name not in listing) + self.assert_(self.obj in listing) + + @requires_acls + def test_read_write(self): + if skip3: + raise SkipTest + + def get_listing(url, token, parsed, conn): + conn.request('GET', '%s/%s' % (parsed.path, self.container), '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def get(url, token, parsed, conn, name): + conn.request('GET', '%s/%s/%s' % ( + parsed.path, self.container, name), '', + {'X-Auth-Token': token}) + return check_response(conn) + + def put(url, token, parsed, conn, name): + conn.request('PUT', '%s/%s/%s' % ( + parsed.path, self.container, name), 'test', + {'X-Auth-Token': token}) + return check_response(conn) + + def delete(url, token, parsed, conn, name): + conn.request('DELETE', '%s/%s/%s' % ( + parsed.path, self.container, name), '', + {'X-Auth-Token': token}) + return check_response(conn) + + # cannot list objects + resp = retry(get_listing, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # cannot get object + resp = retry(get, self.obj, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant read-write access + acl_user = swift_test_user[2] + acl = {'read-write': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can list objects + resp = retry(get_listing, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(self.obj in listing) + + # can get object + resp = retry(get, self.obj, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 200) + self.assertEquals(body, 'test') + + # can put an object + obj_name = str(uuid4()) + resp = retry(put, obj_name, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 201) + + # can delete an object + resp = retry(delete, self.obj, use_account=3) + body = resp.read() self.assertEquals(resp.status, 204) + # sanity with account1 + resp = retry(get_listing, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(obj_name in listing) + self.assert_(self.obj not in listing) + + @requires_acls + def test_admin(self): + if skip3: + raise SkipTest + + def get_listing(url, token, parsed, conn): + conn.request('GET', '%s/%s' % (parsed.path, self.container), '', + {'X-Auth-Token': token}) + return check_response(conn) + + def post_account(url, token, parsed, conn, headers): + new_headers = dict({'X-Auth-Token': token}, **headers) + conn.request('POST', parsed.path, '', new_headers) + return check_response(conn) + + def get(url, token, parsed, conn, name): + conn.request('GET', '%s/%s/%s' % ( + parsed.path, self.container, name), '', + {'X-Auth-Token': token}) + return check_response(conn) + + def put(url, token, parsed, conn, name): + conn.request('PUT', '%s/%s/%s' % ( + parsed.path, self.container, name), 'test', + {'X-Auth-Token': token}) + return check_response(conn) + + def delete(url, token, parsed, conn, name): + conn.request('DELETE', '%s/%s/%s' % ( + parsed.path, self.container, name), '', + {'X-Auth-Token': token}) + return check_response(conn) + + # cannot list objects + resp = retry(get_listing, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # cannot get object + resp = retry(get, self.obj, use_account=3) + resp.read() + self.assertEquals(resp.status, 403) + + # grant admin access + acl_user = swift_test_user[2] + acl = {'admin': [acl_user]} + headers = {'x-account-access-control': json.dumps(acl)} + resp = retry(post_account, headers=headers, use_account=1) + resp.read() + self.assertEqual(resp.status, 204) + + # can list objects + resp = retry(get_listing, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(self.obj in listing) + + # can get object + resp = retry(get, self.obj, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 200) + self.assertEquals(body, 'test') + + # can put an object + obj_name = str(uuid4()) + resp = retry(put, obj_name, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 201) + + # can delete an object + resp = retry(delete, self.obj, use_account=3) + body = resp.read() + self.assertEquals(resp.status, 204) + + # sanity with account1 + resp = retry(get_listing, use_account=3) + listing = resp.read() + self.assertEquals(resp.status, 200) + self.assert_(obj_name in listing) + self.assert_(self.obj not in listing) + def test_manifest(self): if skip: raise SkipTest @@ -306,7 +577,7 @@ class TestObject(unittest.TestCase): for objnum in xrange(len(segments1)): resp = retry(put, objnum) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # Upload the manifest def put(url, token, parsed, conn): @@ -318,7 +589,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # Get the manifest (should get all the segments as the body) def get(url, token, parsed, conn): @@ -326,9 +597,9 @@ class TestObject(unittest.TestCase): parsed.path, self.container), '', {'X-Auth-Token': token}) return check_response(conn) resp = retry(get) - self.assertEquals(resp.read(), ''.join(segments1)) - self.assertEquals(resp.status, 200) - self.assertEquals(resp.getheader('content-type'), 'text/jibberish') + self.assertEqual(resp.read(), ''.join(segments1)) + self.assertEqual(resp.status, 200) + self.assertEqual(resp.getheader('content-type'), 'text/jibberish') # Get with a range at the start of the second segment def get(url, token, parsed, conn): @@ -337,8 +608,8 @@ class TestObject(unittest.TestCase): 'X-Auth-Token': token, 'Range': 'bytes=3-'}) return check_response(conn) resp = retry(get) - self.assertEquals(resp.read(), ''.join(segments1[1:])) - self.assertEquals(resp.status, 206) + self.assertEqual(resp.read(), ''.join(segments1[1:])) + self.assertEqual(resp.status, 206) # Get with a range in the middle of the second segment def get(url, token, parsed, conn): @@ -347,8 +618,8 @@ class TestObject(unittest.TestCase): 'X-Auth-Token': token, 'Range': 'bytes=5-'}) return check_response(conn) resp = retry(get) - self.assertEquals(resp.read(), ''.join(segments1)[5:]) - self.assertEquals(resp.status, 206) + self.assertEqual(resp.read(), ''.join(segments1)[5:]) + self.assertEqual(resp.status, 206) # Get with a full start and stop range def get(url, token, parsed, conn): @@ -357,8 +628,8 @@ class TestObject(unittest.TestCase): 'X-Auth-Token': token, 'Range': 'bytes=5-10'}) return check_response(conn) resp = retry(get) - self.assertEquals(resp.read(), ''.join(segments1)[5:11]) - self.assertEquals(resp.status, 206) + self.assertEqual(resp.read(), ''.join(segments1)[5:11]) + self.assertEqual(resp.status, 206) # Upload the second set of segments def put(url, token, parsed, conn, objnum): @@ -369,7 +640,7 @@ class TestObject(unittest.TestCase): for objnum in xrange(len(segments2)): resp = retry(put, objnum) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # Get the manifest (should still be the first segments of course) def get(url, token, parsed, conn): @@ -377,8 +648,8 @@ class TestObject(unittest.TestCase): parsed.path, self.container), '', {'X-Auth-Token': token}) return check_response(conn) resp = retry(get) - self.assertEquals(resp.read(), ''.join(segments1)) - self.assertEquals(resp.status, 200) + self.assertEqual(resp.read(), ''.join(segments1)) + self.assertEqual(resp.status, 200) # Update the manifest def put(url, token, parsed, conn): @@ -390,7 +661,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # Get the manifest (should be the second set of segments now) def get(url, token, parsed, conn): @@ -398,8 +669,8 @@ class TestObject(unittest.TestCase): parsed.path, self.container), '', {'X-Auth-Token': token}) return check_response(conn) resp = retry(get) - self.assertEquals(resp.read(), ''.join(segments2)) - self.assertEquals(resp.status, 200) + self.assertEqual(resp.read(), ''.join(segments2)) + self.assertEqual(resp.status, 200) if not skip3: @@ -410,7 +681,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(get, use_account=3) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # Grant access to the third account def post(url, token, parsed, conn): @@ -420,7 +691,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # The third account should be able to get the manifest now def get(url, token, parsed, conn): @@ -428,8 +699,8 @@ class TestObject(unittest.TestCase): parsed.path, self.container), '', {'X-Auth-Token': token}) return check_response(conn) resp = retry(get, use_account=3) - self.assertEquals(resp.read(), ''.join(segments2)) - self.assertEquals(resp.status, 200) + self.assertEqual(resp.read(), ''.join(segments2)) + self.assertEqual(resp.status, 200) # Create another container for the third set of segments acontainer = uuid4().hex @@ -440,7 +711,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # Upload the third set of segments in the other container def put(url, token, parsed, conn, objnum): @@ -451,7 +722,7 @@ class TestObject(unittest.TestCase): for objnum in xrange(len(segments3)): resp = retry(put, objnum) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # Update the manifest def put(url, token, parsed, conn): @@ -463,7 +734,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) # Get the manifest to ensure it's the third set of segments def get(url, token, parsed, conn): @@ -471,8 +742,8 @@ class TestObject(unittest.TestCase): parsed.path, self.container), '', {'X-Auth-Token': token}) return check_response(conn) resp = retry(get) - self.assertEquals(resp.read(), ''.join(segments3)) - self.assertEquals(resp.status, 200) + self.assertEqual(resp.read(), ''.join(segments3)) + self.assertEqual(resp.status, 200) if not skip3: @@ -486,7 +757,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(get, use_account=3) resp.read() - self.assertEquals(resp.status, 403) + self.assertEqual(resp.status, 403) # Grant access to the third account def post(url, token, parsed, conn): @@ -496,7 +767,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(post) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # The third account should be able to get the manifest now def get(url, token, parsed, conn): @@ -504,8 +775,8 @@ class TestObject(unittest.TestCase): parsed.path, self.container), '', {'X-Auth-Token': token}) return check_response(conn) resp = retry(get, use_account=3) - self.assertEquals(resp.read(), ''.join(segments3)) - self.assertEquals(resp.status, 200) + self.assertEqual(resp.read(), ''.join(segments3)) + self.assertEqual(resp.status, 200) # Delete the manifest def delete(url, token, parsed, conn, objnum): @@ -515,7 +786,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete, objnum) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Delete the third set of segments def delete(url, token, parsed, conn, objnum): @@ -526,7 +797,7 @@ class TestObject(unittest.TestCase): for objnum in xrange(len(segments3)): resp = retry(delete, objnum) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Delete the second set of segments def delete(url, token, parsed, conn, objnum): @@ -537,7 +808,7 @@ class TestObject(unittest.TestCase): for objnum in xrange(len(segments2)): resp = retry(delete, objnum) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Delete the first set of segments def delete(url, token, parsed, conn, objnum): @@ -548,7 +819,7 @@ class TestObject(unittest.TestCase): for objnum in xrange(len(segments1)): resp = retry(delete, objnum) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) # Delete the extra container def delete(url, token, parsed, conn): @@ -557,7 +828,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete) resp.read() - self.assertEquals(resp.status, 204) + self.assertEqual(resp.status, 204) def test_delete_content_type(self): if skip: @@ -569,7 +840,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) def delete(url, token, parsed, conn): conn.request('DELETE', '%s/%s/hi' % (parsed.path, self.container), @@ -577,9 +848,9 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete) resp.read() - self.assertEquals(resp.status, 204) - self.assertEquals(resp.getheader('Content-Type'), - 'text/html; charset=UTF-8') + self.assertEqual(resp.status, 204) + self.assertEqual(resp.getheader('Content-Type'), + 'text/html; charset=UTF-8') def test_delete_if_delete_at_bad(self): if skip: @@ -592,7 +863,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) resp.read() - self.assertEquals(resp.status, 201) + self.assertEqual(resp.status, 201) def delete(url, token, parsed, conn): conn.request('DELETE', '%s/%s/hi' % (parsed.path, self.container), @@ -601,7 +872,7 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(delete) resp.read() - self.assertEquals(resp.status, 400) + self.assertEqual(resp.status, 400) def test_null_name(self): if skip: @@ -614,10 +885,121 @@ class TestObject(unittest.TestCase): return check_response(conn) resp = retry(put) if (web_front_end == 'apache2'): - self.assertEquals(resp.status, 404) + self.assertEqual(resp.status, 404) else: - self.assertEquals(resp.read(), 'Invalid UTF8 or contains NULL') - self.assertEquals(resp.status, 412) + self.assertEqual(resp.read(), 'Invalid UTF8 or contains NULL') + self.assertEqual(resp.status, 412) + + def test_cors(self): + if skip: + raise SkipTest + + def is_strict_mode(url, token, parsed, conn): + conn.request('GET', '/info') + resp = conn.getresponse() + if resp.status // 100 == 2: + info = json.loads(resp.read()) + return info.get('swift', {}).get('strict_cors_mode', False) + return False + + def put_cors_cont(url, token, parsed, conn, orig): + conn.request( + 'PUT', '%s/%s' % (parsed.path, self.container), + '', {'X-Auth-Token': token, + 'X-Container-Meta-Access-Control-Allow-Origin': orig}) + return check_response(conn) + + def put_obj(url, token, parsed, conn, obj): + conn.request( + 'PUT', '%s/%s/%s' % (parsed.path, self.container, obj), + 'test', {'X-Auth-Token': token}) + return check_response(conn) + + def check_cors(url, token, parsed, conn, + method, obj, headers): + if method != 'OPTIONS': + headers['X-Auth-Token'] = token + conn.request( + method, '%s/%s/%s' % (parsed.path, self.container, obj), + '', headers) + return conn.getresponse() + + strict_cors = retry(is_strict_mode) + + resp = retry(put_cors_cont, '*') + resp.read() + self.assertEquals(resp.status // 100, 2) + + resp = retry(put_obj, 'cat') + resp.read() + self.assertEquals(resp.status // 100, 2) + + resp = retry(check_cors, + 'OPTIONS', 'cat', {'Origin': 'http://m.com'}) + self.assertEquals(resp.status, 401) + + resp = retry(check_cors, + 'OPTIONS', 'cat', + {'Origin': 'http://m.com', + 'Access-Control-Request-Method': 'GET'}) + + self.assertEquals(resp.status, 200) + resp.read() + headers = dict((k.lower(), v) for k, v in resp.getheaders()) + self.assertEquals(headers.get('access-control-allow-origin'), + '*') + + resp = retry(check_cors, + 'GET', 'cat', {'Origin': 'http://m.com'}) + self.assertEquals(resp.status, 200) + headers = dict((k.lower(), v) for k, v in resp.getheaders()) + self.assertEquals(headers.get('access-control-allow-origin'), + '*') + + resp = retry(check_cors, + 'GET', 'cat', {'Origin': 'http://m.com', + 'X-Web-Mode': 'True'}) + self.assertEquals(resp.status, 200) + headers = dict((k.lower(), v) for k, v in resp.getheaders()) + self.assertEquals(headers.get('access-control-allow-origin'), + '*') + + #################### + + resp = retry(put_cors_cont, 'http://secret.com') + resp.read() + self.assertEquals(resp.status // 100, 2) + + resp = retry(check_cors, + 'OPTIONS', 'cat', + {'Origin': 'http://m.com', + 'Access-Control-Request-Method': 'GET'}) + resp.read() + self.assertEquals(resp.status, 401) + + if strict_cors: + resp = retry(check_cors, + 'GET', 'cat', {'Origin': 'http://m.com'}) + resp.read() + self.assertEquals(resp.status, 200) + headers = dict((k.lower(), v) for k, v in resp.getheaders()) + self.assertTrue('access-control-allow-origin' not in headers) + + resp = retry(check_cors, + 'GET', 'cat', {'Origin': 'http://secret.com'}) + resp.read() + self.assertEquals(resp.status, 200) + headers = dict((k.lower(), v) for k, v in resp.getheaders()) + self.assertEquals(headers.get('access-control-allow-origin'), + 'http://secret.com') + else: + resp = retry(check_cors, + 'GET', 'cat', {'Origin': 'http://m.com'}) + resp.read() + self.assertEquals(resp.status, 200) + headers = dict((k.lower(), v) for k, v in resp.getheaders()) + self.assertEquals(headers.get('access-control-allow-origin'), + 'http://m.com') if __name__ == '__main__': diff --git a/test/functional/tests.py b/test/functional/tests.py index 0d9a9ef..ad87d7e 100644 --- a/test/functional/tests.py +++ b/test/functional/tests.py @@ -19,14 +19,16 @@ from datetime import datetime import os import hashlib +import hmac import json import locale import random import StringIO import time import threading -import uuid import unittest +import urllib +import uuid from nose import SkipTest from ConfigParser import ConfigParser @@ -36,7 +38,7 @@ from test.functional.swift_test_client import Account, Connection, File, \ from swift.common.constraints import MAX_FILE_SIZE, MAX_META_NAME_LENGTH, \ MAX_META_VALUE_LENGTH, MAX_META_COUNT, MAX_META_OVERALL_SIZE, \ MAX_OBJECT_NAME_LENGTH, CONTAINER_LISTING_LIMIT, ACCOUNT_LISTING_LIMIT, \ - MAX_ACCOUNT_NAME_LENGTH, MAX_CONTAINER_NAME_LENGTH + MAX_ACCOUNT_NAME_LENGTH, MAX_CONTAINER_NAME_LENGTH, MAX_HEADER_SIZE from gluster.swift.common.constraints import \ set_object_name_component_length, get_object_name_component_length @@ -50,7 +52,8 @@ default_constraints = dict(( ('container_listing_limit', CONTAINER_LISTING_LIMIT), ('account_listing_limit', ACCOUNT_LISTING_LIMIT), ('max_account_name_length', MAX_ACCOUNT_NAME_LENGTH), - ('max_container_name_length', MAX_CONTAINER_NAME_LENGTH))) + ('max_container_name_length', MAX_CONTAINER_NAME_LENGTH), + ('max_header_size', MAX_HEADER_SIZE))) constraints_conf = ConfigParser() conf_exists = constraints_conf.read('/etc/swift/swift.conf') # Constraints are set first from the test config, then from @@ -285,7 +288,7 @@ class TestAccount(Base): if try_count < 5: time.sleep(1) - self.assertEquals(info['container_count'], len(self.env.containers)) + self.assertEqual(info['container_count'], len(self.env.containers)) self.assert_status(204) def testContainerSerializedInfo(self): @@ -309,11 +312,11 @@ class TestAccount(Base): headers = dict(self.env.conn.response.getheaders()) if format_type == 'json': - self.assertEquals(headers['content-type'], - 'application/json; charset=utf-8') + self.assertEqual(headers['content-type'], + 'application/json; charset=utf-8') elif format_type == 'xml': - self.assertEquals(headers['content-type'], - 'application/xml; charset=utf-8') + self.assertEqual(headers['content-type'], + 'application/xml; charset=utf-8') def testListingLimit(self): limit = load_constraint('account_listing_limit') @@ -337,7 +340,7 @@ class TestAccount(Base): if isinstance(b[0], dict): b = [x['name'] for x in b] - self.assertEquals(a, b) + self.assertEqual(a, b) def testInvalidAuthToken(self): hdrs = {'X-Auth-Token': 'bogus_auth_token'} @@ -347,12 +350,12 @@ class TestAccount(Base): def testLastContainerMarker(self): for format_type in [None, 'json', 'xml']: containers = self.env.account.containers({'format': format_type}) - self.assertEquals(len(containers), len(self.env.containers)) + self.assertEqual(len(containers), len(self.env.containers)) self.assert_status(200) containers = self.env.account.containers( parms={'format': format_type, 'marker': containers[-1]}) - self.assertEquals(len(containers), 0) + self.assertEqual(len(containers), 0) if format_type is None: self.assert_status(204) else: @@ -380,8 +383,8 @@ class TestAccount(Base): parms={'format': format_type}) if isinstance(containers[0], dict): containers = [x['name'] for x in containers] - self.assertEquals(sorted(containers, cmp=locale.strcoll), - containers) + self.assertEqual(sorted(containers, cmp=locale.strcoll), + containers) class TestAccountUTF8(Base2, TestAccount): @@ -518,13 +521,13 @@ class TestContainer(Base): for format_type in [None, 'json', 'xml']: for prefix in prefixs: files = cont.files(parms={'prefix': prefix}) - self.assertEquals(files, sorted(prefix_files[prefix])) + self.assertEqual(files, sorted(prefix_files[prefix])) for format_type in [None, 'json', 'xml']: for prefix in prefixs: files = cont.files(parms={'limit': limit_count, 'prefix': prefix}) - self.assertEquals(len(files), limit_count) + self.assertEqual(len(files), limit_count) for file_item in files: self.assert_(file_item.startswith(prefix)) @@ -548,7 +551,7 @@ class TestContainer(Base): container = self.env.account.container(valid_utf8) self.assert_(container.create(cfg={'no_path_quote': True})) self.assert_(container.name in self.env.account.containers()) - self.assertEquals(container.files(), []) + self.assertEqual(container.files(), []) self.assert_(container.delete()) container = self.env.account.container(invalid_utf8) @@ -614,12 +617,12 @@ class TestContainer(Base): def testLastFileMarker(self): for format_type in [None, 'json', 'xml']: files = self.env.container.files({'format': format_type}) - self.assertEquals(len(files), len(self.env.files)) + self.assertEqual(len(files), len(self.env.files)) self.assert_status(200) files = self.env.container.files( parms={'format': format_type, 'marker': files[-1]}) - self.assertEquals(len(files), 0) + self.assertEqual(len(files), 0) if format_type is None: self.assert_status(204) @@ -665,14 +668,14 @@ class TestContainer(Base): files = self.env.container.files(parms={'format': format_type}) if isinstance(files[0], dict): files = [x['name'] for x in files] - self.assertEquals(sorted(files, cmp=locale.strcoll), files) + self.assertEqual(sorted(files, cmp=locale.strcoll), files) def testContainerInfo(self): info = self.env.container.info() self.assert_status(204) - self.assertEquals(info['object_count'], self.env.file_count) - self.assertEquals(info['bytes_used'], - self.env.file_count * self.env.file_size) + self.assertEqual(info['object_count'], self.env.file_count) + self.assertEqual(info['bytes_used'], + self.env.file_count * self.env.file_size) def testContainerInfoOnContainerThatDoesNotExist(self): container = self.env.account.container(Utils.create_name()) @@ -683,7 +686,7 @@ class TestContainer(Base): for format_type in [None, 'json', 'xml']: files = self.env.container.files(parms={'format': format_type, 'limit': 2}) - self.assertEquals(len(files), 2) + self.assertEqual(len(files), 2) def testTooLongName(self): cont = self.env.account.container('x' * 257) @@ -838,7 +841,7 @@ class TestContainerPaths(Base): if isinstance(files[0], dict): files = [str(x['name']) for x in files] - self.assertEquals(files, self.env.stored_files) + self.assertEqual(files, self.env.stored_files) for format_type in ('json', 'xml'): for file_item in self.env.container.files(parms={'format': @@ -846,13 +849,13 @@ class TestContainerPaths(Base): self.assert_(int(file_item['bytes']) >= 0) self.assert_('last_modified' in file_item) if file_item['name'].endswith('/'): - self.assertEquals(file_item['content_type'], - 'application/directory') + self.assertEqual(file_item['content_type'], + 'application/directory') def testStructure(self): def assert_listing(path, file_list): files = self.env.container.files(parms={'path': path}) - self.assertEquals(sorted(file_list, cmp=locale.strcoll), files) + self.assertEqual(sorted(file_list, cmp=locale.strcoll), files) if not normalized_urls: assert_listing('/', ['/dir1/', '/dir2/', '/file1', '/file A']) assert_listing('/dir1', @@ -1176,7 +1179,7 @@ class TestFile(Base): for i in container.files(parms={'format': 'json'}): file_types_read[i['name'].split('.')[1]] = i['content_type'] - self.assertEquals(file_types, file_types_read) + self.assertEqual(file_types, file_types_read) def testRangedGets(self): file_length = 10000 @@ -1201,7 +1204,7 @@ class TestFile(Base): self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(416) else: - self.assertEquals(file_item.read(hdrs=hdrs), data[-i:]) + self.assertEqual(file_item.read(hdrs=hdrs), data[-i:]) range_string = 'bytes=%d-' % (i) hdrs = {'Range': range_string} @@ -1350,9 +1353,9 @@ class TestFile(Base): info = file_item.info() self.assert_status(200) - self.assertEquals(info['content_length'], self.env.file_size) - self.assertEquals(info['etag'], md5) - self.assertEquals(info['content_type'], content_type) + self.assertEqual(info['content_length'], self.env.file_size) + self.assertEqual(info['etag'], md5) + self.assertEqual(info['content_type'], content_type) self.assert_('last_modified' in info) def testDeleteOfFileThatDoesNotExist(self): @@ -1395,7 +1398,7 @@ class TestFile(Base): file_item = self.env.container.file(file_item.name) self.assert_(file_item.initialize()) self.assert_status(200) - self.assertEquals(file_item.metadata, metadata) + self.assertEqual(file_item.metadata, metadata) def testGetContentType(self): file_name = Utils.create_name() @@ -1408,7 +1411,7 @@ class TestFile(Base): file_item = self.env.container.file(file_name) file_item.read() - self.assertEquals(content_type, file_item.content_type) + self.assertEqual(content_type, file_item.content_type) def testGetOnFileThatDoesNotExist(self): # in container that exists @@ -1449,7 +1452,7 @@ class TestFile(Base): file_item = self.env.container.file(file_item.name) self.assert_(file_item.initialize()) self.assert_status(200) - self.assertEquals(file_item.metadata, metadata) + self.assertEqual(file_item.metadata, metadata) def testSerialization(self): container = self.env.account.container(Utils.create_name()) @@ -1478,9 +1481,9 @@ class TestFile(Base): if f['name'] != file_item['name']: continue - self.assertEquals(file_item['content_type'], - f['content_type']) - self.assertEquals(int(file_item['bytes']), f['bytes']) + self.assertEqual(file_item['content_type'], + f['content_type']) + self.assertEqual(int(file_item['bytes']), f['bytes']) d = datetime.strptime( file_item['last_modified'].split('.')[0], @@ -1488,7 +1491,7 @@ class TestFile(Base): lm = time.mktime(d.timetuple()) if 'last_modified' in f: - self.assertEquals(f['last_modified'], lm) + self.assertEqual(f['last_modified'], lm) else: f['last_modified'] = lm @@ -1500,11 +1503,11 @@ class TestFile(Base): headers = dict(self.env.conn.response.getheaders()) if format_type == 'json': - self.assertEquals(headers['content-type'], - 'application/json; charset=utf-8') + self.assertEqual(headers['content-type'], + 'application/json; charset=utf-8') elif format_type == 'xml': - self.assertEquals(headers['content-type'], - 'application/xml; charset=utf-8') + self.assertEqual(headers['content-type'], + 'application/xml; charset=utf-8') lm_diff = max([f['last_modified'] for f in files]) -\ min([f['last_modified'] for f in files]) @@ -1547,7 +1550,7 @@ class TestFile(Base): self.assert_('etag' in headers.keys()) header_etag = headers['etag'].strip('"') - self.assertEquals(etag, header_etag) + self.assertEqual(etag, header_etag) def testChunkedPut(self): if (web_front_end == 'apache2'): @@ -1565,7 +1568,7 @@ class TestFile(Base): self.assert_(data == file_item.read()) info = file_item.info() - self.assertEquals(etag, info['etag']) + self.assertEqual(etag, info['etag']) class TestFileUTF8(Base2, TestFile): @@ -1677,12 +1680,30 @@ class TestDlo(Base): file_contents, "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffff") + def test_copy_manifest(self): + # Copying the manifest should result in another manifest + try: + man1_item = self.env.container.file('man1') + man1_item.copy(self.env.container.name, "copied-man1", + parms={'multipart-manifest': 'get'}) + + copied = self.env.container.file("copied-man1") + copied_contents = copied.read(parms={'multipart-manifest': 'get'}) + self.assertEqual(copied_contents, "man1-contents") + + copied_contents = copied.read() + self.assertEqual( + copied_contents, + "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee") + finally: + # try not to leave this around for other tests to stumble over + self.env.container.file("copied-man1").delete() class TestDloUTF8(Base2, TestDlo): set_up = False -class TestFileComparisonEnv: +class TestFileComparisonEnv(object): @classmethod def setUp(cls): cls.conn = Connection(config) @@ -1806,19 +1827,8 @@ class TestSloEnv(object): cls.conn.authenticate() if cls.slo_enabled is None: - status = cls.conn.make_request('GET', '/info', - cfg={'verbatim_path': True}) - if not (200 <= status <= 299): - # Can't tell if SLO is enabled or not since we're running - # against an old cluster, so let's skip the tests instead of - # possibly having spurious failures. - cls.slo_enabled = False - else: - # Don't bother looking for ValueError here. If something is - # responding to a GET /info request with invalid JSON, then - # the cluster is broken and a test failure will let us know. - cluster_info = json.loads(cls.conn.response.read()) - cls.slo_enabled = 'slo' in cluster_info + cluster_info = cls.conn.cluster_info() + cls.slo_enabled = 'slo' in cluster_info if not cls.slo_enabled: return @@ -2034,5 +2044,347 @@ class TestSloUTF8(Base2, TestSlo): set_up = False +class TestObjectVersioningEnv(object): + versioning_enabled = None # tri-state: None initially, then True/False + + @classmethod + def setUp(cls): + cls.conn = Connection(config) + cls.conn.authenticate() + + cls.account = Account(cls.conn, config.get('account', + config['username'])) + + # avoid getting a prefix that stops halfway through an encoded + # character + prefix = Utils.create_name().decode("utf-8")[:10].encode("utf-8") + + cls.versions_container = cls.account.container(prefix + "-versions") + if not cls.versions_container.create(): + raise ResponseError(cls.conn.response) + + cls.container = cls.account.container(prefix + "-objs") + if not cls.container.create( + hdrs={'X-Versions-Location': cls.versions_container.name}): + raise ResponseError(cls.conn.response) + + container_info = cls.container.info() + # if versioning is off, then X-Versions-Location won't persist + cls.versioning_enabled = 'versions' in container_info + + +class TestObjectVersioning(Base): + env = TestObjectVersioningEnv + set_up = False + + def setUp(self): + super(TestObjectVersioning, self).setUp() + if self.env.versioning_enabled is False: + raise SkipTest("Object versioning not enabled") + elif self.env.versioning_enabled is not True: + # just some sanity checking + raise Exception( + "Expected versioning_enabled to be True/False, got %r" % + (self.env.versioning_enabled,)) + + def test_overwriting(self): + container = self.env.container + versions_container = self.env.versions_container + obj_name = Utils.create_name() + + versioned_obj = container.file(obj_name) + versioned_obj.write("aaaaa") + + self.assertEqual(0, versions_container.info()['object_count']) + + versioned_obj.write("bbbbb") + + # the old version got saved off + self.assertEqual(1, versions_container.info()['object_count']) + versioned_obj_name = versions_container.files()[0] + self.assertEqual( + "aaaaa", versions_container.file(versioned_obj_name).read()) + + # if we overwrite it again, there are two versions + versioned_obj.write("ccccc") + self.assertEqual(2, versions_container.info()['object_count']) + + # as we delete things, the old contents return + self.assertEqual("ccccc", versioned_obj.read()) + versioned_obj.delete() + self.assertEqual("bbbbb", versioned_obj.read()) + versioned_obj.delete() + self.assertEqual("aaaaa", versioned_obj.read()) + versioned_obj.delete() + self.assertRaises(ResponseError, versioned_obj.read) + + +class TestObjectVersioningUTF8(Base2, TestObjectVersioning): + set_up = False + + +class TestTempurlEnv(object): + tempurl_enabled = None # tri-state: None initially, then True/False + + @classmethod + def setUp(cls): + cls.conn = Connection(config) + cls.conn.authenticate() + + if cls.tempurl_enabled is None: + cluster_info = cls.conn.cluster_info() + cls.tempurl_enabled = 'tempurl' in cluster_info + if not cls.tempurl_enabled: + return + cls.tempurl_methods = cluster_info['tempurl']['methods'] + + cls.tempurl_key = Utils.create_name() + cls.tempurl_key2 = Utils.create_name() + + cls.account = Account( + cls.conn, config.get('account', config['username'])) + cls.account.delete_containers() + cls.account.update_metadata({ + 'temp-url-key': cls.tempurl_key, + 'temp-url-key-2': cls.tempurl_key2 + }) + + cls.container = cls.account.container(Utils.create_name()) + if not cls.container.create(): + raise ResponseError(cls.conn.response) + + cls.obj = cls.container.file(Utils.create_name()) + cls.obj.write("obj contents") + cls.other_obj = cls.container.file(Utils.create_name()) + cls.other_obj.write("other obj contents") + + +class TestTempurl(Base): + env = TestTempurlEnv + set_up = False + + def setUp(self): + super(TestTempurl, self).setUp() + if self.env.tempurl_enabled is False: + raise SkipTest("TempURL not enabled") + elif self.env.tempurl_enabled is not True: + # just some sanity checking + raise Exception( + "Expected tempurl_enabled to be True/False, got %r" % + (self.env.tempurl_enabled,)) + + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'GET', expires, self.env.conn.make_path(self.env.obj.path), + self.env.tempurl_key) + self.obj_tempurl_parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + def tempurl_sig(self, method, expires, path, key): + return hmac.new( + key, + '%s\n%s\n%s' % (method, expires, urllib.unquote(path)), + hashlib.sha1).hexdigest() + + def test_GET(self): + contents = self.env.obj.read( + parms=self.obj_tempurl_parms, + cfg={'no_auth_token': True}) + self.assertEqual(contents, "obj contents") + + # GET tempurls also allow HEAD requests + self.assert_(self.env.obj.info(parms=self.obj_tempurl_parms, + cfg={'no_auth_token': True})) + + def test_GET_with_key_2(self): + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'GET', expires, self.env.conn.make_path(self.env.obj.path), + self.env.tempurl_key2) + parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + contents = self.env.obj.read(parms=parms, cfg={'no_auth_token': True}) + self.assertEqual(contents, "obj contents") + + def test_PUT(self): + new_obj = self.env.container.file(Utils.create_name()) + + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'PUT', expires, self.env.conn.make_path(new_obj.path), + self.env.tempurl_key) + put_parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + new_obj.write('new obj contents', + parms=put_parms, cfg={'no_auth_token': True}) + self.assertEqual(new_obj.read(), "new obj contents") + + # PUT tempurls also allow HEAD requests + self.assert_(new_obj.info(parms=put_parms, + cfg={'no_auth_token': True})) + + def test_HEAD(self): + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'HEAD', expires, self.env.conn.make_path(self.env.obj.path), + self.env.tempurl_key) + head_parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + self.assert_(self.env.obj.info(parms=head_parms, + cfg={'no_auth_token': True})) + # HEAD tempurls don't allow PUT or GET requests, despite the fact that + # PUT and GET tempurls both allow HEAD requests + self.assertRaises(ResponseError, self.env.other_obj.read, + cfg={'no_auth_token': True}, + parms=self.obj_tempurl_parms) + self.assert_status([401]) + + self.assertRaises(ResponseError, self.env.other_obj.write, + 'new contents', + cfg={'no_auth_token': True}, + parms=self.obj_tempurl_parms) + self.assert_status([401]) + + def test_different_object(self): + contents = self.env.obj.read( + parms=self.obj_tempurl_parms, + cfg={'no_auth_token': True}) + self.assertEqual(contents, "obj contents") + + self.assertRaises(ResponseError, self.env.other_obj.read, + cfg={'no_auth_token': True}, + parms=self.obj_tempurl_parms) + self.assert_status([401]) + + def test_changing_sig(self): + contents = self.env.obj.read( + parms=self.obj_tempurl_parms, + cfg={'no_auth_token': True}) + self.assertEqual(contents, "obj contents") + + parms = self.obj_tempurl_parms.copy() + if parms['temp_url_sig'][0] == 'a': + parms['temp_url_sig'] = 'b' + parms['temp_url_sig'][1:] + else: + parms['temp_url_sig'] = 'a' + parms['temp_url_sig'][1:] + + self.assertRaises(ResponseError, self.env.obj.read, + cfg={'no_auth_token': True}, + parms=parms) + self.assert_status([401]) + + def test_changing_expires(self): + contents = self.env.obj.read( + parms=self.obj_tempurl_parms, + cfg={'no_auth_token': True}) + self.assertEqual(contents, "obj contents") + + parms = self.obj_tempurl_parms.copy() + if parms['temp_url_expires'][-1] == '0': + parms['temp_url_expires'] = parms['temp_url_expires'][:-1] + '1' + else: + parms['temp_url_expires'] = parms['temp_url_expires'][:-1] + '0' + + self.assertRaises(ResponseError, self.env.obj.read, + cfg={'no_auth_token': True}, + parms=parms) + self.assert_status([401]) + + +class TestTempurlUTF8(Base2, TestTempurl): + set_up = False + + +class TestSloTempurlEnv(object): + enabled = None # tri-state: None initially, then True/False + + @classmethod + def setUp(cls): + cls.conn = Connection(config) + cls.conn.authenticate() + + if cls.enabled is None: + cluster_info = cls.conn.cluster_info() + cls.enabled = 'tempurl' in cluster_info and 'slo' in cluster_info + + cls.tempurl_key = Utils.create_name() + + cls.account = Account( + cls.conn, config.get('account', config['username'])) + cls.account.delete_containers() + cls.account.update_metadata({'temp-url-key': cls.tempurl_key}) + + cls.manifest_container = cls.account.container(Utils.create_name()) + cls.segments_container = cls.account.container(Utils.create_name()) + if not cls.manifest_container.create(): + raise ResponseError(cls.conn.response) + if not cls.segments_container.create(): + raise ResponseError(cls.conn.response) + + seg1 = cls.segments_container.file(Utils.create_name()) + seg1.write('1' * 1024 * 1024) + + seg2 = cls.segments_container.file(Utils.create_name()) + seg2.write('2' * 1024 * 1024) + + cls.manifest_data = [{'size_bytes': 1024 * 1024, + 'etag': seg1.md5, + 'path': '/%s/%s' % (cls.segments_container.name, + seg1.name)}, + {'size_bytes': 1024 * 1024, + 'etag': seg2.md5, + 'path': '/%s/%s' % (cls.segments_container.name, + seg2.name)}] + + cls.manifest = cls.manifest_container.file(Utils.create_name()) + cls.manifest.write( + json.dumps(cls.manifest_data), + parms={'multipart-manifest': 'put'}) + + +class TestSloTempurl(Base): + env = TestSloTempurlEnv + set_up = False + + def setUp(self): + super(TestSloTempurl, self).setUp() + if self.env.enabled is False: + raise SkipTest("TempURL and SLO not both enabled") + elif self.env.enabled is not True: + # just some sanity checking + raise Exception( + "Expected enabled to be True/False, got %r" % + (self.env.enabled,)) + + def tempurl_sig(self, method, expires, path, key): + return hmac.new( + key, + '%s\n%s\n%s' % (method, expires, urllib.unquote(path)), + hashlib.sha1).hexdigest() + + def test_GET(self): + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'GET', expires, self.env.conn.make_path(self.env.manifest.path), + self.env.tempurl_key) + parms = {'temp_url_sig': sig, 'temp_url_expires': str(expires)} + + contents = self.env.manifest.read( + parms=parms, + cfg={'no_auth_token': True}) + self.assertEqual(len(contents), 2 * 1024 * 1024) + + # GET tempurls also allow HEAD requests + self.assert_(self.env.manifest.info( + parms=parms, cfg={'no_auth_token': True})) + + +class TestSloTempurlUTF8(Base2, TestSloTempurl): + set_up = False + + if __name__ == '__main__': unittest.main() diff --git a/test/functional_auth/tempauth/conf/account-server.conf b/test/functional_auth/common_conf/account-server.conf index 4996367..549a094 100644 --- a/test/functional_auth/tempauth/conf/account-server.conf +++ b/test/functional_auth/common_conf/account-server.conf @@ -30,3 +30,7 @@ log_level = WARN # normal request logging for the account server to unclutter the log # files. Warnings and errors will still be logged. log_requests = off + +# The following parameter is used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs diff --git a/test/functional_auth/gswauth/conf/container-server.conf b/test/functional_auth/common_conf/container-server.conf index 122d97e..487ccb8 100644 --- a/test/functional_auth/gswauth/conf/container-server.conf +++ b/test/functional_auth/common_conf/container-server.conf @@ -33,3 +33,7 @@ log_requests = off #enable object versioning for functional test allow_versions = on + +# The following parameter is used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs diff --git a/test/functional_auth/gswauth/conf/fs.conf b/test/functional_auth/common_conf/fs.conf index b06a854..b06a854 100644 --- a/test/functional_auth/gswauth/conf/fs.conf +++ b/test/functional_auth/common_conf/fs.conf diff --git a/test/functional_auth/common_conf/object-expirer.conf b/test/functional_auth/common_conf/object-expirer.conf new file mode 100644 index 0000000..4449ee2 --- /dev/null +++ b/test/functional_auth/common_conf/object-expirer.conf @@ -0,0 +1,27 @@ +#TODO: Add documentation to explain various options +#For now, refer: https://github.com/openstack/swift/blob/master/etc/object-expirer.conf-sample + +[DEFAULT] + +[object-expirer] +user = root +log_facility = LOG_LOCAL2 +log_level = DEBUG +# The following parameters are used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs +expiring_objects_account_name = expiring + +interval = 30 + +[pipeline:main] +pipeline = catch_errors cache proxy-server + +[app:proxy-server] +use = egg:gluster_swift#proxy + +[filter:cache] +use = egg:swift#memcache + +[filter:catch_errors] +use = egg:swift#catch_errors diff --git a/test/functional_auth/gswauth/conf/object-server.conf b/test/functional_auth/common_conf/object-server.conf index 3cb9ead..2599c87 100644 --- a/test/functional_auth/gswauth/conf/object-server.conf +++ b/test/functional_auth/common_conf/object-server.conf @@ -46,3 +46,8 @@ disk_chunk_size = 65536 # Adjust this value match whatever is set for the disk_chunk_size initially. # This will provide a reasonable starting point for tuning this value. network_chunk_size = 65556 + +# The following parameter is used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs +expiring_objects_account_name = expiring diff --git a/test/functional_auth/gswauth/conf/swift.conf b/test/functional_auth/common_conf/swift.conf index f64ba5a..f64ba5a 100644 --- a/test/functional_auth/gswauth/conf/swift.conf +++ b/test/functional_auth/common_conf/swift.conf diff --git a/test/functional_auth/swiftkerbauth/conf/test.conf b/test/functional_auth/common_conf/test.conf index 643f2d1..15c9aea 100644 --- a/test/functional_auth/swiftkerbauth/conf/test.conf +++ b/test/functional_auth/common_conf/test.conf @@ -1,16 +1,25 @@ [func_test] -# Swiftkerbauth configuration +# sample config auth_host = 127.0.0.1 auth_port = 8080 +auth_ssl = no auth_prefix = /auth/ -auth_scheme = http:// -auth_mode = passive -auth_version = 1 -domain_name = RHELBOX.COM +## sample config for Swift with Keystone +#auth_version = 2 +#auth_host = localhost +#auth_port = 5000 +#auth_ssl = no +#auth_prefix = /v2.0/ + +# GSWauth internal admin user configuration information +admin_key = gswauthkey +admin_user = .super_admin + +# Gluster setup information +devices = /mnt/gluster-object +gsmetadata_volume = gsmetadata -#All the accounts, users & passwords to be prepared on kerberos server. # Primary functional test account (needs admin access to the account) -# Note: Account name to be prepared on kerberos server 'AUTH_accoun' account = test username = tester password = testing diff --git a/test/functional_auth/gswauth/conf/account-server.conf b/test/functional_auth/gswauth/conf/account-server.conf deleted file mode 100644 index 4996367..0000000 --- a/test/functional_auth/gswauth/conf/account-server.conf +++ /dev/null @@ -1,32 +0,0 @@ -[DEFAULT] -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the account-server workers start, -# you can *consider* setting this value to "false" to reduce the per-request -# overhead it can incur. -# -# *** Keep false for Functional Tests *** -mount_check = false -bind_port = 6012 -# -# Override swift's default behaviour for fallocate. -disable_fallocate = true -# -# One or two workers should be sufficient for almost any installation of -# Gluster. -workers = 1 - -[pipeline:main] -pipeline = account-server - -[app:account-server] -use = egg:gluster_swift#account -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# After ensuring things are running in a stable manner, you can turn off -# normal request logging for the account server to unclutter the log -# files. Warnings and errors will still be logged. -log_requests = off diff --git a/test/functional_auth/gswauth/conf/object-expirer.conf b/test/functional_auth/gswauth/conf/object-expirer.conf deleted file mode 100644 index b75963c..0000000 --- a/test/functional_auth/gswauth/conf/object-expirer.conf +++ /dev/null @@ -1,17 +0,0 @@ -[DEFAULT] - -[object-expirer] -# auto_create_account_prefix = . - -[pipeline:main] -pipeline = catch_errors cache proxy-server - -[app:proxy-server] -use = egg:swift#proxy - -[filter:cache] -use = egg:swift#memcache -memcache_servers = 127.0.0.1:11211 - -[filter:catch_errors] -use = egg:swift#catch_errors diff --git a/test/functional_auth/gswauth/conf/proxy-server.conf b/test/functional_auth/gswauth/conf/proxy-server.conf index 165cb0c..0a3c1f6 100644 --- a/test/functional_auth/gswauth/conf/proxy-server.conf +++ b/test/functional_auth/gswauth/conf/proxy-server.conf @@ -5,7 +5,7 @@ user = root workers = 1 [pipeline:main] -pipeline = catch_errors healthcheck proxy-logging cache gswauth proxy-logging proxy-server +pipeline = catch_errors healthcheck proxy-logging cache tempurl gswauth proxy-logging proxy-server [app:proxy-server] use = egg:gluster_swift#proxy @@ -48,6 +48,10 @@ object_chunk_size = 65536 # amount of memory available on the system can accommodate increased values # for object_chunk_size. put_queue_depth = 10 +# The following parameter is used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs +expiring_objects_account_name = expiring [filter:catch_errors] use = egg:swift#catch_errors @@ -58,21 +62,17 @@ use = egg:swift#proxy_logging [filter:healthcheck] use = egg:swift#healthcheck -[filter:tempauth] -use = egg:swift#tempauth -user_admin_admin = admin .admin .reseller_admin -user_test_tester = testing .admin -user_test2_tester2 = testing2 .admin -user_test_tester3 = testing3 +[filter:cache] +use = egg:swift#memcache +# Update this line to contain a comma separated list of memcache servers +# shared by all nodes running the proxy-server service. +memcache_servers = localhost:11211 + +[filter:tempurl] +use = egg:swift#tempurl [filter:gswauth] use = egg:gluster_swift#gswauth set log_name = gswauth super_admin_key = gswauthkey metadata_volume = gsmetadata - -[filter:cache] -use = egg:swift#memcache -# Update this line to contain a comma separated list of memcache servers -# shared by all nodes running the proxy-server service. -memcache_servers = localhost:11211 diff --git a/test/functional_auth/keystone/conf/account-server.conf b/test/functional_auth/keystone/conf/account-server.conf deleted file mode 100644 index 4996367..0000000 --- a/test/functional_auth/keystone/conf/account-server.conf +++ /dev/null @@ -1,32 +0,0 @@ -[DEFAULT] -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the account-server workers start, -# you can *consider* setting this value to "false" to reduce the per-request -# overhead it can incur. -# -# *** Keep false for Functional Tests *** -mount_check = false -bind_port = 6012 -# -# Override swift's default behaviour for fallocate. -disable_fallocate = true -# -# One or two workers should be sufficient for almost any installation of -# Gluster. -workers = 1 - -[pipeline:main] -pipeline = account-server - -[app:account-server] -use = egg:gluster_swift#account -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# After ensuring things are running in a stable manner, you can turn off -# normal request logging for the account server to unclutter the log -# files. Warnings and errors will still be logged. -log_requests = off diff --git a/test/functional_auth/keystone/conf/container-server.conf b/test/functional_auth/keystone/conf/container-server.conf deleted file mode 100644 index 122d97e..0000000 --- a/test/functional_auth/keystone/conf/container-server.conf +++ /dev/null @@ -1,35 +0,0 @@ -[DEFAULT] -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the container-server workers -# start, you can *consider* setting this value to "false" to reduce the -# per-request overhead it can incur. -# -# *** Keep false for Functional Tests *** -mount_check = false -bind_port = 6011 -# -# Override swift's default behaviour for fallocate. -disable_fallocate = true -# -# One or two workers should be sufficient for almost any installation of -# Gluster. -workers = 1 - -[pipeline:main] -pipeline = container-server - -[app:container-server] -use = egg:gluster_swift#container -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# After ensuring things are running in a stable manner, you can turn off -# normal request logging for the container server to unclutter the log -# files. Warnings and errors will still be logged. -log_requests = off - -#enable object versioning for functional test -allow_versions = on diff --git a/test/functional_auth/keystone/conf/fs.conf b/test/functional_auth/keystone/conf/fs.conf deleted file mode 100644 index b06a854..0000000 --- a/test/functional_auth/keystone/conf/fs.conf +++ /dev/null @@ -1,19 +0,0 @@ -[DEFAULT] -# -# IP address of a node in the GlusterFS server cluster hosting the -# volumes to be served via Swift API. -mount_ip = localhost - -# Performance optimization parameter. When turned off, the filesystem will -# see a reduced number of stat calls, resulting in substantially faster -# response time for GET and HEAD container requests on containers with large -# numbers of objects, at the expense of an accurate count of combined bytes -# used by all objects in the container. For most installations "off" works -# fine. -# -# *** Keep on for Functional Tests *** -accurate_size_in_listing = on - -# *** Keep on for Functional Tests *** -container_update_object_count = on -account_update_container_count = on diff --git a/test/functional_auth/keystone/conf/object-server.conf b/test/functional_auth/keystone/conf/object-server.conf deleted file mode 100644 index 3cb9ead..0000000 --- a/test/functional_auth/keystone/conf/object-server.conf +++ /dev/null @@ -1,48 +0,0 @@ -[DEFAULT] -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the object-server workers start, -# you can *consider* setting this value to "false" to reduce the per-request -# overhead it can incur. -# -# *** Keep false for Functional Tests *** -mount_check = false -bind_port = 6010 -# -# Maximum number of clients one worker can process simultaneously (it will -# actually accept N + 1). Setting this to one (1) will only handle one request -# at a time, without accepting another request concurrently. By increasing the -# number of workers to a much higher value, one can prevent slow file system -# operations for one request from starving other requests. -max_clients = 1024 -# -# If not doing the above, setting this value initially to match the number of -# CPUs is a good starting point for determining the right value. -workers = 1 -# Override swift's default behaviour for fallocate. -disable_fallocate = true - -[pipeline:main] -pipeline = object-server - -[app:object-server] -use = egg:gluster_swift#object -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# For performance, after ensuring things are running in a stable manner, you -# can turn off normal request logging for the object server to reduce the -# per-request overhead and unclutter the log files. Warnings and errors will -# still be logged. -log_requests = off -# -# Adjust this value to match the stripe width of the underlying storage array -# (not the stripe element size). This will provide a reasonable starting point -# for tuning this value. -disk_chunk_size = 65536 -# -# Adjust this value match whatever is set for the disk_chunk_size initially. -# This will provide a reasonable starting point for tuning this value. -network_chunk_size = 65556 diff --git a/test/functional_auth/keystone/conf/proxy-server.conf b/test/functional_auth/keystone/conf/proxy-server.conf index 084e6a5..72a84da 100644 --- a/test/functional_auth/keystone/conf/proxy-server.conf +++ b/test/functional_auth/keystone/conf/proxy-server.conf @@ -6,7 +6,7 @@ workers = 1 [pipeline:main] #pipeline = catch_errors healthcheck proxy-logging cache tempauth proxy-logging proxy-server -pipeline = catch_errors healthcheck proxy-logging cache authtoken keystoneauth proxy-logging proxy-server +pipeline = catch_errors healthcheck proxy-logging cache tempurl authtoken keystoneauth proxy-logging proxy-server [app:proxy-server] use = egg:gluster_swift#proxy @@ -49,6 +49,10 @@ object_chunk_size = 65536 # amount of memory available on the system can accommodate increased values # for object_chunk_size. put_queue_depth = 10 +# The following parameter is used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs +expiring_objects_account_name = expiring [filter:catch_errors] use = egg:swift#catch_errors @@ -59,13 +63,14 @@ use = egg:swift#proxy_logging [filter:healthcheck] use = egg:swift#healthcheck -[filter:tempauth] -use = egg:swift#tempauth -user_admin_admin = admin .admin .reseller_admin -user_d4dde08c621a4f0fb4cde0ac6a62aa0c_tester = testing .admin -user_test_tester = testing .admin -user_test2_tester2 = testing2 .admin -user_test_tester3 = testing3 +[filter:cache] +use = egg:swift#memcache +# Update this line to contain a comma separated list of memcache servers +# shared by all nodes running the proxy-server service. +memcache_servers = localhost:11211 + +[filter:tempurl] +use = egg:swift#tempurl [filter:authtoken] paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory @@ -87,8 +92,4 @@ operator_roles = admin is_admin = true cache = swift.cache -[filter:cache] -use = egg:swift#memcache -# Update this line to contain a comma separated list of memcache servers -# shared by all nodes running the proxy-server service. -memcache_servers = localhost:11211 + diff --git a/test/functional_auth/keystone/conf/swift.conf b/test/functional_auth/keystone/conf/swift.conf deleted file mode 100644 index ce9a4d0..0000000 --- a/test/functional_auth/keystone/conf/swift.conf +++ /dev/null @@ -1,85 +0,0 @@ -[DEFAULT] - - -[swift-hash] -# random unique string that can never change (DO NOT LOSE) -swift_hash_path_suffix = gluster - - -# The swift-constraints section sets the basic constraints on data -# saved in the swift cluster. - -[swift-constraints] - -# max_file_size is the largest "normal" object that can be saved in -# the cluster. This is also the limit on the size of each segment of -# a "large" object when using the large object manifest support. -# This value is set in bytes. Setting it to lower than 1MiB will cause -# some tests to fail. -# Default is 1 TiB = 2**30*1024 -max_file_size = 1099511627776 - - -# max_meta_name_length is the max number of bytes in the utf8 encoding -# of the name portion of a metadata header. - -#max_meta_name_length = 128 - - -# max_meta_value_length is the max number of bytes in the utf8 encoding -# of a metadata value - -#max_meta_value_length = 256 - - -# max_meta_count is the max number of metadata keys that can be stored -# on a single account, container, or object - -#max_meta_count = 90 - - -# max_meta_overall_size is the max number of bytes in the utf8 encoding -# of the metadata (keys + values) - -#max_meta_overall_size = 4096 - - -# max_object_name_length is the max number of bytes in the utf8 encoding of an -# object name: Gluster FS can handle much longer file names, but the length -# between the slashes of the URL is handled below. Remember that most web -# clients can't handle anything greater than 2048, and those that do are -# rather clumsy. - -max_object_name_length = 2048 - -# max_object_name_component_length (GlusterFS) is the max number of bytes in -# the utf8 encoding of an object name component (the part between the -# slashes); this is a limit imposed by the underlying file system (for XFS it -# is 255 bytes). - -max_object_name_component_length = 255 - -# container_listing_limit is the default (and max) number of items -# returned for a container listing request - -#container_listing_limit = 10000 - - -# account_listing_limit is the default (and max) number of items returned -# for an account listing request - -#account_listing_limit = 10000 - - -# max_account_name_length is the max number of bytes in the utf8 encoding of -# an account name: Gluster FS Filename limit (XFS limit?), must be the same -# size as max_object_name_component_length above. - -max_account_name_length = 255 - - -# max_container_name_length is the max number of bytes in the utf8 encoding -# of a container name: Gluster FS Filename limit (XFS limit?), must be the same -# size as max_object_name_component_length above. - -max_container_name_length = 255 diff --git a/test/functional_auth/swiftkerbauth/__init__.py b/test/functional_auth/swiftkerbauth/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/test/functional_auth/swiftkerbauth/__init__.py +++ /dev/null diff --git a/test/functional_auth/swiftkerbauth/conf/account-server.conf b/test/functional_auth/swiftkerbauth/conf/account-server.conf deleted file mode 100644 index 9ad458a..0000000 --- a/test/functional_auth/swiftkerbauth/conf/account-server.conf +++ /dev/null @@ -1,36 +0,0 @@ -[DEFAULT] -# -# Default gluster mount point to be used for object store,can be changed by -# setting the following value in {account,container,object}-server.conf files. -# It is recommended to keep this value same for all the three services but can -# be kept different if environment demands. -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the account-server workers start, -# you can *consider* setting this value to "false" to reduce the per-request -# overhead it can incur. -mount_check = true -bind_port = 6012 -# -# Override swift's default behaviour for fallocate. -disable_fallocate = true -# -# One or two workers should be sufficient for almost any installation of -# Gluster. -workers = 1 - -[pipeline:main] -pipeline = account-server - -[app:account-server] -use = egg:gluster_swift#account -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# After ensuring things are running in a stable manner, you can turn off -# normal request logging for the account server to unclutter the log -# files. Warnings and errors will still be logged. -log_requests = off - diff --git a/test/functional_auth/swiftkerbauth/conf/container-server.conf b/test/functional_auth/swiftkerbauth/conf/container-server.conf deleted file mode 100644 index a406b4d..0000000 --- a/test/functional_auth/swiftkerbauth/conf/container-server.conf +++ /dev/null @@ -1,36 +0,0 @@ -[DEFAULT] -# -# Default gluster mount point to be used for object store,can be changed by -# setting the following value in {account,container,object}-server.conf files. -# It is recommended to keep this value same for all the three services but can -# be kept different if environment demands. -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the container-server workers -# start, you can *consider* setting this value to "false" to reduce the -# per-request overhead it can incur. -mount_check = true -bind_port = 6011 -# -# Override swift's default behaviour for fallocate. -disable_fallocate = true -# -# One or two workers should be sufficient for almost any installation of -# Gluster. -workers = 1 - -[pipeline:main] -pipeline = container-server - -[app:container-server] -use = egg:gluster_swift#container -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# After ensuring things are running in a stable manner, you can turn off -# normal request logging for the container server to unclutter the log -# files. Warnings and errors will still be logged. -log_requests = off - diff --git a/test/functional_auth/swiftkerbauth/conf/fs.conf b/test/functional_auth/swiftkerbauth/conf/fs.conf deleted file mode 100644 index 6d2a791..0000000 --- a/test/functional_auth/swiftkerbauth/conf/fs.conf +++ /dev/null @@ -1,13 +0,0 @@ -[DEFAULT] -# -# IP address of a node in the GlusterFS server cluster hosting the -# volumes to be served via Swift API. -mount_ip = localhost - -# Performance optimization parameter. When turned off, the filesystem will -# see a reduced number of stat calls, resulting in substantially faster -# response time for GET and HEAD container requests on containers with large -# numbers of objects, at the expense of an accurate count of combined bytes -# used by all objects in the container. For most installations "off" works -# fine. -accurate_size_in_listing = off
\ No newline at end of file diff --git a/test/functional_auth/swiftkerbauth/conf/object-server.conf b/test/functional_auth/swiftkerbauth/conf/object-server.conf deleted file mode 100644 index d10d282..0000000 --- a/test/functional_auth/swiftkerbauth/conf/object-server.conf +++ /dev/null @@ -1,51 +0,0 @@ -[DEFAULT] -# -# Default gluster mount point to be used for object store,can be changed by -# setting the following value in {account,container,object}-server.conf files. -# It is recommended to keep this value same for all the three services but can -# be kept different if environment demands. -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the object-server workers start, -# you can *consider* setting this value to "false" to reduce the per-request -# overhead it can incur. -mount_check = true -bind_port = 6010 -# -# Maximum number of clients one worker can process simultaneously (it will -# actually accept N + 1). Setting this to one (1) will only handle one request -# at a time, without accepting another request concurrently. By increasing the -# number of workers to a much higher value, one can prevent slow file system -# operations for one request from starving other requests. -max_clients = 1024 -# -# If not doing the above, setting this value initially to match the number of -# CPUs is a good starting point for determining the right value. -workers = 1 -# Override swift's default behaviour for fallocate. -disable_fallocate = true - -[pipeline:main] -pipeline = object-server - -[app:object-server] -use = egg:gluster_swift#object -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# For performance, after ensuring things are running in a stable manner, you -# can turn off normal request logging for the object server to reduce the -# per-request overhead and unclutter the log files. Warnings and errors will -# still be logged. -log_requests = off -# -# Adjust this value to match the stripe width of the underlying storage array -# (not the stripe element size). This will provide a reasonable starting point -# for tuning this value. -disk_chunk_size = 65536 -# -# Adjust this value match whatever is set for the disk_chunk_size initially. -# This will provide a reasonable starting point for tuning this value. -network_chunk_size = 65536 diff --git a/test/functional_auth/swiftkerbauth/conf/proxy-server.conf b/test/functional_auth/swiftkerbauth/conf/proxy-server.conf index e6f8aa1..248f87c 100644 --- a/test/functional_auth/swiftkerbauth/conf/proxy-server.conf +++ b/test/functional_auth/swiftkerbauth/conf/proxy-server.conf @@ -5,7 +5,7 @@ user = root workers = 1 [pipeline:main] -pipeline = catch_errors healthcheck proxy-logging cache proxy-logging kerbauth proxy-server +pipeline = catch_errors healthcheck proxy-logging cache tempurl proxy-logging kerbauth proxy-server [app:proxy-server] use = egg:gluster_swift#proxy @@ -48,6 +48,10 @@ object_chunk_size = 65536 # amount of memory available on the system can accommodate increased values # for object_chunk_size. put_queue_depth = 10 +# The following parameter is used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs +expiring_objects_account_name = expiring [filter:catch_errors] use = egg:swift#catch_errors @@ -59,12 +63,15 @@ access_log_level = WARN [filter:healthcheck] use = egg:swift#healthcheck -[filter:kerbauth] -use = egg:gluster_swift#kerbauth -ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth - [filter:cache] use = egg:swift#memcache # Update this line to contain a comma separated list of memcache servers # shared by all nodes running the proxy-server service. memcache_servers = localhost:11211 + +[filter:tempurl] +use = egg:swift#tempurl + +[filter:kerbauth] +use = egg:gluster_swift#kerbauth +ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth diff --git a/test/functional_auth/swiftkerbauth/conf/swift.conf b/test/functional_auth/swiftkerbauth/conf/swift.conf deleted file mode 100644 index 0d25209..0000000 --- a/test/functional_auth/swiftkerbauth/conf/swift.conf +++ /dev/null @@ -1,84 +0,0 @@ -[DEFAULT] - - -[swift-hash] -# random unique string that can never change (DO NOT LOSE) -swift_hash_path_suffix = gluster - - -# The swift-constraints section sets the basic constraints on data -# saved in the swift cluster. - -[swift-constraints] -# max_file_size is the largest "normal" object that can be saved in -# the cluster. This is also the limit on the size of each segment of -# a "large" object when using the large object manifest support. -# This value is set in bytes. Setting it to lower than 1MiB will cause -# some tests to fail. -# Default is 1 TiB = 2**30*1024 - -max_file_size = 1099511627776 - -# max_meta_name_length is the max number of bytes in the utf8 encoding -# of the name portion of a metadata header. - -#max_meta_name_length = 128 - - -# max_meta_value_length is the max number of bytes in the utf8 encoding -# of a metadata value - -#max_meta_value_length = 256 - - -# max_meta_count is the max number of metadata keys that can be stored -# on a single account, container, or object - -#max_meta_count = 90 - - -# max_meta_overall_size is the max number of bytes in the utf8 encoding -# of the metadata (keys + values) - -#max_meta_overall_size = 4096 - - -# max_object_name_length is the max number of bytes in the utf8 encoding of an -# object name: Gluster FS can handle much longer file names, but the length -# between the slashes of the URL is handled below. Remember that most web -# clients can't handle anything greater than 2048, and those that do are -# rather clumsy. - -max_object_name_length = 2048 - -# max_object_name_component_length (GlusterFS) is the max number of bytes in -# the utf8 encoding of an object name component (the part between the -# slashes); this is a limit imposed by the underlying file system (for XFS it -# is 255 bytes). - -max_object_name_component_length = 255 - -# container_listing_limit is the default (and max) number of items -# returned for a container listing request - -#container_listing_limit = 10000 - - -# account_listing_limit is the default (and max) number of items returned -# for an account listing request - -#account_listing_limit = 10000 - - -# max_account_name_length is the max number of bytes in the utf8 encoding of -# an account name: Gluster FS Filename limit (XFS limit?), must be the same -# size as max_object_name_component_length above. - -max_account_name_length = 255 - - -# max_container_name_length is the max number of bytes in the utf8 encoding -# of a container name: Gluster FS Filename limit (XFS limit?), must be the same -# size as max_object_name_component_length above. - -max_container_name_length = 255 diff --git a/test/functional_auth/swiftkerbauth/test_swkrbath_active.py b/test/functional_auth/swiftkerbauth/test_swkrbath_active.py deleted file mode 100644 index 86c79ef..0000000 --- a/test/functional_auth/swiftkerbauth/test_swkrbath_active.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/python - -# Copyright (c) 2010-2014 OpenStack Foundation -# -# 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. - -import re -import unittest -from nose import SkipTest -import commands -import os -from test import get_config -from swift.common.bufferedhttp import http_connect_raw as http_connect - -config = get_config('func_test') - -class Utils: - @classmethod - def SwiftKerbAuthPrep(self, - user=config['username'],domain=config['domain_name'],\ - passwd=config['password']): - username = '%s@%s' % (user, domain) - return commands.getstatusoutput('kinit %s <<< %s' % (username, passwd)) - - @classmethod - def SwiftKerbAuthCleanAll(self): - return commands.getstatusoutput('kdestroy') - - -class TestSwKrbAthActive(unittest.TestCase): - def setUp(self): - #Perform kinit in active mode. - (status, output) = Utils.SwiftKerbAuthPrep() - self.assertEqual(status, 0, \ - 'swkrbauth prep failed with valid credentials'+output) - self.auth_host = config['auth_host'] - self.auth_port = int(config['auth_port']) - self.auth_prefix = config.get('auth_prefix', '/auth/') - self.auth_version = str(config.get('auth_version', '1')) - self.account_name = config['account'] - self.username = config['username'] - self.password = config['password'] - self.auth_scheme = config['auth_scheme'] - - #Prepare auth_url. e.g. http://client.rhelbox.com:8080/auth/v1.0 - if self.auth_version == "1": - self.auth_path = '%sv1.0' % (self.auth_prefix) - else: - self.auth_path = self.auth_prefix - self.auth_netloc = "%s:%d" % (self.auth_host, self.auth_port) - auth_url = self.auth_scheme + self.auth_netloc + self.auth_path - - #Obtain the X-Auth-Token from kerberos server to use it in furhter - #testing - self.auth_token = None - (status, output) = commands.getstatusoutput('curl -v -u : --negotiate\ - --location-trusted %s' % (auth_url)) - self.assertEqual(status, 0, 'Token negotiation failed:' +output) - match = re.search('X-Auth-Token: AUTH.*', output) - if match: - self.auth_token = match.group(0).split(':')[1].strip() - else: - self.fail('No X-Auth-Token found, failed') - - def tearDown(self): - Utils.SwiftKerbAuthCleanAll() - - - def _get_auth_token(self): - return {'X-Auth-Token' : self.auth_token} - - def testGetAccounts(self): - #TODO: The test case is to perform GET on the account mentioned via - #configuration file. This is a sample test case. The whole test - #suite can be enhanced further to have further complicated test cases. - path = '/v1/AUTH_%s' % (config['account']) - - headers = self._get_auth_token() - conn = http_connect(config['auth_host'], config['auth_port'], 'GET', - path, headers) - resp = conn.getresponse() - self.assertTrue(resp.status == 204) diff --git a/test/functional_auth/tempauth/conf/container-server.conf b/test/functional_auth/tempauth/conf/container-server.conf deleted file mode 100644 index 122d97e..0000000 --- a/test/functional_auth/tempauth/conf/container-server.conf +++ /dev/null @@ -1,35 +0,0 @@ -[DEFAULT] -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the container-server workers -# start, you can *consider* setting this value to "false" to reduce the -# per-request overhead it can incur. -# -# *** Keep false for Functional Tests *** -mount_check = false -bind_port = 6011 -# -# Override swift's default behaviour for fallocate. -disable_fallocate = true -# -# One or two workers should be sufficient for almost any installation of -# Gluster. -workers = 1 - -[pipeline:main] -pipeline = container-server - -[app:container-server] -use = egg:gluster_swift#container -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# After ensuring things are running in a stable manner, you can turn off -# normal request logging for the container server to unclutter the log -# files. Warnings and errors will still be logged. -log_requests = off - -#enable object versioning for functional test -allow_versions = on diff --git a/test/functional_auth/tempauth/conf/fs.conf b/test/functional_auth/tempauth/conf/fs.conf deleted file mode 100644 index b06a854..0000000 --- a/test/functional_auth/tempauth/conf/fs.conf +++ /dev/null @@ -1,19 +0,0 @@ -[DEFAULT] -# -# IP address of a node in the GlusterFS server cluster hosting the -# volumes to be served via Swift API. -mount_ip = localhost - -# Performance optimization parameter. When turned off, the filesystem will -# see a reduced number of stat calls, resulting in substantially faster -# response time for GET and HEAD container requests on containers with large -# numbers of objects, at the expense of an accurate count of combined bytes -# used by all objects in the container. For most installations "off" works -# fine. -# -# *** Keep on for Functional Tests *** -accurate_size_in_listing = on - -# *** Keep on for Functional Tests *** -container_update_object_count = on -account_update_container_count = on diff --git a/test/functional_auth/tempauth/conf/object-expirer.conf b/test/functional_auth/tempauth/conf/object-expirer.conf deleted file mode 100644 index b75963c..0000000 --- a/test/functional_auth/tempauth/conf/object-expirer.conf +++ /dev/null @@ -1,17 +0,0 @@ -[DEFAULT] - -[object-expirer] -# auto_create_account_prefix = . - -[pipeline:main] -pipeline = catch_errors cache proxy-server - -[app:proxy-server] -use = egg:swift#proxy - -[filter:cache] -use = egg:swift#memcache -memcache_servers = 127.0.0.1:11211 - -[filter:catch_errors] -use = egg:swift#catch_errors diff --git a/test/functional_auth/tempauth/conf/object-server.conf b/test/functional_auth/tempauth/conf/object-server.conf deleted file mode 100644 index 3cb9ead..0000000 --- a/test/functional_auth/tempauth/conf/object-server.conf +++ /dev/null @@ -1,48 +0,0 @@ -[DEFAULT] -devices = /mnt/gluster-object -# -# Once you are confident that your startup processes will always have your -# gluster volumes properly mounted *before* the object-server workers start, -# you can *consider* setting this value to "false" to reduce the per-request -# overhead it can incur. -# -# *** Keep false for Functional Tests *** -mount_check = false -bind_port = 6010 -# -# Maximum number of clients one worker can process simultaneously (it will -# actually accept N + 1). Setting this to one (1) will only handle one request -# at a time, without accepting another request concurrently. By increasing the -# number of workers to a much higher value, one can prevent slow file system -# operations for one request from starving other requests. -max_clients = 1024 -# -# If not doing the above, setting this value initially to match the number of -# CPUs is a good starting point for determining the right value. -workers = 1 -# Override swift's default behaviour for fallocate. -disable_fallocate = true - -[pipeline:main] -pipeline = object-server - -[app:object-server] -use = egg:gluster_swift#object -user = root -log_facility = LOG_LOCAL2 -log_level = WARN -# -# For performance, after ensuring things are running in a stable manner, you -# can turn off normal request logging for the object server to reduce the -# per-request overhead and unclutter the log files. Warnings and errors will -# still be logged. -log_requests = off -# -# Adjust this value to match the stripe width of the underlying storage array -# (not the stripe element size). This will provide a reasonable starting point -# for tuning this value. -disk_chunk_size = 65536 -# -# Adjust this value match whatever is set for the disk_chunk_size initially. -# This will provide a reasonable starting point for tuning this value. -network_chunk_size = 65556 diff --git a/test/functional_auth/tempauth/conf/proxy-server.conf b/test/functional_auth/tempauth/conf/proxy-server.conf index 830fadf..554ce61 100644 --- a/test/functional_auth/tempauth/conf/proxy-server.conf +++ b/test/functional_auth/tempauth/conf/proxy-server.conf @@ -5,7 +5,7 @@ user = root workers = 1 [pipeline:main] -pipeline = catch_errors healthcheck proxy-logging cache tempauth proxy-logging proxy-server +pipeline = catch_errors healthcheck proxy-logging cache tempurl tempauth proxy-logging proxy-server [app:proxy-server] use = egg:gluster_swift#proxy @@ -48,6 +48,10 @@ object_chunk_size = 65536 # amount of memory available on the system can accommodate increased values # for object_chunk_size. put_queue_depth = 10 +# The following parameter is used by object-expirer and needs to be same +# across all conf files! +auto_create_account_prefix = gs +expiring_objects_account_name = expiring [filter:catch_errors] use = egg:swift#catch_errors @@ -58,15 +62,18 @@ use = egg:swift#proxy_logging [filter:healthcheck] use = egg:swift#healthcheck +[filter:cache] +use = egg:swift#memcache +# Update this line to contain a comma separated list of memcache servers +# shared by all nodes running the proxy-server service. +memcache_servers = localhost:11211 + +[filter:tempurl] +use = egg:swift#tempurl + [filter:tempauth] use = egg:swift#tempauth user_admin_admin = admin .admin .reseller_admin user_test_tester = testing .admin user_test2_tester2 = testing2 .admin user_test_tester3 = testing3 - -[filter:cache] -use = egg:swift#memcache -# Update this line to contain a comma separated list of memcache servers -# shared by all nodes running the proxy-server service. -memcache_servers = localhost:11211 diff --git a/test/functional_auth/tempauth/conf/swift.conf b/test/functional_auth/tempauth/conf/swift.conf deleted file mode 100644 index ce9a4d0..0000000 --- a/test/functional_auth/tempauth/conf/swift.conf +++ /dev/null @@ -1,85 +0,0 @@ -[DEFAULT] - - -[swift-hash] -# random unique string that can never change (DO NOT LOSE) -swift_hash_path_suffix = gluster - - -# The swift-constraints section sets the basic constraints on data -# saved in the swift cluster. - -[swift-constraints] - -# max_file_size is the largest "normal" object that can be saved in -# the cluster. This is also the limit on the size of each segment of -# a "large" object when using the large object manifest support. -# This value is set in bytes. Setting it to lower than 1MiB will cause -# some tests to fail. -# Default is 1 TiB = 2**30*1024 -max_file_size = 1099511627776 - - -# max_meta_name_length is the max number of bytes in the utf8 encoding -# of the name portion of a metadata header. - -#max_meta_name_length = 128 - - -# max_meta_value_length is the max number of bytes in the utf8 encoding -# of a metadata value - -#max_meta_value_length = 256 - - -# max_meta_count is the max number of metadata keys that can be stored -# on a single account, container, or object - -#max_meta_count = 90 - - -# max_meta_overall_size is the max number of bytes in the utf8 encoding -# of the metadata (keys + values) - -#max_meta_overall_size = 4096 - - -# max_object_name_length is the max number of bytes in the utf8 encoding of an -# object name: Gluster FS can handle much longer file names, but the length -# between the slashes of the URL is handled below. Remember that most web -# clients can't handle anything greater than 2048, and those that do are -# rather clumsy. - -max_object_name_length = 2048 - -# max_object_name_component_length (GlusterFS) is the max number of bytes in -# the utf8 encoding of an object name component (the part between the -# slashes); this is a limit imposed by the underlying file system (for XFS it -# is 255 bytes). - -max_object_name_component_length = 255 - -# container_listing_limit is the default (and max) number of items -# returned for a container listing request - -#container_listing_limit = 10000 - - -# account_listing_limit is the default (and max) number of items returned -# for an account listing request - -#account_listing_limit = 10000 - - -# max_account_name_length is the max number of bytes in the utf8 encoding of -# an account name: Gluster FS Filename limit (XFS limit?), must be the same -# size as max_object_name_component_length above. - -max_account_name_length = 255 - - -# max_container_name_length is the max number of bytes in the utf8 encoding -# of a container name: Gluster FS Filename limit (XFS limit?), must be the same -# size as max_object_name_component_length above. - -max_container_name_length = 255 diff --git a/gluster/swift/common/middleware/gswauth/test_swauth/unit/__init__.py b/test/object_expirer_functional/__init__.py index e69de29..e69de29 100644 --- a/gluster/swift/common/middleware/gswauth/test_swauth/unit/__init__.py +++ b/test/object_expirer_functional/__init__.py diff --git a/test/object_expirer_functional/test_object_expirer.py b/test/object_expirer_functional/test_object_expirer.py new file mode 100644 index 0000000..aaec75e --- /dev/null +++ b/test/object_expirer_functional/test_object_expirer.py @@ -0,0 +1,332 @@ +# Copyright (c) 2014 Red Hat, Inc. +# +# 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. + +import os +import time + +from swift.common.manager import Manager +from swift.common.internal_client import InternalClient + +from test.functional.tests import Base, config, Utils +from test.functional.swift_test_client import Account, Connection, \ + ResponseError + + +class TestObjectExpirerEnv: + @classmethod + def setUp(cls): + cls.conn = Connection(config) + cls.conn.authenticate() + cls.account = Account(cls.conn, + config.get('account', + config['username'])) + cls.account.delete_containers() + cls.container = cls.account.container(Utils.create_name()) + if not cls.container.create(): + raise ResponseError(cls.conn.response) + cls.file_size = 8 + cls.root_dir = os.path.join('/mnt/gluster-object', + cls.account.conn.storage_url.split('/')[2].split('_')[1]) + cls.client = InternalClient('/etc/swift/object-expirer.conf', + 'Test Object Expirer', 1) + cls.expirer = Manager(['object-expirer']) + + +class TestObjectExpirer(Base): + env = TestObjectExpirerEnv + set_up = False + + def test_object_expiry_X_Delete_At_PUT(self): + obj = self.env.container.file(Utils.create_name()) + x_delete_at = str(int(time.time()) + 2) + obj.write_random(self.env.file_size, + hdrs={'X-Delete-At': x_delete_at}) + + # Object is not expired. Should still be accessible. + obj.read() + self.assert_status(200) + + # Ensure X-Delete-At is saved as object metadata. + self.assertEqual(x_delete_at, str(obj.info()['x_delete_at'])) + + # Wait for object to be expired. + time.sleep(3) + + # Object has expired. Should no longer be accessible. + self.assertRaises(ResponseError, obj.read) + self.assert_status(404) + + # Object should still be present on filesystem. + self.assertTrue(os.path.isfile(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # But, GET on container should list the expired object. + result = self.env.container.files() + self.assertTrue(obj.name in self.env.container.files()) + + # Check existence of corresponding tracker object in gsexpiring + # account. + enteredLoop = False + for c in self.env.client.iter_containers("gsexpiring"): + for o in self.env.client.iter_objects("gsexpiring", c['name']): + enteredLoop = True + l = o['name'].split('/') + self.assertTrue(l[0].endswith('AUTH_' + self.env.account.name)) + self.assertEqual(l[1], self.env.container.name) + self.assertEqual(l[2], obj.name) + if not enteredLoop: + self.fail("Tracker object not found.") + + # Run expirer daemon once. + self.env.expirer.once() + + # Ensure object is physically deleted from filesystem. + self.assertFalse(os.path.exists(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # Ensure tracker object is consumed. + try: + self.env.client.iter_containers("gsexpiring").next() + except StopIteration: + pass + else: + self.fail("Tracker object persists!") + + # GET on container should no longer list the object. + self.assertFalse(obj.name in self.env.container.files()) + + def test_object_expiry_X_Delete_After_PUT(self): + obj = self.env.container.file(Utils.create_name()) + obj.write_random(self.env.file_size, + hdrs={'X-Delete-After': 2}) + + # Object is not expired. Should still be accessible. + obj.read() + self.assert_status(200) + + # Ensure X-Delete-At is saved as object metadata. + self.assertTrue(str(obj.info()['x_delete_at'])) + + # Wait for object to be expired. + time.sleep(3) + + # Object has expired. Should no longer be accessible. + self.assertRaises(ResponseError, obj.read) + self.assert_status(404) + + # Object should still be present on filesystem. + self.assertTrue(os.path.isfile(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # But, GET on container should list the expired object. + result = self.env.container.files() + self.assertTrue(obj.name in self.env.container.files()) + + # Check existence of corresponding tracker object in gsexpiring + # account. + enteredLoop = False + for c in self.env.client.iter_containers("gsexpiring"): + for o in self.env.client.iter_objects("gsexpiring", c['name']): + enteredLoop = True + l = o['name'].split('/') + self.assertTrue(l[0].endswith('AUTH_' + self.env.account.name)) + self.assertEqual(l[1], self.env.container.name) + self.assertEqual(l[2], obj.name) + if not enteredLoop: + self.fail("Tracker object not found.") + + # Run expirer daemon once. + self.env.expirer.once() + + # Ensure object is physically deleted from filesystem. + self.assertFalse(os.path.exists(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # Ensure tracker object is consumed. + try: + self.env.client.iter_containers("gsexpiring").next() + except StopIteration: + pass + else: + self.fail("Tracker object persists!") + + # GET on container should no longer list the object. + self.assertFalse(obj.name in self.env.container.files()) + + + def test_object_expiry_X_Delete_At_POST(self): + + # Create normal object + obj = self.env.container.file(Utils.create_name()) + obj.write_random(self.env.file_size) + obj.read() + self.assert_status(200) + + # Send POST on that object and set it to be expired. + x_delete_at = str(int(time.time()) + 2) + obj.sync_metadata(metadata={'X-Delete-At': x_delete_at}, + cfg={'x_delete_at': x_delete_at}) + + # Ensure X-Delete-At is saved as object metadata. + self.assertEqual(x_delete_at, str(obj.info()['x_delete_at'])) + + # Object is not expired. Should still be accessible. + obj.read() + self.assert_status(200) + + # Wait for object to be expired. + time.sleep(3) + + # Object has expired. Should no longer be accessible. + self.assertRaises(ResponseError, obj.read) + self.assert_status(404) + + # Object should still be present on filesystem. + self.assertTrue(os.path.isfile(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # But, GET on container should list the expired object. + result = self.env.container.files() + self.assertTrue(obj.name in self.env.container.files()) + + # Check existence of corresponding tracker object in gsexpiring + # account. + + enteredLoop = False + for c in self.env.client.iter_containers("gsexpiring"): + for o in self.env.client.iter_objects("gsexpiring", c['name']): + enteredLoop = True + l = o['name'].split('/') + self.assertTrue(l[0].endswith('AUTH_' + self.env.account.name)) + self.assertEqual(l[1], self.env.container.name) + self.assertEqual(l[2], obj.name) + if not enteredLoop: + self.fail("Tracker object not found.") + + # Run expirer daemon once. + self.env.expirer.once() + time.sleep(3) + + # Ensure object is physically deleted from filesystem. + self.assertFalse(os.path.exists(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # Ensure tracker object is consumed. + try: + self.env.client.iter_containers("gsexpiring").next() + except StopIteration: + pass + else: + self.fail("Tracker object persists!") + + # GET on container should no longer list the object. + self.assertFalse(obj.name in self.env.container.files()) + + + def test_object_expiry_X_Delete_After_POST(self): + + # Create normal object + obj = self.env.container.file(Utils.create_name()) + obj.write_random(self.env.file_size) + obj.read() + self.assert_status(200) + + # Send POST on that object and set it to be expired. + obj.sync_metadata(metadata={'X-Delete-After': 2}, + cfg={'x_delete_after': 2}) + + # Ensure X-Delete-At is saved as object metadata. + self.assertTrue(str(obj.info()['x_delete_at'])) + + # Object is not expired. Should still be accessible. + obj.read() + self.assert_status(200) + + # Wait for object to be expired. + time.sleep(3) + + # Object has expired. Should no longer be accessible. + self.assertRaises(ResponseError, obj.read) + self.assert_status(404) + + # Object should still be present on filesystem. + self.assertTrue(os.path.isfile(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # But, GET on container should list the expired object. + result = self.env.container.files() + self.assertTrue(obj.name in self.env.container.files()) + + # Check existence of corresponding tracker object in gsexpiring + # account. + + enteredLoop = False + for c in self.env.client.iter_containers("gsexpiring"): + for o in self.env.client.iter_objects("gsexpiring", c['name']): + enteredLoop = True + l = o['name'].split('/') + self.assertTrue(l[0].endswith('AUTH_' + self.env.account.name)) + self.assertEqual(l[1], self.env.container.name) + self.assertEqual(l[2], obj.name) + if not enteredLoop: + self.fail("Tracker object not found.") + + # Run expirer daemon once. + self.env.expirer.once() + time.sleep(3) + + # Ensure object is physically deleted from filesystem. + self.assertFalse(os.path.exists(os.path.join(self.env.root_dir, + self.env.container.name, + obj.name))) + + # Ensure tracker object is consumed. + try: + self.env.client.iter_containers("gsexpiring").next() + except StopIteration: + pass + else: + self.fail("Tracker object persists!") + + # GET on container should no longer list the object. + self.assertFalse(obj.name in self.env.container.files()) + + + def test_object_expiry_err(self): + obj = self.env.container.file(Utils.create_name()) + + # X-Delete-At is invalid or is in the past + for i in (-2, 'abc', str(int(time.time()) - 2), 5.8): + self.assertRaises(ResponseError, + obj.write_random, + self.env.file_size, + hdrs={'X-Delete-At': i}) + self.assert_status(400) + + # X-Delete-After is invalid. + for i in (-2, 'abc', 3.7): + self.assertRaises(ResponseError, + obj.write_random, + self.env.file_size, + hdrs={'X-Delete-After': i}) + self.assert_status(400) + diff --git a/test/unit/__init__.py b/test/unit/__init__.py index 847479a..a1bfef8 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -33,6 +33,7 @@ from hashlib import md5 from eventlet import sleep, Timeout import logging.handlers from httplib import HTTPException +from numbers import Number class FakeRing(object): @@ -248,6 +249,7 @@ class FakeLogger(logging.Logger): if 'facility' in kwargs: self.facility = kwargs['facility'] self.statsd_client = None + self.thread_locals = None def _clear(self): self.log_dict = defaultdict(list) @@ -465,8 +467,11 @@ def fake_http_connect(*code_iter, **kwargs): self.body = body self.headers = headers or {} self.timestamp = timestamp - if kwargs.get('slow') and isinstance(kwargs['slow'], list): - kwargs['slow'][0] -= 1 + if 'slow' in kwargs and isinstance(kwargs['slow'], list): + try: + self._next_sleep = kwargs['slow'].pop(0) + except IndexError: + self._next_sleep = None def getresponse(self): if kwargs.get('raise_exc'): @@ -482,6 +487,8 @@ def fake_http_connect(*code_iter, **kwargs): return FakeConn(507) if self.expect_status == -4: return FakeConn(201) + if self.expect_status == 412: + return FakeConn(412) return FakeConn(100) def getheaders(self): @@ -510,31 +517,39 @@ def fake_http_connect(*code_iter, **kwargs): headers['x-container-timestamp'] = '1' except StopIteration: pass - if self.am_slow(): + am_slow, value = self.get_slow() + if am_slow: headers['content-length'] = '4' headers.update(self.headers) return headers.items() - def am_slow(self): - if kwargs.get('slow') and isinstance(kwargs['slow'], list): - return kwargs['slow'][0] >= 0 - return bool(kwargs.get('slow')) + def get_slow(self): + if 'slow' in kwargs and isinstance(kwargs['slow'], list): + if self._next_sleep is not None: + return True, self._next_sleep + else: + return False, 0.01 + if kwargs.get('slow') and isinstance(kwargs['slow'], Number): + return True, kwargs['slow'] + return bool(kwargs.get('slow')), 0.1 def read(self, amt=None): - if self.am_slow(): + am_slow, value = self.get_slow() + if am_slow: if self.sent < 4: self.sent += 1 - sleep(0.1) + sleep(value) return ' ' rv = self.body[:amt] self.body = self.body[amt:] return rv def send(self, amt=None): - if self.am_slow(): + am_slow, value = self.get_slow() + if am_slow: if self.received < 4: self.received += 1 - sleep(0.1) + sleep(value) def getheader(self, name, default=None): return dict(self.getheaders()).get(name.lower(), default) @@ -584,4 +599,6 @@ def fake_http_connect(*code_iter, **kwargs): return FakeConn(status, etag, body=body, timestamp=timestamp, expect_status=expect_status, headers=headers) + connect.code_iter = code_iter + return connect diff --git a/test/unit/common/middleware/swiftkerbauth/__init__.py b/test/unit/common/middleware/swiftkerbauth/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/test/unit/common/middleware/swiftkerbauth/__init__.py +++ /dev/null diff --git a/test/unit/common/middleware/swiftkerbauth/test_kerbauth.py b/test/unit/common/middleware/swiftkerbauth/test_kerbauth.py deleted file mode 100644 index 537b8d3..0000000 --- a/test/unit/common/middleware/swiftkerbauth/test_kerbauth.py +++ /dev/null @@ -1,478 +0,0 @@ -# Copyright (c) 2013 Red Hat, Inc. -# -# 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. - -import os -import errno -import unittest -from time import time -from mock import patch, Mock -from test.unit import FakeMemcache -from swift.common.swob import Request, Response -from gluster.swift.common.middleware.swiftkerbauth import kerbauth as auth - -EXT_AUTHENTICATION_URL = "127.0.0.1" -REDIRECT_STATUS = 303 # HTTPSeeOther - - -def my_filter_factory(global_conf, **local_conf): - if 'ext_authentication_url' not in global_conf: - global_conf['ext_authentication_url'] = EXT_AUTHENTICATION_URL - conf = global_conf.copy() - conf.update(local_conf) - - def auth_filter(app): - return auth.KerbAuth(app, conf) - return auth_filter - -# Monkey patching filter_factory to always pass ext_authentication_url -# as a parameter. Absence of ext_authentication_url raises a RuntimeError - - -def patch_filter_factory(): - auth.filter_factory = my_filter_factory - - -def unpatch_filter_factory(): - reload(auth) - - -class FakeApp(object): - - def __init__(self, status_headers_body_iter=None, acl=None, sync_key=None): - self.calls = 0 - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter([('404 Not Found', {}, '')]) - self.acl = acl - self.sync_key = sync_key - - def __call__(self, env, start_response): - self.calls += 1 - self.request = Request.blank('', environ=env) - if self.acl: - self.request.acl = self.acl - if self.sync_key: - self.request.environ['swift_sync_key'] = self.sync_key - if 'swift.authorize' in env: - resp = env['swift.authorize'](self.request) - if resp: - return resp(env, start_response) - status, headers, body = self.status_headers_body_iter.next() - return Response(status=status, headers=headers, - body=body)(env, start_response) - - -class TestKerbAuth(unittest.TestCase): - - # Patch auth.filter_factory() - patch_filter_factory() - - def setUp(self): - self.test_auth = \ - auth.filter_factory({'auth_method': 'active'})(FakeApp()) - self.test_auth_passive = \ - auth.filter_factory({'auth_method': 'passive'})(FakeApp()) - - def _make_request(self, path, **kwargs): - req = Request.blank(path, **kwargs) - req.environ['swift.cache'] = FakeMemcache() - return req - - def test_no_ext_authentication_url(self): - app = FakeApp() - try: - # Use original auth.filter_factory and NOT monkey patched version - unpatch_filter_factory() - auth.filter_factory({})(app) - except RuntimeError as e: - # Restore monkey patched version - patch_filter_factory() - self.assertTrue(e.args[0].startswith("Missing filter parameter " - "ext_authentication_url")) - - def test_reseller_prefix_init(self): - app = FakeApp() - ath = auth.filter_factory({})(app) - self.assertEquals(ath.reseller_prefix, 'AUTH_') - ath = auth.filter_factory({'reseller_prefix': 'TEST'})(app) - self.assertEquals(ath.reseller_prefix, 'TEST_') - ath = auth.filter_factory({'reseller_prefix': 'TEST_'})(app) - self.assertEquals(ath.reseller_prefix, 'TEST_') - - def test_auth_prefix_init(self): - app = FakeApp() - ath = auth.filter_factory({})(app) - self.assertEquals(ath.auth_prefix, '/auth/') - ath = auth.filter_factory({'auth_prefix': ''})(app) - self.assertEquals(ath.auth_prefix, '/auth/') - ath = auth.filter_factory({'auth_prefix': '/'})(app) - self.assertEquals(ath.auth_prefix, '/auth/') - ath = auth.filter_factory({'auth_prefix': '/test/'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - ath = auth.filter_factory({'auth_prefix': '/test'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - ath = auth.filter_factory({'auth_prefix': 'test/'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - ath = auth.filter_factory({'auth_prefix': 'test'})(app) - self.assertEquals(ath.auth_prefix, '/test/') - - def test_top_level_redirect(self): - req = self._make_request('/') - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - self.assertEquals(req.environ['swift.authorize'], - self.test_auth.denied_response) - - def test_passive_top_level_deny(self): - req = self._make_request('/') - resp = req.get_response(self.test_auth_passive) - self.assertEquals(resp.status_int, 401) - self.assertEquals(req.environ['swift.authorize'], - self.test_auth_passive.denied_response) - - def test_passive_deny_invalid_token(self): - req = self._make_request('/v1/AUTH_account', - headers={'X-Auth-Token': 'AUTH_t'}) - resp = req.get_response(self.test_auth_passive) - self.assertEquals(resp.status_int, 401) - - def test_override_asked_for_and_allowed(self): - self.test_auth = \ - auth.filter_factory({'allow_overrides': 'true'})(FakeApp()) - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertTrue('swift.authorize' not in req.environ) - - def test_override_default_allowed(self): - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - self.assertTrue('swift.authorize' not in req.environ) - - def test_options_call(self): - req = self._make_request('/v1/AUTH_cfa/c/o', - environ={'REQUEST_METHOD': 'OPTIONS'}) - resp = self.test_auth.authorize(req) - self.assertEquals(resp, None) - - def test_auth_deny_non_reseller_prefix_no_override(self): - fake_authorize = lambda x: Response(status='500 Fake') - req = self._make_request('/v1/BLAH_account', - headers={'X-Auth-Token': 'BLAH_t'}, - environ={'swift.authorize': fake_authorize} - ) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 500) - self.assertEquals(req.environ['swift.authorize'], fake_authorize) - - def test_authorize_acl_group_access(self): - req = self._make_request('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = self._make_request('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act' - self.assertEquals(self.test_auth.authorize(req), None) - req = self._make_request('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - req.acl = 'act:usr' - self.assertEquals(self.test_auth.authorize(req), None) - req = self._make_request('/v1/AUTH_cfa') - req.remote_user = 'act:usr,act' - - def test_deny_cross_reseller(self): - # Tests that cross-reseller is denied, even if ACLs/group names match - req = self._make_request('/v1/OTHER_cfa') - req.remote_user = 'act:usr,act,AUTH_cfa' - req.acl = 'act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - - def test_authorize_acl_referer_after_user_groups(self): - req = self._make_request('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr' - req.acl = '.r:*,act:usr' - self.assertEquals(self.test_auth.authorize(req), None) - - def test_detect_reseller_request(self): - req = self._make_request('/v1/AUTH_admin', - headers={'X-Auth-Token': 'AUTH_t'}) - cache_key = 'AUTH_/token/AUTH_t' - cache_entry = (time() + 3600, '.reseller_admin') - req.environ['swift.cache'].set(cache_key, cache_entry) - req.get_response(self.test_auth) - self.assertTrue(req.environ.get('reseller_request', False)) - - def test_regular_is_not_owner(self): - orig_authorize = self.test_auth.authorize - owner_values = [] - - def mitm_authorize(req): - rv = orig_authorize(req) - owner_values.append(req.environ.get('swift_owner', False)) - return rv - - self.test_auth.authorize = mitm_authorize - - req = self._make_request( - '/v1/AUTH_cfa/c', - headers={'X-Auth-Token': 'AUTH_t'}) - req.remote_user = 'act:usr' - self.test_auth.authorize(req) - self.assertEquals(owner_values, [False]) - - def test_no_memcache(self): - env = {'swift.cache': None} - try: - self.test_auth.get_groups(env, None) - except Exception as e: - self.assertTrue(e.args[0].startswith("Memcache required")) - - def test_handle_request(self): - req = self._make_request('/auth/v1.0') - resp = self.test_auth.handle_request(req) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - - def test_handle_request_bad_request(self): - req = self._make_request('////') - resp = self.test_auth.handle_request(req) - self.assertEquals(resp.status_int, 404) - - def test_handle_request_no_handler(self): - req = self._make_request('/blah/blah/blah/blah') - resp = self.test_auth.handle_request(req) - self.assertEquals(resp.status_int, 400) - - def test_handle_get_token_bad_request(self): - req = self._make_request('/blah/blah') - resp = self.test_auth.handle_get_token(req) - self.assertEquals(resp.status_int, 400) - req = self._make_request('/////') - resp = self.test_auth.handle_get_token(req) - self.assertEquals(resp.status_int, 404) - - def test_passive_handle_get_token_no_user_or_key(self): - #No user and key - req = self._make_request('/auth/v1.0') - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - #User given but no key - req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'test:user'}) - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, 401) - - def test_passive_handle_get_token_account_in_req_path(self): - req = self._make_request('/v1/test/auth', - headers={'X-Auth-User': 'test:user', - 'X-Auth-Key': 'password'}) - _mock_run_kinit = Mock(return_value=0) - _mock_get_groups = Mock(return_value="user,auth_test") - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username', - _mock_get_groups): - resp = self.test_auth_passive.handle_get_token(req) - _mock_run_kinit.assert_called_once_with('user', 'password') - self.assertEquals(_mock_get_groups.call_count, 2) - self.assertEquals(resp.status_int, 200) - self.assertTrue(resp.headers['X-Auth-Token'] is not None) - self.assertTrue(resp.headers['X-Storage-Token'] is not None) - self.assertTrue(resp.headers['X-Storage-Url'] is not None) - - def test_passive_handle_get_token_user_invalid_or_no__account(self): - #X-Auth-User not in acc:user format - req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'user'}) - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, 401) - req = self._make_request('/v1/test/auth', - headers={'X-Auth-User': 'user'}) - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, 401) - # Account name mismatch - req = self._make_request('/v1/test/auth', - headers={'X-Auth-User': 'wrongacc:user'}) - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, 401) - - def test_passive_handle_get_token_no_kinit(self): - req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'test:user', - 'X-Auth-Key': 'password'}) - _mock_run_kinit = Mock(side_effect=OSError(errno.ENOENT, - os.strerror(errno.ENOENT))) - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, 500) - self.assertTrue("kinit command not found" in resp.body) - _mock_run_kinit.assert_called_once_with('user', 'password') - - def test_passive_handle_get_token_kinit_fail(self): - req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'test:user', - 'X-Auth-Key': 'password'}) - _mock_run_kinit = Mock(return_value=1) - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, 401) - _mock_run_kinit.assert_called_once_with('user', 'password') - - def test_passive_handle_get_token_kinit_success_token_not_present(self): - req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'test:user', - 'X-Auth-Key': 'password'}) - _mock_run_kinit = Mock(return_value=0) - _mock_get_groups = Mock(return_value="user,auth_test") - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username', - _mock_get_groups): - resp = self.test_auth_passive.handle_get_token(req) - _mock_run_kinit.assert_called_once_with('user', 'password') - self.assertEquals(_mock_get_groups.call_count, 2) - self.assertEquals(resp.status_int, 200) - self.assertTrue(resp.headers['X-Auth-Token'] is not None) - self.assertTrue(resp.headers['X-Storage-Token'] is not None) - self.assertTrue(resp.headers['X-Storage-Url'] is not None) - - def test_passive_handle_get_token_kinit_realm_and_memcache(self): - req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'test:user', - 'X-Auth-Key': 'password'}) - req.environ['swift.cache'] = None - _auth_passive = \ - auth.filter_factory({'auth_method': 'passive', - 'realm_name': 'EXAMPLE.COM'})(FakeApp()) - _mock_run_kinit = Mock(return_value=0) - _mock_get_groups = Mock(return_value="user,auth_test") - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username', - _mock_get_groups): - try: - _auth_passive.handle_get_token(req) - except Exception as e: - self.assertTrue(e.args[0].startswith("Memcache " - "required")) - else: - self.fail("Expected Exception - Memcache required") - _mock_run_kinit.assert_called_once_with('user@EXAMPLE.COM', 'password') - _mock_get_groups.assert_called_once_with('user') - - def test_passive_handle_get_token_user_in_any__account(self): - req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'test:user', - 'X-Auth-Key': 'password'}) - _mock_run_kinit = Mock(return_value=0) - _mock_get_groups = Mock(return_value="user,auth_blah") - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): - with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username', - _mock_get_groups): - resp = self.test_auth_passive.handle_get_token(req) - self.assertEquals(resp.status_int, 401) - _mock_run_kinit.assert_called_once_with('user', 'password') - _mock_get_groups.assert_called_once_with('user') - - def test_handle(self): - req = self._make_request('/auth/v1.0') - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - - def test_authorize_invalid_req(self): - req = self._make_request('/') - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 404) - - def test_authorize_set_swift_owner(self): - req = self._make_request('/v1/AUTH_test/c1/o1') - req.remote_user = 'test,auth_reseller_admin' - resp = self.test_auth.authorize(req) - self.assertEquals(req.environ['swift_owner'], True) - self.assertTrue(resp is None) - req = self._make_request('/v1/AUTH_test/c1/o1') - req.remote_user = 'test,auth_test' - resp = self.test_auth.authorize(req) - self.assertEquals(req.environ['swift_owner'], True) - self.assertTrue(resp is None) - - def test_authorize_swift_sync_key(self): - req = self._make_request( - '/v1/AUTH_cfa/c/o', - environ={'swift_sync_key': 'secret'}, - headers={'x-container-sync-key': 'secret', - 'x-timestamp': '123.456'}) - resp = self.test_auth.authorize(req) - self.assertTrue(resp is None) - - def test_authorize_acl_referrer_access(self): - req = self._make_request('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = self._make_request('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:*,.rlistings' - self.assertEquals(self.test_auth.authorize(req), None) - req = self._make_request('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:*' # No listings allowed - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = self._make_request('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.acl = '.r:.example.com,.rlistings' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, 403) - req = self._make_request('/v1/AUTH_cfa/c') - req.remote_user = 'act:usr,act' - req.referer = 'http://www.example.com/index.html' - req.acl = '.r:.example.com,.rlistings' - self.assertEquals(self.test_auth.authorize(req), None) - req = self._make_request('/v1/AUTH_cfa/c') - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - req = self._make_request('/v1/AUTH_cfa/c') - req.acl = '.r:*,.rlistings' - self.assertEquals(self.test_auth.authorize(req), None) - req = self._make_request('/v1/AUTH_cfa/c') - req.acl = '.r:*' # No listings allowed - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - req = self._make_request('/v1/AUTH_cfa/c') - req.acl = '.r:.example.com,.rlistings' - resp = self.test_auth.authorize(req) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - req = self._make_request('/v1/AUTH_cfa/c') - req.referer = 'http://www.example.com/index.html' - req.acl = '.r:.example.com,.rlistings' - self.assertEquals(self.test_auth.authorize(req), None) - - def test_handle_x_storage_token(self): - req = self._make_request( - '/auth/v1.0', - headers={'x-storage-token': 'blahblah', }) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - - def test_invalid_token(self): - req = self._make_request('/k1/test') - req.environ['HTTP_X_AUTH_TOKEN'] = 'AUTH_blahblahblah' - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, REDIRECT_STATUS) - -if __name__ == '__main__': - unittest.main() diff --git a/test/unit/common/middleware/swiftkerbauth/test_kerbauth_utils.py b/test/unit/common/middleware/swiftkerbauth/test_kerbauth_utils.py deleted file mode 100644 index 2a4e90b..0000000 --- a/test/unit/common/middleware/swiftkerbauth/test_kerbauth_utils.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (c) 2013 Red Hat, Inc. -# -# 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. - -import unittest -import re -from time import time -from test.unit import FakeMemcache -from gluster.swift.common.middleware.swiftkerbauth import kerbauth_utils as ku - - -class TestKerbUtils(unittest.TestCase): - - def test_get_remote_user(self): - env = {'REMOTE_USER': "auth_admin@EXAMPLE.COM"} - result = ku.get_remote_user(env) - self.assertEqual(result, "auth_admin") - - def test_get_remote_user_err(self): - env = {'REMOTE_USER': "auth_admin"} - try: - ku.get_remote_user(env) - except RuntimeError as err: - self.assertTrue(err.args[0].startswith("Malformed REMOTE_USER")) - else: - self.fail("Expected RuntimeError") - - def test_get_auth_data(self): - mc = FakeMemcache() - expiry = time() + 100 - ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin") - (token, expires, groups) = ku.get_auth_data(mc, "root") - self.assertEqual(("AUTH_tk", expiry, "root,admin"), - (token, expires, groups)) - - def test_get_auth_data_err(self): - mc = FakeMemcache() - (token, expires, groups) = ku.get_auth_data(mc, "root") - self.assertEqual((token, expires, groups), (None, None, None)) - - expiry = time() - 1 - ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin") - (token, expires, groups) = ku.get_auth_data(mc, "root") - self.assertEqual((token, expires, groups), (None, None, None)) - - def test_set_auth_data(self): - mc = FakeMemcache() - expiry = time() + 100 - ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin") - - def test_generate_token(self): - token = ku.generate_token() - matches = re.match('AUTH_tk[a-f0-9]{32}', token) - self.assertTrue(matches is not None) - - def test_get_groups_from_username(self): - groups = ku.get_groups_from_username("root") - self.assertTrue("root" in groups) - - def test_get_groups_from_username_err(self): - try: - ku.get_groups_from_username("Zroot") - except RuntimeError as err: - self.assertTrue(err.args[0].startswith("Failure running id -G")) - else: - self.fail("Expected RuntimeError") diff --git a/test/unit/common/test_constraints.py b/test/unit/common/test_constraints.py index 6c78d75..e8ddd69 100644 --- a/test/unit/common/test_constraints.py +++ b/test/unit/common/test_constraints.py @@ -57,10 +57,10 @@ class TestConstraints(unittest.TestCase): cnt.set_object_name_component_length() self.assertEqual(len, cnt.get_object_name_component_length()) - with patch('swift.common.constraints.constraints_conf_int', - mock_constraints_conf_int): - cnt.set_object_name_component_length() - self.assertEqual(cnt.get_object_name_component_length(), 1000) + with patch('swift.common.constraints.constraints_conf_int', + mock_constraints_conf_int): + cnt.set_object_name_component_length() + self.assertEqual(cnt.get_object_name_component_length(), 1000) def test_validate_obj_name_component(self): max_obj_len = cnt.get_object_name_component_length() @@ -75,65 +75,6 @@ class TestConstraints(unittest.TestCase): self.assertTrue(cnt.validate_obj_name_component('..')) self.assertTrue(cnt.validate_obj_name_component('')) - def test_validate_headers(self): - req = Mock() - req.headers = [] - self.assertEqual(cnt.validate_headers(req), '') - req.headers = ['x-some-header'] - self.assertEqual(cnt.validate_headers(req), '') - #TODO: Although we now support x-delete-at and x-delete-after, - #retained this test case as we may add some other header to - #unsupported list in future - raise SkipTest - req.headers = ['x-delete-at', 'x-some-header'] - self.assertNotEqual(cnt.validate_headers(req), '') - req.headers = ['x-delete-after', 'x-some-header'] - self.assertNotEqual(cnt.validate_headers(req), '') - req.headers = ['x-delete-at', 'x-delete-after', 'x-some-header'] - self.assertNotEqual(cnt.validate_headers(req), '') - - def test_validate_headers_ignoring_config_set(self): - with patch('gluster.swift.common.constraints.' - 'Glusterfs._ignore_unsupported_headers', True): - req = Mock() - req.headers = [] - self.assertEqual(cnt.validate_headers(req), '') - req.headers = ['x-some-header'] - self.assertEqual(cnt.validate_headers(req), '') - #TODO: Although we now support x-delete-at and x-delete-after, - #retained this test case as we may add some other header to - #unsupported list in future - raise SkipTest - req.headers = ['x-delete-at', 'x-some-header'] - self.assertEqual(cnt.validate_headers(req), '') - req.headers = ['x-delete-after', 'x-some-header'] - self.assertEqual(cnt.validate_headers(req), '') - req.headers = ['x-delete-at', 'x-delete-after', 'x-some-header'] - self.assertEqual(cnt.validate_headers(req), '') - - def test_gluster_check_metadata(self): - mock_check_metadata = Mock() - with patch('gluster.swift.common.constraints.__check_metadata', - mock_check_metadata): - req = Mock() - req.headers = [] - cnt.gluster_check_metadata(req, 'object') - self.assertTrue(1, mock_check_metadata.call_count) - cnt.gluster_check_metadata(req, 'object', POST=False) - self.assertTrue(1, mock_check_metadata.call_count) - req.headers = ['x-some-header'] - self.assertEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None) - #TODO: Although we now support x-delete-at and x-delete-after, - #retained this test case as we may add some other header to - #unsupported list in future - raise SkipTest - req.headers = ['x-delete-at', 'x-some-header'] - self.assertNotEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None) - req.headers = ['x-delete-after', 'x-some-header'] - self.assertNotEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None) - req.headers = ['x-delete-at', 'x-delete-after', 'x-some-header'] - self.assertNotEqual(cnt.gluster_check_metadata(req, 'object', POST=False), None) - def test_gluster_check_object_creation(self): with patch('gluster.swift.common.constraints.__check_object_creation', mock_check_object_creation): @@ -147,9 +88,3 @@ class TestConstraints(unittest.TestCase): req = Mock() req.headers = [] self.assertTrue(cnt.gluster_check_object_creation(req, 'dir/.')) - #TODO: Although we now support x-delete-at and x-delete-after, - #retained this test case as we may add some other header to - #unsupported list in future - raise SkipTest - req.headers = ['x-delete-at'] - self.assertTrue(cnt.gluster_check_object_creation(req, 'dir/z')) diff --git a/test/unit/common/test_diskdir.py b/test/unit/common/test_diskdir.py index f32c3ad..ebc554a 100644 --- a/test/unit/common/test_diskdir.py +++ b/test/unit/common/test_diskdir.py @@ -408,7 +408,7 @@ class TestContainerBroker(unittest.TestCase): self.assertEqual(os.path.basename(broker.db_file), 'db_file.db') broker.initialize(self.initial_ts) self.assertTrue(os.path.isdir(self.container)) - self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP]) + self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0]) self.assertFalse(broker.is_deleted()) def test_creation_existing(self): @@ -419,7 +419,7 @@ class TestContainerBroker(unittest.TestCase): self.assertEqual(os.path.basename(broker.db_file), 'db_file.db') broker.initialize(self.initial_ts) self.assertTrue(os.path.isdir(self.container)) - self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP]) + self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0]) self.assertFalse(broker.is_deleted()) def test_creation_existing_bad_metadata(self): @@ -432,7 +432,7 @@ class TestContainerBroker(unittest.TestCase): self.assertEqual(os.path.basename(broker.db_file), 'db_file.db') broker.initialize(self.initial_ts) self.assertTrue(os.path.isdir(self.container)) - self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP]) + self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0]) self.assertFalse(broker.is_deleted()) def test_empty(self): @@ -954,7 +954,7 @@ class TestContainerBroker(unittest.TestCase): self.assertEqual(os.path.basename(broker.db_file), 'db_file.db') broker.initialize(self.initial_ts) self.assertTrue(os.path.isdir(self.container)) - self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP]) + self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0]) self.assertFalse(broker.is_deleted()) broker.delete_db(normalize_timestamp(time())) self.assertTrue(broker.is_deleted()) @@ -1005,7 +1005,7 @@ class TestAccountBroker(unittest.TestCase): self.assertEqual(os.path.basename(broker.db_file), 'db_file.db') broker.initialize(self.initial_ts) self.assertTrue(os.path.isdir(self.drive_fullpath)) - self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP]) + self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0]) self.assertFalse(broker.is_deleted()) def test_creation_bad_metadata(self): @@ -1016,7 +1016,7 @@ class TestAccountBroker(unittest.TestCase): self.assertEqual(os.path.basename(broker.db_file), 'db_file.db') broker.initialize(self.initial_ts) self.assertTrue(os.path.isdir(self.drive_fullpath)) - self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP]) + self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0]) self.assertFalse(broker.is_deleted()) def test_empty(self): @@ -1219,7 +1219,7 @@ class TestAccountBroker(unittest.TestCase): self.assertEqual(os.path.basename(broker.db_file), 'db_file.db') broker.initialize(self.initial_ts) self.assertTrue(os.path.isdir(self.drive_fullpath)) - self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP]) + self.assertEquals(self.initial_ts, broker.metadata[utils.X_TIMESTAMP][0]) self.assertFalse(broker.is_deleted()) broker.delete_db(normalize_timestamp(time())) # Deleting the "db" should be a NOOP diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py index dd03bd8..55bd0cf 100644 --- a/test/unit/common/test_utils.py +++ b/test/unit/common/test_utils.py @@ -25,9 +25,10 @@ import hashlib import tarfile import shutil from collections import defaultdict -from mock import patch +from mock import patch, Mock from gluster.swift.common import utils, Glusterfs -from gluster.swift.common.exceptions import GlusterFileSystemOSError +from gluster.swift.common.exceptions import GlusterFileSystemOSError,\ + GlusterFileSystemIOError from swift.common.exceptions import DiskFileNoSpace # @@ -712,6 +713,18 @@ class TestUtils(unittest.TestCase): ret = utils.validate_object(md) assert ret + def test_validate_object_with_stat(self): + md = {utils.X_TIMESTAMP: 'na', + utils.X_CONTENT_TYPE: 'na', + utils.X_ETAG: 'bad', + utils.X_CONTENT_LENGTH: '12345', + utils.X_TYPE: utils.OBJECT, + utils.X_OBJECT_TYPE: 'na'} + fake_stat = Mock(st_size=12346) + self.assertFalse(utils.validate_object(md, fake_stat)) + fake_stat = Mock(st_size=12345) + self.assertTrue(utils.validate_object(md, fake_stat)) + class TestUtilsDirObjects(unittest.TestCase): @@ -794,7 +807,8 @@ class TestUtilsDirObjects(unittest.TestCase): def _mock_rm(path): print "_mock_rm-metadata_enoent(%s)" % path shutil.rmtree(path) - raise OSError(errno.ENOENT, os.strerror(errno.ENOENT)) + raise GlusterFileSystemIOError(errno.ENOENT, + os.strerror(errno.ENOENT)) # Remove the files for f in self.files: @@ -805,8 +819,8 @@ class TestUtilsDirObjects(unittest.TestCase): try: try: self.assertTrue(utils.rmobjdir(self.rootdir)) - except OSError: - self.fail("Unexpected OSError") + except IOError: + self.fail("Unexpected IOError") else: pass finally: diff --git a/test/unit/obj/test_diskfile.py b/test/unit/obj/test_diskfile.py index f8c26db..1fe0904 100644 --- a/test/unit/obj/test_diskfile.py +++ b/test/unit/obj/test_diskfile.py @@ -223,7 +223,7 @@ class TestDiskFile(unittest.TestCase): ini_md = { 'X-Type': 'Object', 'X-Object-Type': 'file', - 'Content-Length': 5, + 'Content-Length': 4, 'ETag': 'etag', 'X-Timestamp': 'ts', 'Content-Type': 'application/loctet-stream'} @@ -283,9 +283,8 @@ class TestDiskFile(unittest.TestCase): with gdf.open(): assert gdf._is_dir assert gdf._data_file == the_dir - assert gdf._metadata == exp_md - def _create_and_get_diskfile(self, dev, par, acc, con, obj): + def _create_and_get_diskfile(self, dev, par, acc, con, obj, fsize=256): # FIXME: assumes account === volume the_path = os.path.join(self.td, dev, con) the_file = os.path.join(the_path, obj) @@ -293,7 +292,7 @@ class TestDiskFile(unittest.TestCase): base_dir = os.path.dirname(the_file) os.makedirs(base_dir) with open(the_file, "wb") as fd: - fd.write("y" * 256) + fd.write("y" * fsize) gdf = self._get_diskfile(dev, par, acc, con, obj) assert gdf._obj == base_obj assert not gdf._is_dir @@ -353,6 +352,26 @@ class TestDiskFile(unittest.TestCase): assert len(chunks) == 1, repr(chunks) assert called[0] == 1, called + def test_reader_larger_file(self): + closed = [False] + fd = [-1] + + def mock_close(*args, **kwargs): + closed[0] = True + os.close(fd[0]) + + with mock.patch("gluster.swift.obj.diskfile.do_close", mock_close): + gdf = self._create_and_get_diskfile("vol0", "p57", "ufo47", "bar", "z", fsize=1024*1024*2) + with gdf.open(): + assert gdf._fd is not None + assert gdf._data_file == os.path.join(self.td, "vol0", "bar", "z") + reader = gdf.reader() + assert reader._fd is not None + fd[0] = reader._fd + chunks = [ck for ck in reader] + assert reader._fd is None + assert closed[0] + def test_reader_dir_object(self): called = [False] @@ -926,3 +945,31 @@ class TestDiskFile(unittest.TestCase): dw.write("123") os.unlink(saved_tmppath) assert not os.path.exists(saved_tmppath) + + def test_unlink_not_called_after_rename(self): + the_obj_path = os.path.join("b", "a") + the_file = os.path.join(the_obj_path, "z") + gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", the_file) + + body = '1234\n' + etag = md5(body).hexdigest() + metadata = { + 'X-Timestamp': '1234', + 'Content-Type': 'file', + 'ETag': etag, + 'Content-Length': '5', + } + + _mock_do_unlink = Mock() # Shouldn't be called + with patch("gluster.swift.obj.diskfile.do_unlink", _mock_do_unlink): + with gdf.create() as dw: + assert dw._tmppath is not None + tmppath = dw._tmppath + dw.write(body) + dw.put(metadata) + # do_unlink is not called if dw._tmppath is set to None + assert dw._tmppath is None + self.assertFalse(_mock_do_unlink.called) + + assert os.path.exists(gdf._data_file) # Real file exists + assert not os.path.exists(tmppath) # Temp file does not exist diff --git a/test/unit/obj/test_expirer.py b/test/unit/obj/test_expirer.py index 4329eef..236775e 100644 --- a/test/unit/obj/test_expirer.py +++ b/test/unit/obj/test_expirer.py @@ -46,7 +46,7 @@ class TestObjectExpirer(TestCase): self.old_loadapp = internal_client.loadapp self.old_sleep = internal_client.sleep - internal_client.loadapp = lambda x: None + internal_client.loadapp = lambda *a, **kw: None internal_client.sleep = not_sleep def teardown(self): @@ -618,7 +618,7 @@ class TestObjectExpirer(TestCase): start_response('204 No Content', [('Content-Length', '0')]) return [] - internal_client.loadapp = lambda x: fake_app + internal_client.loadapp = lambda *a, **kw: fake_app x = expirer.ObjectExpirer({}) ts = '1234' @@ -635,7 +635,7 @@ class TestObjectExpirer(TestCase): start_response('204 No Content', [('Content-Length', '0')]) return [] - internal_client.loadapp = lambda x: fake_app + internal_client.loadapp = lambda *a, **kw: fake_app x = expirer.ObjectExpirer({}) ts = '1234' @@ -649,7 +649,7 @@ class TestObjectExpirer(TestCase): start_response('404 Not Found', [('Content-Length', '0')]) return [] - internal_client.loadapp = lambda x: fake_app + internal_client.loadapp = lambda *a, **kw: fake_app x = expirer.ObjectExpirer({}) x.delete_actual_object('/path/to/object', '1234') @@ -661,7 +661,7 @@ class TestObjectExpirer(TestCase): [('Content-Length', '0')]) return [] - internal_client.loadapp = lambda x: fake_app + internal_client.loadapp = lambda *a, **kw: fake_app x = expirer.ObjectExpirer({}) x.delete_actual_object('/path/to/object', '1234') @@ -674,7 +674,7 @@ class TestObjectExpirer(TestCase): [('Content-Length', '0')]) return [] - internal_client.loadapp = lambda x: fake_app + internal_client.loadapp = lambda *a, **kw: fake_app x = expirer.ObjectExpirer({}) exc = None @@ -692,7 +692,7 @@ class TestObjectExpirer(TestCase): x = expirer.ObjectExpirer({}) x.swift.make_request = mock.MagicMock() x.delete_actual_object(name, timestamp) - x.swift.make_request.assert_called_once() + self.assertTrue(x.swift.make_request.called) self.assertEqual(x.swift.make_request.call_args[0][1], '/v1/' + urllib.quote(name)) diff --git a/test/unit/proxy/controllers/test_obj.py b/test/unit/proxy/controllers/test_obj.py index aada616..4942691 100755 --- a/test/unit/proxy/controllers/test_obj.py +++ b/test/unit/proxy/controllers/test_obj.py @@ -22,7 +22,7 @@ import mock import swift from swift.proxy import server as proxy_server from swift.common.swob import HTTPException -from test.unit import FakeRing, FakeMemcache, fake_http_connect +from test.unit import FakeRing, FakeMemcache, fake_http_connect, debug_logger @contextmanager @@ -90,26 +90,96 @@ class TestObjControllerWriteAffinity(unittest.TestCase): class TestObjController(unittest.TestCase): + def setUp(self): + logger = debug_logger('proxy-server') + logger.thread_locals = ('txn1', '127.0.0.2') + self.app = proxy_server.Application( + None, FakeMemcache(), account_ring=FakeRing(), + container_ring=FakeRing(), object_ring=FakeRing(), + logger=logger) + self.controller = proxy_server.ObjectController(self.app, + 'a', 'c', 'o') + self.controller.container_info = mock.MagicMock(return_value={ + 'partition': 1, + 'nodes': [ + {'ip': '127.0.0.1', 'port': '1', 'device': 'sda'}, + {'ip': '127.0.0.1', 'port': '2', 'device': 'sda'}, + {'ip': '127.0.0.1', 'port': '3', 'device': 'sda'}, + ], + 'write_acl': None, + 'read_acl': None, + 'sync_key': None, + 'versions': None}) + + def test_PUT_simple(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + req.headers['content-length'] = '0' + with set_http_connect(201, 201, 201): + resp = self.controller.PUT(req) + self.assertEquals(resp.status_int, 201) + + def test_PUT_if_none_match(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + req.headers['if-none-match'] = '*' + req.headers['content-length'] = '0' + with set_http_connect(201, 201, 201): + resp = self.controller.PUT(req) + self.assertEquals(resp.status_int, 201) + + def test_PUT_if_none_match_denied(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + req.headers['if-none-match'] = '*' + req.headers['content-length'] = '0' + with set_http_connect(201, (412, 412), 201): + resp = self.controller.PUT(req) + self.assertEquals(resp.status_int, 412) + + def test_PUT_if_none_match_not_star(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + req.headers['if-none-match'] = 'somethingelse' + req.headers['content-length'] = '0' + with set_http_connect(201, 201, 201): + resp = self.controller.PUT(req) + self.assertEquals(resp.status_int, 400) + + def test_GET_simple(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + with set_http_connect(200): + resp = self.controller.GET(req) + self.assertEquals(resp.status_int, 200) + + def test_DELETE_simple(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + with set_http_connect(204, 204, 204): + resp = self.controller.DELETE(req) + self.assertEquals(resp.status_int, 204) + + def test_POST_simple(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + with set_http_connect(200, 200, 200, 201, 201, 201): + resp = self.controller.POST(req) + self.assertEquals(resp.status_int, 202) + + def test_COPY_simple(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + with set_http_connect(200, 200, 200, 201, 201, 201): + resp = self.controller.POST(req) + self.assertEquals(resp.status_int, 202) + + def test_HEAD_simple(self): + req = swift.common.swob.Request.blank('/v1/a/c/o') + with set_http_connect(200, 200, 200, 201, 201, 201): + resp = self.controller.POST(req) + self.assertEquals(resp.status_int, 202) def test_PUT_log_info(self): # mock out enough to get to the area of the code we want to test with mock.patch('swift.proxy.controllers.obj.check_object_creation', mock.MagicMock(return_value=None)): - app = mock.MagicMock() - app.container_ring.get_nodes.return_value = (1, [2]) - app.object_ring.get_nodes.return_value = (1, [2]) - controller = proxy_server.ObjectController(app, 'a', 'c', 'o') - controller.container_info = mock.MagicMock(return_value={ - 'partition': 1, - 'nodes': [{}], - 'write_acl': None, - 'sync_key': None, - 'versions': None}) - # and now test that we add the header to log_info req = swift.common.swob.Request.blank('/v1/a/c/o') req.headers['x-copy-from'] = 'somewhere' try: - controller.PUT(req) + self.controller.PUT(req) except HTTPException: pass self.assertEquals( @@ -119,7 +189,7 @@ class TestObjController(unittest.TestCase): req.method = 'POST' req.headers['x-copy-from'] = 'elsewhere' try: - controller.PUT(req) + self.controller.PUT(req) except HTTPException: pass self.assertEquals(req.environ.get('swift.log_info'), None) diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index 0cb2278..1a59016 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -30,6 +30,7 @@ from urllib import quote from hashlib import md5 from tempfile import mkdtemp import weakref +import re import mock from eventlet import sleep, spawn, wsgi, listen @@ -60,7 +61,8 @@ from swift.proxy.controllers.base import get_container_memcache_key, \ get_account_memcache_key, cors_validation import swift.proxy.controllers from swift.common.request_helpers import get_sys_meta_prefix -from swift.common.swob import Request, Response, HTTPUnauthorized +from swift.common.swob import Request, Response, HTTPUnauthorized, \ + HTTPException # mocks logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) @@ -245,6 +247,7 @@ def set_http_connect(*args, **kwargs): swift.proxy.controllers.obj.http_connect = new_connect swift.proxy.controllers.account.http_connect = new_connect swift.proxy.controllers.container.http_connect = new_connect + return new_connect # tests @@ -692,10 +695,10 @@ class TestObjectController(unittest.TestCase): def setUp(self): self.app = proxy_server.Application(None, FakeMemcache(), + logger=debug_logger('proxy-ut'), account_ring=FakeRing(), container_ring=FakeRing(), object_ring=FakeRing()) - monkey_patch_mimetools() def tearDown(self): self.app.account_ring.set_replicas(3) @@ -802,6 +805,7 @@ class TestObjectController(unittest.TestCase): self.app.update_request(req) self.app.memcache.store = {} res = controller.PUT(req) + self.assertEqual(test_errors, []) self.assertTrue(res.status.startswith('201 ')) def test_PUT_respects_write_affinity(self): @@ -1609,7 +1613,7 @@ class TestObjectController(unittest.TestCase): dev['ip'] = '127.0.0.1' dev['port'] = 1 - class SlowBody(): + class SlowBody(object): def __init__(self): self.sent = 0 @@ -1658,7 +1662,7 @@ class TestObjectController(unittest.TestCase): dev['ip'] = '127.0.0.1' dev['port'] = 1 - class SlowBody(): + class SlowBody(object): def __init__(self): self.sent = 0 @@ -1693,7 +1697,7 @@ class TestObjectController(unittest.TestCase): dev['port'] = 1 req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'}) self.app.update_request(req) - set_http_connect(200, 200, 200, slow=True) + set_http_connect(200, 200, 200, slow=0.1) req.sent_size = 0 resp = req.get_response(self.app) got_exc = False @@ -1703,7 +1707,7 @@ class TestObjectController(unittest.TestCase): got_exc = True self.assert_(not got_exc) self.app.recoverable_node_timeout = 0.1 - set_http_connect(200, 200, 200, slow=True) + set_http_connect(200, 200, 200, slow=1.0) resp = req.get_response(self.app) got_exc = False try: @@ -1718,16 +1722,17 @@ class TestObjectController(unittest.TestCase): self.app.update_request(req) self.app.recoverable_node_timeout = 0.1 - set_http_connect(200, 200, 200, slow=[3]) + set_http_connect(200, 200, 200, slow=[1.0, 1.0, 1.0]) resp = req.get_response(self.app) got_exc = False try: - resp.body + self.assertEquals('', resp.body) except ChunkReadTimeout: got_exc = True self.assert_(got_exc) - set_http_connect(200, 200, 200, body='lalala', slow=[2]) + set_http_connect(200, 200, 200, body='lalala', + slow=[1.0, 1.0]) resp = req.get_response(self.app) got_exc = False try: @@ -1736,8 +1741,8 @@ class TestObjectController(unittest.TestCase): got_exc = True self.assert_(not got_exc) - set_http_connect(200, 200, 200, body='lalala', slow=[2], - etags=['a', 'a', 'a']) + set_http_connect(200, 200, 200, body='lalala', + slow=[1.0, 1.0], etags=['a', 'a', 'a']) resp = req.get_response(self.app) got_exc = False try: @@ -1746,8 +1751,8 @@ class TestObjectController(unittest.TestCase): got_exc = True self.assert_(not got_exc) - set_http_connect(200, 200, 200, body='lalala', slow=[2], - etags=['a', 'b', 'a']) + set_http_connect(200, 200, 200, body='lalala', + slow=[1.0, 1.0], etags=['a', 'b', 'a']) resp = req.get_response(self.app) got_exc = False try: @@ -1757,8 +1762,8 @@ class TestObjectController(unittest.TestCase): self.assert_(not got_exc) req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'}) - set_http_connect(200, 200, 200, body='lalala', slow=[2], - etags=['a', 'b', 'b']) + set_http_connect(200, 200, 200, body='lalala', + slow=[1.0, 1.0], etags=['a', 'b', 'b']) resp = req.get_response(self.app) got_exc = False try: @@ -1787,17 +1792,17 @@ class TestObjectController(unittest.TestCase): 'Content-Type': 'text/plain'}, body=' ') self.app.update_request(req) - set_http_connect(200, 200, 201, 201, 201, slow=True) + set_http_connect(200, 200, 201, 201, 201, slow=0.1) resp = req.get_response(self.app) self.assertEquals(resp.status_int, 201) self.app.node_timeout = 0.1 - set_http_connect(201, 201, 201, slow=True) req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'Content-Length': '4', 'Content-Type': 'text/plain'}, body=' ') self.app.update_request(req) + set_http_connect(201, 201, 201, slow=1.0) resp = req.get_response(self.app) self.assertEquals(resp.status_int, 503) @@ -2227,307 +2232,322 @@ class TestObjectController(unittest.TestCase): resp = controller.PUT(req) self.assertEquals(resp.status_int, 400) - def test_copy_from(self): + @contextmanager + def controller_context(self, req, *args, **kwargs): + _v, account, container, obj = utils.split_path(req.path, 4, 4, True) + controller = proxy_server.ObjectController(self.app, account, + container, obj) + self.app.update_request(req) + self.app.memcache.store = {} with save_globals(): - controller = proxy_server.ObjectController(self.app, 'account', - 'container', 'object') - # initial source object PUT - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0'}) - self.app.update_request(req) - set_http_connect(200, 200, 201, 201, 201) - # acct cont obj obj obj + new_connect = set_http_connect(*args, **kwargs) + yield controller + unused_status_list = [] + while True: + try: + unused_status_list.append(new_connect.code_iter.next()) + except StopIteration: + break + if unused_status_list: + raise self.fail('UN-USED STATUS CODES: %r' % + unused_status_list) + + def test_basic_put_with_x_copy_from(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': 'c/o'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o') - # basic copy - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': 'c/o'}) - self.app.update_request(req) - set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_basic_put_with_x_copy_from_across_container(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': 'c2/o'}) + status_list = (200, 200, 200, 200, 200, 200, 201, 201, 201) + # acct cont conc objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c2/o') - # non-zero content length - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '5', - 'X-Copy-From': 'c/o'}) - self.app.update_request(req) - set_http_connect(200, 200, 200, 200, 200, 200, 200) - # acct cont acct cont objc objc objc - self.app.memcache.store = {} + def test_copy_non_zero_content_length(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '5', + 'X-Copy-From': 'c/o'}) + status_list = (200, 200) + # acct cont + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 400) + self.assertEquals(resp.status_int, 400) - # extra source path parsing - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': 'c/o/o2'}) - req.account = 'a' - set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_copy_with_slashes_in_x_copy_from(self): + # extra source path parsing + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': 'c/o/o2'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') - # space in soure path - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': 'c/o%20o2'}) - req.account = 'a' - set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_copy_with_spaces_in_x_copy_from(self): + # space in soure path + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': 'c/o%20o2'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2') - # repeat tests with leading / - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c/o'}) - self.app.update_request(req) - set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_copy_with_leading_slash_in_x_copy_from(self): + # repeat tests with leading / + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c/o'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o') - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c/o/o2'}) - req.account = 'a' - set_http_connect(200, 200, 200, 200, 200, 200, 200, 201, 201, 201) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_copy_with_leading_slash_and_slashes_in_x_copy_from(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c/o/o2'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') - # negative tests + def test_copy_with_no_object_in_x_copy_from(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c'}) + status_list = (200, 200) + # acct cont + with self.controller_context(req, *status_list) as controller: + try: + controller.PUT(req) + except HTTPException as resp: + self.assertEquals(resp.status_int // 100, 4) # client error + else: + raise self.fail('Invalid X-Copy-From did not raise ' + 'client error') - # invalid x-copy-from path - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c'}) - self.app.update_request(req) - self.app.memcache.store = {} + def test_copy_server_error_reading_source(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c/o'}) + status_list = (200, 200, 503, 503, 503) + # acct cont objc objc objc + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int // 100, 4) # client error + self.assertEquals(resp.status_int, 503) - # server error - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c/o'}) - self.app.update_request(req) - set_http_connect(200, 200, 503, 503, 503) - # acct cont objc objc objc - self.app.memcache.store = {} + def test_copy_not_found_reading_source(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c/o'}) + # not found + status_list = (200, 200, 404, 404, 404) + # acct cont objc objc objc + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 503) + self.assertEquals(resp.status_int, 404) - # not found - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c/o'}) - self.app.update_request(req) - set_http_connect(200, 200, 404, 404, 404) - # acct cont objc objc objc - self.app.memcache.store = {} + def test_copy_with_some_missing_sources(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c/o'}) + status_list = (200, 200, 404, 404, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 404) + self.assertEquals(resp.status_int, 201) - # some missing containers - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c/o'}) - self.app.update_request(req) - set_http_connect(200, 200, 404, 404, 200, 201, 201, 201) - # acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_copy_with_object_metadata(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c/o', + 'X-Object-Meta-Ours': 'okay'}) + # test object metadata + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers.get('x-object-meta-test'), 'testing') + self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') + self.assertEquals(resp.headers.get('x-delete-at'), '9876543210') - # test object meta data - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c/o', - 'X-Object-Meta-Ours': 'okay'}) - self.app.update_request(req) - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201) - # acct cont objc objc objc obj obj obj - self.app.memcache.store = {} - resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers.get('x-object-meta-test'), - 'testing') - self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') - self.assertEquals(resp.headers.get('x-delete-at'), '9876543210') + def test_copy_source_larger_than_max_file_size(self): + req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, + headers={'Content-Length': '0', + 'X-Copy-From': '/c/o'}) - # copy-from object is too large to fit in target object - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0', - 'X-Copy-From': '/c/o'}) - self.app.update_request(req) + # copy-from object is too large to fit in target object + class LargeResponseBody(object): - class LargeResponseBody(object): + def __len__(self): + return MAX_FILE_SIZE + 1 - def __len__(self): - return MAX_FILE_SIZE + 1 + def __getitem__(self, key): + return '' - def __getitem__(self, key): - return '' + copy_from_obj_body = LargeResponseBody() + status_list = (200, 200, 200, 200, 200) + # acct cont objc objc objc + kwargs = dict(body=copy_from_obj_body) + with self.controller_context(req, *status_list, + **kwargs) as controller: + self.app.update_request(req) - copy_from_obj_body = LargeResponseBody() - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, - body=copy_from_obj_body) self.app.memcache.store = {} resp = controller.PUT(req) self.assertEquals(resp.status_int, 413) - def test_COPY(self): - with save_globals(): - controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o') - req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Content-Length': '0'}) - req.account = 'a' - set_http_connect(200, 200, 201, 201, 201) - # acct cont obj obj obj - resp = controller.PUT(req) - self.assertEquals(resp.status_int, 201) - - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': 'c/o'}) - req.account = 'a' - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_basic_COPY(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': 'c/o2'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o') - req = Request.blank('/v1/a/c/o/o2', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': 'c/o'}) - req.account = 'a' - controller.object_name = 'o/o2' - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_COPY_across_containers(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': 'c2/o'}) + status_list = (200, 200, 200, 200, 200, 200, 201, 201, 201) + # acct cont c2 objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') - - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': '/c/o'}) - req.account = 'a' - controller.object_name = 'o' - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o') + + def test_COPY_source_with_slashes_in_name(self): + req = Request.blank('/v1/a/c/o/o2', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': 'c/o'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') - req = Request.blank('/v1/a/c/o/o2', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': '/c/o'}) - req.account = 'a' - controller.object_name = 'o/o2' - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, 200, 200) - # acct cont acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_COPY_destination_leading_slash(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o') + + def test_COPY_source_with_slashes_destination_leading_slash(self): + req = Request.blank('/v1/a/c/o/o2', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: + resp = controller.COPY(req) + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': 'c_o'}) - req.account = 'a' - controller.object_name = 'o' - set_http_connect(200, 200) - # acct cont - self.app.memcache.store = {} + def test_COPY_no_object_in_destination(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': 'c_o'}) + status_list = [] # no requests needed + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 412) + self.assertEquals(resp.status_int, 412) - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': '/c/o'}) - req.account = 'a' - controller.object_name = 'o' - set_http_connect(200, 200, 503, 503, 503) - # acct cont objc objc objc - self.app.memcache.store = {} + def test_COPY_server_error_reading_source(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o'}) + status_list = (200, 200, 503, 503, 503) + # acct cont objc objc objc + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 503) + self.assertEquals(resp.status_int, 503) - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': '/c/o'}) - req.account = 'a' - controller.object_name = 'o' - set_http_connect(200, 200, 404, 404, 404) - # acct cont objc objc objc - self.app.memcache.store = {} + def test_COPY_not_found_reading_source(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o'}) + status_list = (200, 200, 404, 404, 404) + # acct cont objc objc objc + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 404) + self.assertEquals(resp.status_int, 404) - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': '/c/o'}) - req.account = 'a' - controller.object_name = 'o' - set_http_connect(200, 200, 404, 404, 200, 201, 201, 201) - # acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_COPY_with_some_missing_sources(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o'}) + status_list = (200, 200, 404, 404, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.status_int, 201) - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': '/c/o', - 'X-Object-Meta-Ours': 'okay'}) - req.account = 'a' - controller.object_name = 'o' - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201) - # acct cont objc objc objc obj obj obj - self.app.memcache.store = {} + def test_COPY_with_metadata(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o', + 'X-Object-Meta-Ours': 'okay'}) + status_list = (200, 200, 200, 200, 200, 201, 201, 201) + # acct cont objc objc objc obj obj obj + with self.controller_context(req, *status_list) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 201) - self.assertEquals(resp.headers.get('x-object-meta-test'), - 'testing') - self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') - self.assertEquals(resp.headers.get('x-delete-at'), '9876543210') + self.assertEquals(resp.status_int, 201) + self.assertEquals(resp.headers.get('x-object-meta-test'), + 'testing') + self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') + self.assertEquals(resp.headers.get('x-delete-at'), '9876543210') - req = Request.blank('/v1/a/c/o', - environ={'REQUEST_METHOD': 'COPY'}, - headers={'Destination': '/c/o'}) - self.app.update_request(req) + def test_COPY_source_larger_than_max_file_size(self): + req = Request.blank('/v1/a/c/o', + environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o'}) - class LargeResponseBody(object): + class LargeResponseBody(object): - def __len__(self): - return MAX_FILE_SIZE + 1 + def __len__(self): + return MAX_FILE_SIZE + 1 - def __getitem__(self, key): - return '' + def __getitem__(self, key): + return '' - copy_from_obj_body = LargeResponseBody() - set_http_connect(200, 200, 200, 200, 200, 201, 201, 201, - body=copy_from_obj_body) - self.app.memcache.store = {} + copy_from_obj_body = LargeResponseBody() + status_list = (200, 200, 200, 200, 200) + # acct cont objc objc objc + kwargs = dict(body=copy_from_obj_body) + with self.controller_context(req, *status_list, + **kwargs) as controller: resp = controller.COPY(req) - self.assertEquals(resp.status_int, 413) + self.assertEquals(resp.status_int, 413) def test_COPY_newest(self): with save_globals(): @@ -2574,7 +2594,7 @@ class TestObjectController(unittest.TestCase): def test_chunked_put(self): - class ChunkedFile(): + class ChunkedFile(object): def __init__(self, bytes): self.bytes = bytes @@ -3824,9 +3844,7 @@ class TestObjectController(unittest.TestCase): req.content_length = 0 resp = controller.OPTIONS(req) self.assertEquals(200, resp.status_int) - self.assertEquals( - 'https://bar.baz', - resp.headers['access-control-allow-origin']) + self.assertEquals('*', resp.headers['access-control-allow-origin']) for verb in 'OPTIONS COPY GET POST PUT DELETE HEAD'.split(): self.assertTrue( verb in resp.headers['access-control-allow-methods']) @@ -3842,10 +3860,11 @@ class TestObjectController(unittest.TestCase): def stubContainerInfo(*args): return { 'cors': { - 'allow_origin': 'http://foo.bar' + 'allow_origin': 'http://not.foo.bar' } } controller.container_info = stubContainerInfo + controller.app.strict_cors_mode = False def objectGET(controller, req): return Response(headers={ @@ -3876,6 +3895,50 @@ class TestObjectController(unittest.TestCase): 'x-trans-id', 'x-object-meta-color']) self.assertEquals(expected_exposed, exposed) + controller.app.strict_cors_mode = True + req = Request.blank( + '/v1/a/c/o.jpg', + {'REQUEST_METHOD': 'GET'}, + headers={'Origin': 'http://foo.bar'}) + + resp = cors_validation(objectGET)(controller, req) + + self.assertEquals(200, resp.status_int) + self.assertTrue('access-control-allow-origin' not in resp.headers) + + def test_CORS_valid_with_obj_headers(self): + with save_globals(): + controller = proxy_server.ObjectController(self.app, 'a', 'c', 'o') + + def stubContainerInfo(*args): + return { + 'cors': { + 'allow_origin': 'http://foo.bar' + } + } + controller.container_info = stubContainerInfo + + def objectGET(controller, req): + return Response(headers={ + 'X-Object-Meta-Color': 'red', + 'X-Super-Secret': 'hush', + 'Access-Control-Allow-Origin': 'http://obj.origin', + 'Access-Control-Expose-Headers': 'x-trans-id' + }) + + req = Request.blank( + '/v1/a/c/o.jpg', + {'REQUEST_METHOD': 'GET'}, + headers={'Origin': 'http://foo.bar'}) + + resp = cors_validation(objectGET)(controller, req) + + self.assertEquals(200, resp.status_int) + self.assertEquals('http://obj.origin', + resp.headers['access-control-allow-origin']) + self.assertEquals('x-trans-id', + resp.headers['access-control-expose-headers']) + def _gather_x_container_headers(self, controller_call, req, *connect_args, **kwargs): header_list = kwargs.pop('header_list', ['X-Container-Device', @@ -4092,7 +4155,8 @@ class TestContainerController(unittest.TestCase): self.app = proxy_server.Application(None, FakeMemcache(), account_ring=FakeRing(), container_ring=FakeRing(), - object_ring=FakeRing()) + object_ring=FakeRing(), + logger=FakeLogger()) def test_transfer_headers(self): src_headers = {'x-remove-versions-location': 'x', @@ -4843,9 +4907,7 @@ class TestContainerController(unittest.TestCase): req.content_length = 0 resp = controller.OPTIONS(req) self.assertEquals(200, resp.status_int) - self.assertEquals( - 'https://bar.baz', - resp.headers['access-control-allow-origin']) + self.assertEquals('*', resp.headers['access-control-allow-origin']) for verb in 'OPTIONS GET POST PUT DELETE HEAD'.split(): self.assertTrue( verb in resp.headers['access-control-allow-methods']) @@ -5016,11 +5078,60 @@ class TestContainerController(unittest.TestCase): 'X-Account-Device': 'sdc'} ]) + def test_PUT_backed_x_timestamp_header(self): + timestamps = [] + + def capture_timestamps(*args, **kwargs): + headers = kwargs['headers'] + timestamps.append(headers.get('X-Timestamp')) + + req = Request.blank('/v1/a/c', method='PUT', headers={'': ''}) + with save_globals(): + new_connect = set_http_connect(200, # account existance check + 201, 201, 201, + give_connect=capture_timestamps) + resp = self.app.handle_request(req) + + # sanity + self.assertRaises(StopIteration, new_connect.code_iter.next) + self.assertEqual(2, resp.status_int // 100) + + timestamps.pop(0) # account existance check + self.assertEqual(3, len(timestamps)) + for timestamp in timestamps: + self.assertEqual(timestamp, timestamps[0]) + self.assert_(re.match('[0-9]{10}\.[0-9]{5}', timestamp)) + + def test_DELETE_backed_x_timestamp_header(self): + timestamps = [] + + def capture_timestamps(*args, **kwargs): + headers = kwargs['headers'] + timestamps.append(headers.get('X-Timestamp')) + + req = Request.blank('/v1/a/c', method='DELETE', headers={'': ''}) + self.app.update_request(req) + with save_globals(): + new_connect = set_http_connect(200, # account existance check + 201, 201, 201, + give_connect=capture_timestamps) + resp = self.app.handle_request(req) + + # sanity + self.assertRaises(StopIteration, new_connect.code_iter.next) + self.assertEqual(2, resp.status_int // 100) + + timestamps.pop(0) # account existance check + self.assertEqual(3, len(timestamps)) + for timestamp in timestamps: + self.assertEqual(timestamp, timestamps[0]) + self.assert_(re.match('[0-9]{10}\.[0-9]{5}', timestamp)) + def test_node_read_timeout_retry_to_container(self): with save_globals(): req = Request.blank('/v1/a/c', environ={'REQUEST_METHOD': 'GET'}) self.app.node_timeout = 0.1 - set_http_connect(200, 200, 200, body='abcdef', slow=[2]) + set_http_connect(200, 200, 200, body='abcdef', slow=[1.0, 1.0]) resp = req.get_response(self.app) got_exc = False try: @@ -5547,6 +5658,143 @@ class TestAccountControllerFakeGetResponse(unittest.TestCase): resp = req.get_response(self.app) self.assertEqual(400, resp.status_int) + def test_account_acl_header_access(self): + acl = { + 'admin': ['AUTH_alice'], + 'read-write': ['AUTH_bob'], + 'read-only': ['AUTH_carol'], + } + prefix = get_sys_meta_prefix('account') + privileged_headers = {(prefix + 'core-access-control'): format_acl( + version=2, acl_dict=acl)} + + app = proxy_server.Application( + None, FakeMemcache(), account_ring=FakeRing(), + container_ring=FakeRing(), object_ring=FakeRing()) + + with save_globals(): + # Mock account server will provide privileged information (ACLs) + set_http_connect(200, 200, 200, headers=privileged_headers) + req = Request.blank('/v1/a', environ={'REQUEST_METHOD': 'GET'}) + resp = app.handle_request(req) + + # Not a swift_owner -- ACLs should NOT be in response + header = 'X-Account-Access-Control' + self.assert_(header not in resp.headers, '%r was in %r' % ( + header, resp.headers)) + + # Same setup -- mock acct server will provide ACLs + set_http_connect(200, 200, 200, headers=privileged_headers) + req = Request.blank('/v1/a', environ={'REQUEST_METHOD': 'GET', + 'swift_owner': True}) + resp = app.handle_request(req) + + # For a swift_owner, the ACLs *should* be in response + self.assert_(header in resp.headers, '%r not in %r' % ( + header, resp.headers)) + + def test_account_acls_through_delegation(self): + + # Define a way to grab the requests sent out from the AccountController + # to the Account Server, and a way to inject responses we'd like the + # Account Server to return. + resps_to_send = [] + + @contextmanager + def patch_account_controller_method(verb): + old_method = getattr(proxy_server.AccountController, verb) + new_method = lambda self, req, *_, **__: resps_to_send.pop(0) + try: + setattr(proxy_server.AccountController, verb, new_method) + yield + finally: + setattr(proxy_server.AccountController, verb, old_method) + + def make_test_request(http_method, swift_owner=True): + env = { + 'REQUEST_METHOD': http_method, + 'swift_owner': swift_owner, + } + acl = { + 'admin': ['foo'], + 'read-write': ['bar'], + 'read-only': ['bas'], + } + headers = {} if http_method in ('GET', 'HEAD') else { + 'x-account-access-control': format_acl(version=2, acl_dict=acl) + } + + return Request.blank('/v1/a', environ=env, headers=headers) + + # Our AccountController will invoke methods to communicate with the + # Account Server, and they will return responses like these: + def make_canned_response(http_method): + acl = { + 'admin': ['foo'], + 'read-write': ['bar'], + 'read-only': ['bas'], + } + headers = {'x-account-sysmeta-core-access-control': format_acl( + version=2, acl_dict=acl)} + canned_resp = Response(headers=headers) + canned_resp.environ = { + 'PATH_INFO': '/acct', + 'REQUEST_METHOD': http_method, + } + resps_to_send.append(canned_resp) + + app = proxy_server.Application( + None, FakeMemcache(), account_ring=FakeRing(), + container_ring=FakeRing(), object_ring=FakeRing()) + app.allow_account_management = True + + ext_header = 'x-account-access-control' + with patch_account_controller_method('GETorHEAD_base'): + # GET/HEAD requests should remap sysmeta headers from acct server + for verb in ('GET', 'HEAD'): + make_canned_response(verb) + req = make_test_request(verb) + resp = app.handle_request(req) + h = parse_acl(version=2, data=resp.headers.get(ext_header)) + self.assertEqual(h['admin'], ['foo']) + self.assertEqual(h['read-write'], ['bar']) + self.assertEqual(h['read-only'], ['bas']) + + # swift_owner = False: GET/HEAD shouldn't return sensitive info + make_canned_response(verb) + req = make_test_request(verb, swift_owner=False) + resp = app.handle_request(req) + h = resp.headers + self.assertEqual(None, h.get(ext_header)) + + # swift_owner unset: GET/HEAD shouldn't return sensitive info + make_canned_response(verb) + req = make_test_request(verb, swift_owner=False) + del req.environ['swift_owner'] + resp = app.handle_request(req) + h = resp.headers + self.assertEqual(None, h.get(ext_header)) + + # Verify that PUT/POST requests remap sysmeta headers from acct server + with patch_account_controller_method('make_requests'): + make_canned_response('PUT') + req = make_test_request('PUT') + resp = app.handle_request(req) + + h = parse_acl(version=2, data=resp.headers.get(ext_header)) + self.assertEqual(h['admin'], ['foo']) + self.assertEqual(h['read-write'], ['bar']) + self.assertEqual(h['read-only'], ['bas']) + + make_canned_response('POST') + req = make_test_request('POST') + resp = app.handle_request(req) + + h = parse_acl(version=2, data=resp.headers.get(ext_header)) + self.assertEqual(h['admin'], ['foo']) + self.assertEqual(h['read-write'], ['bar']) + self.assertEqual(h['read-only'], ['bas']) + class FakeObjectController(object): diff --git a/tools/gswauth_functional_tests.sh b/tools/gswauth_functional_tests.sh index dbe2248..287a20a 100755 --- a/tools/gswauth_functional_tests.sh +++ b/tools/gswauth_functional_tests.sh @@ -50,7 +50,7 @@ quit() fail() { cleanup - quit "$1" + quit "$1" } run_generic_tests() @@ -66,9 +66,9 @@ run_generic_tests() nosetests -v --exe \ --with-xunit \ - --xunit-file functional_tests/gluster-swift-gswauth-generic-functional-TC-report.xml \ + --xunit-file functional_tests_result/gluster-swift-gswauth-generic-functional-TC-report.xml \ --with-html-output \ - --html-out-file functional_tests/gluster-swift-gswauth-generic-functional-result.html \ + --html-out-file functional_tests_result/gluster-swift-gswauth-generic-functional-result.html \ test/functional || fail "Functional tests failed" } @@ -91,6 +91,7 @@ export SWIFT_TEST_CONFIG_FILE=/etc/swift/test.conf # Install the configuration files sudo mkdir /etc/swift > /dev/null 2>&1 +sudo cp -r test/functional_auth/common_conf/* /etc/swift || fail "Unable to copy configuration files to /etc/swift" sudo cp -r test/functional_auth/gswauth/conf/* /etc/swift || fail "Unable to copy configuration files to /etc/swift" sudo_env gluster-swift-gen-builders test test2 gsmetadata || fail "Unable to create ring files" @@ -101,12 +102,12 @@ sudo_env swift-init main start || fail "Unable to start swift" #swauth-prep sudo_env gswauth-prep -K gswauthkey || fail "Unable to prep gswauth" -mkdir functional_tests > /dev/null 2>&1 +mkdir functional_tests_result > /dev/null 2>&1 nosetests -v --exe \ --with-xunit \ - --xunit-file functional_tests/gluster-swift-gswauth-functional-TC-report.xml \ + --xunit-file functional_tests_result/gluster-swift-gswauth-functional-TC-report.xml \ --with-html-output \ - --html-out-file functional_tests/gluster-swift-gswauth-functional-result.html \ + --html-out-file functional_tests_result/gluster-swift-gswauth-functional-result.html \ test/functional_auth/gswauth || fail "Functional gswauth test failed" run_generic_tests diff --git a/tools/keystone_functional_tests.sh b/tools/keystone_functional_tests.sh index 620bcc7..b5aa25a 100755 --- a/tools/keystone_functional_tests.sh +++ b/tools/keystone_functional_tests.sh @@ -87,15 +87,15 @@ sudo_env gluster-swift-gen-builders $accounts || fail "Unable to create ring fil sudo service memcached start || fail "Unable to start memcached" sudo_env swift-init main start || fail "Unable to start swift" -mkdir functional_tests > /dev/null 2>&1 +mkdir functional_tests_result > /dev/null 2>&1 echo "== Keystone: Generic Functional Tests ==" nosetests -v --exe \ --with-xunit \ - --xunit-file functional_tests/gluster-swift-keystone-generic-functional-TC-report.xml \ + --xunit-file functional_tests_result/gluster-swift-keystone-generic-functional-TC-report.xml \ --with-html-output \ - --html-out-file functional_tests/gluster-swift-keystone-generic-functional-result.html \ + --html-out-file functional_tests_result/gluster-swift-keystone-generic-functional-result.html \ test/functional || fail "Functional tests failed" cleanup diff --git a/tools/swkrbath_functional_tests.sh b/tools/object_expirer_functional.sh index 9995f1d..2578619 100755 --- a/tools/swkrbath_functional_tests.sh +++ b/tools/object_expirer_functional.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2014 Red Hat, Inc. +# Copyright (c) 2013 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,69 +28,60 @@ cleanup() sudo service memcached stop sudo_env swift-init main stop sudo rm -rf /etc/swift > /dev/null 2>&1 - for acct in /mnt/gluster-object/* ; do - sudo rm -rf /mnt/gluster-object/${acct}/* > /dev/null 2>&1 - sudo setfattr -x user.swift.metadata /mnt/gluster-object/${acct} > /dev/null 2>&1 - done + sudo rm -rf /mnt/gluster-object/test{,2}/* > /dev/null 2>&1 + sudo rm -rf /mnt/gluster-object/gsexpiring/* > /dev/null 2>&1 + sudo setfattr -x user.swift.metadata /mnt/gluster-object/test{,2} > /dev/null 2>&1 } quit() { - echo "$1" - exit 1 + echo "$1" + exit 1 } fail() { - cleanup - quit "$1" + cleanup + quit "$1" } ### MAIN ### # Only run if there is no configuration in the system if [ -x /etc/swift ] ; then - quit "/etc/swift exists, cannot run functional tests." + quit "/etc/swift exists, cannot run functional tests." fi # Check the directories exist -DIRS="/mnt/gluster-object /mnt/gluster-object/test /mnt/gluster-object/test2 /mnt/gluster-object/gsmetadata" +DIRS="/mnt/gluster-object /mnt/gluster-object/test /mnt/gluster-object/test2 /mnt/gluster-object/gsexpiring" for d in $DIRS ; do - if [ ! -x $d ] ; then - quit "$d must exist on an XFS or GlusterFS volume" - fi + if [ ! -x $d ] ; then + quit "$d must exist on an XFS or GlusterFS volume" + fi done export SWIFT_TEST_CONFIG_FILE=/etc/swift/test.conf # Install the configuration files sudo mkdir /etc/swift > /dev/null 2>&1 -sudo cp -r test/functional_auth/swiftkerbauth/conf/* /etc/swift || fail "Unable to copy configuration files to /etc/swift" +sudo cp -r test/functional_auth/common_conf/* /etc/swift || fail "Unable to copy configuration files to /etc/swift" +sudo cp -r test/functional_auth/tempauth/conf/* /etc/swift || fail "Unable to copy configuration files to /etc/swift" +sudo_env gluster-swift-gen-builders gsexpiring test || fail "Unable to create ring files" -# Create the ring files -accounts="" -for acct in /mnt/gluster-object/* ; do - acct=`basename $acct` - accounts="$acct $accounts" -done -sudo_env gluster-swift-gen-builders $accounts || fail "Unable to create ring files" +# Remove old files +sudo rm -rf /mnt/gluster-object/test* > /dev/null 2>&1 +sudo rm -rf /mnt/gluster-object/gsexpiring/* > /dev/null 2>&1 # Start the services sudo service memcached start || fail "Unable to start memcached" sudo_env swift-init main start || fail "Unable to start swift" -mkdir functional_tests > /dev/null 2>&1 - -echo "== SwiftKerbAuth: Functional Tests ==" - +echo "Running functional tests with tempauth" +mkdir functional_tests > /dev/null 2>&1 nosetests -v --exe \ - --with-xunit \ - --xunit-file functional_tests/gluster-swift-swiftkerbauth-generic-functional-TC-report.xml \ - --with-html-output \ - --html-out-file functional_tests/gluster-swift-swiftkerbauth-generic-functional-result.html \ - test/functional_auth/swiftkerbauth || fail "Functional tests failed" + test/object_expirer_functional || fail "Object expirer functional tests failed" cleanup exit 0 diff --git a/tools/functional_tests.sh b/tools/tempauth_functional_tests.sh index 94d5d9b..f237f3e 100755 --- a/tools/functional_tests.sh +++ b/tools/tempauth_functional_tests.sh @@ -64,6 +64,7 @@ export SWIFT_TEST_CONFIG_FILE=/etc/swift/test.conf # Install the configuration files sudo mkdir /etc/swift > /dev/null 2>&1 +sudo cp -r test/functional_auth/common_conf/* /etc/swift || fail "Unable to copy configuration files to /etc/swift" sudo cp -r test/functional_auth/tempauth/conf/* /etc/swift || fail "Unable to copy configuration files to /etc/swift" sudo_env gluster-swift-gen-builders test test2 || fail "Unable to create ring files" @@ -71,13 +72,15 @@ sudo_env gluster-swift-gen-builders test test2 || fail "Unable to create ring fi sudo service memcached start || fail "Unable to start memcached" sudo_env swift-init main start || fail "Unable to start swift" +echo "Running functional tests with tempauth" + mkdir functional_tests > /dev/null 2>&1 nosetests -v --exe \ --with-xunit \ --xunit-file functional_tests/gluster-swift-generic-functional-TC-report.xml \ - --with-html-output \ - --html-out-file functional_tests/gluster-swift-generic-functional-result.html \ - test/functional || fail "Functional tests failed" + --with-html-output \ + --html-out-file functional_tests/gluster-swift-generic-functional-result.html \ + test/functional || fail "Functional tests failed" cleanup exit 0 @@ -14,45 +14,47 @@ setenv = VIRTUAL_ENV={envdir} NOSE_OPENSTACK_YELLOW=0.025 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 + NOSE_WITH_COVERAGE=1 + NOSE_COVER_BRANCHES=1 + NOSE_COVER_PACKAGE=gluster deps = - https://launchpad.net/gluster-swift/icehouse/1.13.0/+download/swift-1.13.0.tar.gz - --download-cache={homedir}/.pipcache - -r{toxinidir}/tools/test-requires - -r{toxinidir}/tools/requirements.txt + git+https://github.com/openstack/swift.git@icehouse-eol + -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +# Just having testtools package installed fixes some dependency issue +# https://github.com/swiftstack/vagrant-swift-all-in-one/issues/19 +# pip needs a good dependency resolver :/ + testtools + flake8 changedir = {toxinidir}/test/unit -commands = nosetests -v --exe --with-xunit --with-coverage --cover-package gluster --cover-erase --cover-xml --cover-html --cover-branches --with-html-output {posargs} +commands = nosetests -v {posargs} + +[testenv:cover] +setenv = VIRTUAL_ENV={envdir} + NOSE_WITH_COVERAGE=1 + NOSE_COVER_BRANCHES=1 + NOSE_COVER_HTML=1 + NOSE_COVER_HTML_DIR={toxinidir}/cover [tox:jenkins] downloadcache = ~/cache/pip [testenv:functest] changedir = {toxinidir} -commands = bash tools/functional_tests.sh - bash tools/gswauth_functional_tests.sh +commands = bash ./.functests [testenv:ksfunctest] changedir = {toxinidir} commands = bash tools/keystone_functional_tests.sh -[testenv:swfunctest] -changedir = {toxinidir} -commands = bash tools/swkrbath_functional_tests.sh - [testenv:pep8] -deps = - --download-cache={homedir}/.pipcache - -r{toxinidir}/tools/test-requires changedir = {toxinidir} commands = - flake8 - flake8 gluster test - -[testenv:cover] -setenv = NOSE_WITH_COVERAGE=1 + flake8 gluster test setup.py [testenv:venv] -commands = changedir = {toxinidir} +commands = {posargs} [testenv:run] changedir = {toxinidir} |