tempest-2014.1.dev4108.gf22b6cc/0000775000175000017500000000000012332757136016100 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/0000775000175000017500000000000012332757136017561 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/config.py0000664000175000017500000012614312332757070021404 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import print_function import logging as std_logging import os from oslo.config import cfg from tempest.openstack.common import log as logging def register_opt_group(conf, opt_group, options): conf.register_group(opt_group) for opt in options: conf.register_opt(opt, group=opt_group.name) identity_group = cfg.OptGroup(name='identity', title="Keystone Configuration Options") IdentityGroup = [ cfg.StrOpt('catalog_type', default='identity', help="Catalog type of the Identity service."), cfg.BoolOpt('disable_ssl_certificate_validation', default=False, help="Set to True if using self-signed SSL certificates."), cfg.StrOpt('uri', default=None, help="Full URI of the OpenStack Identity API (Keystone), v2"), cfg.StrOpt('uri_v3', help='Full URI of the OpenStack Identity API (Keystone), v3'), cfg.StrOpt('auth_version', default='v2', help="Identity API version to be used for authentication " "for API tests."), cfg.StrOpt('region', default='RegionOne', help="The identity region name to use. Also used as the other " "services' region name unless they are set explicitly. " "If no such region is found in the service catalog, the " "first found one is used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the identity service."), cfg.StrOpt('username', default=None, help="Username to use for Nova API requests."), cfg.StrOpt('tenant_name', default=None, help="Tenant name to use for Nova API requests."), cfg.StrOpt('admin_role', default='admin', help="Role required to administrate keystone."), cfg.StrOpt('password', default=None, help="API key to use when authenticating.", secret=True), cfg.StrOpt('domain_name', default=None, help="Domain name for authentication (Keystone V3)." "The same domain applies to user and project"), cfg.StrOpt('alt_username', default=None, help="Username of alternate user to use for Nova API " "requests."), cfg.StrOpt('alt_tenant_name', default=None, help="Alternate user's Tenant name to use for Nova API " "requests."), cfg.StrOpt('alt_password', default=None, help="API key to use when authenticating as alternate user.", secret=True), cfg.StrOpt('alt_domain_name', default=None, help="Alternate domain name for authentication (Keystone V3)." "The same domain applies to user and project"), cfg.StrOpt('admin_username', default=None, help="Administrative Username to use for " "Keystone API requests."), cfg.StrOpt('admin_tenant_name', default=None, help="Administrative Tenant name to use for Keystone API " "requests."), cfg.StrOpt('admin_password', default=None, help="API key to use when authenticating as admin.", secret=True), cfg.StrOpt('admin_domain_name', default=None, help="Admin domain name for authentication (Keystone V3)." "The same domain applies to user and project"), ] identity_feature_group = cfg.OptGroup(name='identity-feature-enabled', title='Enabled Identity Features') IdentityFeatureGroup = [ cfg.BoolOpt('trust', default=True, help='Does the identity service have delegation and ' 'impersonation enabled'), cfg.BoolOpt('api_v2', default=True, help='Is the v2 identity API enabled'), cfg.BoolOpt('api_v3', default=True, help='Is the v3 identity API enabled'), ] compute_group = cfg.OptGroup(name='compute', title='Compute Service Options') ComputeGroup = [ cfg.BoolOpt('allow_tenant_isolation', default=False, help="Allows test cases to create/destroy tenants and " "users. This option enables isolated test cases and " "better parallel execution, but also requires that " "OpenStack Identity API admin credentials are known."), cfg.StrOpt('image_ref', default="{$IMAGE_ID}", help="Valid primary image reference to be used in tests."), cfg.StrOpt('image_ref_alt', default="{$IMAGE_ID_ALT}", help="Valid secondary image reference to be used in tests."), cfg.StrOpt('flavor_ref', default="1", help="Valid primary flavor to use in tests."), cfg.StrOpt('flavor_ref_alt', default="2", help='Valid secondary flavor to be used in tests.'), cfg.StrOpt('image_ssh_user', default="root", help="User name used to authenticate to an instance."), cfg.StrOpt('image_ssh_password', default="password", help="Password used to authenticate to an instance."), cfg.StrOpt('image_alt_ssh_user', default="root", help="User name used to authenticate to an instance using " "the alternate image."), cfg.StrOpt('image_alt_ssh_password', default="password", help="Password used to authenticate to an instance using " "the alternate image."), cfg.IntOpt('build_interval', default=10, help="Time in seconds between build status checks."), cfg.IntOpt('build_timeout', default=300, help="Timeout in seconds to wait for an instance to build."), cfg.BoolOpt('run_ssh', default=False, help="Should the tests ssh to instances?"), cfg.StrOpt('ssh_auth_method', default='keypair', help="Auth method used for authenticate to the instance. " "Valid choices are: keypair, configured, adminpass. " "keypair: start the servers with an ssh keypair. " "configured: use the configured user and password. " "adminpass: use the injected adminPass. " "disabled: avoid using ssh when it is an option."), cfg.StrOpt('ssh_connect_method', default='fixed', help="How to connect to the instance? " "fixed: using the first ip belongs the fixed network " "floating: creating and using a floating ip"), cfg.StrOpt('ssh_user', default='root', help="User name used to authenticate to an instance."), cfg.IntOpt('ping_timeout', default=120, help="Timeout in seconds to wait for ping to " "succeed."), cfg.IntOpt('ssh_timeout', default=300, help="Timeout in seconds to wait for authentication to " "succeed."), cfg.IntOpt('ready_wait', default=0, help="Additional wait time for clean state, when there is " "no OS-EXT-STS extension available"), cfg.IntOpt('ssh_channel_timeout', default=60, help="Timeout in seconds to wait for output from ssh " "channel."), cfg.StrOpt('fixed_network_name', default='private', help="Visible fixed network name "), cfg.StrOpt('network_for_ssh', default='public', help="Network used for SSH connections."), cfg.IntOpt('ip_version_for_ssh', default=4, help="IP version used for SSH connections."), cfg.BoolOpt('use_floatingip_for_ssh', default=True, help="Does SSH use Floating IPs?"), cfg.StrOpt('catalog_type', default='compute', help="Catalog type of the Compute service."), cfg.StrOpt('region', default='', help="The compute region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the compute service."), cfg.StrOpt('catalog_v3_type', default='computev3', help="Catalog type of the Compute v3 service."), cfg.StrOpt('path_to_private_key', default=None, help="Path to a private key file for SSH access to remote " "hosts"), cfg.StrOpt('volume_device_name', default='vdb', help="Expected device name when a volume is attached to " "an instance"), cfg.IntOpt('shelved_offload_time', default=0, help='Time in seconds before a shelved instance is eligible ' 'for removing from a host. -1 never offload, 0 offload ' 'when shelved. This time should be the same as the time ' 'of nova.conf, and some tests will run for as long as the ' 'time.') ] compute_features_group = cfg.OptGroup(name='compute-feature-enabled', title="Enabled Compute Service Features") ComputeFeaturesGroup = [ cfg.BoolOpt('api_v3', default=True, help="If false, skip all nova v3 tests."), cfg.BoolOpt('disk_config', default=True, help="If false, skip disk config tests"), cfg.ListOpt('api_extensions', default=['all'], help='A list of enabled compute extensions with a special ' 'entry all which indicates every extension is enabled'), cfg.ListOpt('api_v3_extensions', default=['all'], help='A list of enabled v3 extensions with a special entry all' ' which indicates every extension is enabled'), cfg.BoolOpt('change_password', default=False, help="Does the test environment support changing the admin " "password?"), cfg.BoolOpt('resize', default=False, help="Does the test environment support resizing?"), cfg.BoolOpt('pause', default=True, help="Does the test environment support pausing?"), cfg.BoolOpt('suspend', default=True, help="Does the test environment support suspend/resume?"), cfg.BoolOpt('live_migration', default=False, help="Does the test environment support live migration " "available?"), cfg.BoolOpt('block_migration_for_live_migration', default=False, help="Does the test environment use block devices for live " "migration"), cfg.BoolOpt('block_migrate_cinder_iscsi', default=False, help="Does the test environment block migration support " "cinder iSCSI volumes"), cfg.BoolOpt('vnc_console', default=False, help='Enable VNC console. This configuration value should ' 'be same as [nova.vnc]->vnc_enabled in nova.conf') ] compute_admin_group = cfg.OptGroup(name='compute-admin', title="Compute Admin Options") ComputeAdminGroup = [ cfg.StrOpt('username', default=None, help="Administrative Username to use for Nova API requests."), cfg.StrOpt('tenant_name', default=None, help="Administrative Tenant name to use for Nova API " "requests."), cfg.StrOpt('password', default=None, help="API key to use when authenticating as admin.", secret=True), cfg.StrOpt('domain_name', default=None, help="Domain name for authentication as admin (Keystone V3)." "The same domain applies to user and project"), ] image_group = cfg.OptGroup(name='image', title="Image Service Options") ImageGroup = [ cfg.StrOpt('catalog_type', default='image', help='Catalog type of the Image service.'), cfg.StrOpt('region', default='', help="The image region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the image service."), cfg.StrOpt('http_image', default='http://download.cirros-cloud.net/0.3.1/' 'cirros-0.3.1-x86_64-uec.tar.gz', help='http accessible image') ] image_feature_group = cfg.OptGroup(name='image-feature-enabled', title='Enabled image service features') ImageFeaturesGroup = [ cfg.BoolOpt('api_v2', default=True, help="Is the v2 image API enabled"), cfg.BoolOpt('api_v1', default=True, help="Is the v1 image API enabled"), ] network_group = cfg.OptGroup(name='network', title='Network Service Options') NetworkGroup = [ cfg.StrOpt('catalog_type', default='network', help='Catalog type of the Neutron service.'), cfg.StrOpt('region', default='', help="The network region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the network service."), cfg.StrOpt('tenant_network_cidr', default="10.100.0.0/16", help="The cidr block to allocate tenant ipv4 subnets from"), cfg.IntOpt('tenant_network_mask_bits', default=28, help="The mask bits for tenant ipv4 subnets"), cfg.StrOpt('tenant_network_v6_cidr', default="2003::/64", help="The cidr block to allocate tenant ipv6 subnets from"), cfg.IntOpt('tenant_network_v6_mask_bits', default=96, help="The mask bits for tenant ipv6 subnets"), cfg.BoolOpt('tenant_networks_reachable', default=False, help="Whether tenant network connectivity should be " "evaluated directly"), cfg.StrOpt('public_network_id', default="", help="Id of the public network that provides external " "connectivity"), cfg.StrOpt('public_router_id', default="", help="Id of the public router that provides external " "connectivity"), cfg.IntOpt('build_timeout', default=300, help="Timeout in seconds to wait for network operation to " "complete."), cfg.IntOpt('build_interval', default=10, help="Time in seconds between network operation status " "checks."), ] network_feature_group = cfg.OptGroup(name='network-feature-enabled', title='Enabled network service features') NetworkFeaturesGroup = [ cfg.BoolOpt('ipv6', default=True, help="Allow the execution of IPv6 tests"), cfg.ListOpt('api_extensions', default=['all'], help='A list of enabled network extensions with a special ' 'entry all which indicates every extension is enabled'), ] queuing_group = cfg.OptGroup(name='queuing', title='Queuing Service') QueuingGroup = [ cfg.StrOpt('catalog_type', default='queuing', help='Catalog type of the Queuing service.'), ] volume_group = cfg.OptGroup(name='volume', title='Block Storage Options') VolumeGroup = [ cfg.IntOpt('build_interval', default=10, help='Time in seconds between volume availability checks.'), cfg.IntOpt('build_timeout', default=300, help='Timeout in seconds to wait for a volume to become' 'available.'), cfg.StrOpt('catalog_type', default='volume', help="Catalog type of the Volume Service"), cfg.StrOpt('region', default='', help="The volume region name to use. If empty, the value " "of identity.region is used instead. If no such region " "is found in the service catalog, the first found one is " "used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the volume service."), cfg.StrOpt('backend1_name', default='BACKEND_1', help="Name of the backend1 (must be declared in cinder.conf)"), cfg.StrOpt('backend2_name', default='BACKEND_2', help="Name of the backend2 (must be declared in cinder.conf)"), cfg.StrOpt('storage_protocol', default='iSCSI', help='Backend protocol to target when creating volume types'), cfg.StrOpt('vendor_name', default='Open Source', help='Backend vendor to target when creating volume types'), cfg.StrOpt('disk_format', default='raw', help='Disk format to use when copying a volume to image'), cfg.IntOpt('volume_size', default=1, help='Default size in GB for volumes created by volumes tests'), ] volume_feature_group = cfg.OptGroup(name='volume-feature-enabled', title='Enabled Cinder Features') VolumeFeaturesGroup = [ cfg.BoolOpt('multi_backend', default=False, help="Runs Cinder multi-backend test (requires 2 backends)"), cfg.BoolOpt('backup', default=True, help='Runs Cinder volumes backup test'), cfg.BoolOpt('snapshot', default=True, help='Runs Cinder volume snapshot test'), cfg.ListOpt('api_extensions', default=['all'], help='A list of enabled volume extensions with a special ' 'entry all which indicates every extension is enabled'), cfg.BoolOpt('api_v1', default=True, help="Is the v1 volume API enabled"), cfg.BoolOpt('api_v2', default=True, help="Is the v2 volume API enabled"), ] object_storage_group = cfg.OptGroup(name='object-storage', title='Object Storage Service Options') ObjectStoreGroup = [ cfg.StrOpt('catalog_type', default='object-store', help="Catalog type of the Object-Storage service."), cfg.StrOpt('region', default='', help="The object-storage region name to use. If empty, the " "value of identity.region is used instead. If no such " "region is found in the service catalog, the first found " "one is used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the object-store service."), cfg.IntOpt('container_sync_timeout', default=120, help="Number of seconds to time on waiting for a container " "to container synchronization complete."), cfg.IntOpt('container_sync_interval', default=5, help="Number of seconds to wait while looping to check the " "status of a container to container synchronization"), cfg.StrOpt('operator_role', default='Member', help="Role to add to users created for swift tests to " "enable creating containers"), cfg.StrOpt('reseller_admin_role', default='ResellerAdmin', help="User role that has reseller admin"), ] object_storage_feature_group = cfg.OptGroup( name='object-storage-feature-enabled', title='Enabled object-storage features') ObjectStoreFeaturesGroup = [ cfg.ListOpt('discoverable_apis', default=['all'], help="A list of the enabled optional discoverable apis. " "A single entry, all, indicates that all of these " "features are expected to be enabled"), ] database_group = cfg.OptGroup(name='database', title='Database Service Options') DatabaseGroup = [ cfg.StrOpt('catalog_type', default='database', help="Catalog type of the Database service."), cfg.StrOpt('db_flavor_ref', default="1", help="Valid primary flavor to use in database tests."), ] orchestration_group = cfg.OptGroup(name='orchestration', title='Orchestration Service Options') OrchestrationGroup = [ cfg.StrOpt('catalog_type', default='orchestration', help="Catalog type of the Orchestration service."), cfg.StrOpt('region', default='', help="The orchestration region name to use. If empty, the " "value of identity.region is used instead. If no such " "region is found in the service catalog, the first found " "one is used."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the orchestration service."), cfg.BoolOpt('allow_tenant_isolation', default=False, help="Allows test cases to create/destroy tenants and " "users. This option enables isolated test cases and " "better parallel execution, but also requires that " "OpenStack Identity API admin credentials are known."), cfg.IntOpt('build_interval', default=1, help="Time in seconds between build status checks."), cfg.IntOpt('build_timeout', default=1200, help="Timeout in seconds to wait for a stack to build."), cfg.StrOpt('instance_type', default='m1.micro', help="Instance type for tests. Needs to be big enough for a " "full OS plus the test workload"), cfg.StrOpt('image_ref', default=None, help="Name of heat-cfntools enabled image to use when " "launching test instances."), cfg.StrOpt('keypair_name', default=None, help="Name of existing keypair to launch servers with."), cfg.IntOpt('max_template_size', default=524288, help="Value must match heat configuration of the same name."), cfg.IntOpt('max_resources_per_stack', default=1000, help="Value must match heat configuration of the same name."), ] telemetry_group = cfg.OptGroup(name='telemetry', title='Telemetry Service Options') TelemetryGroup = [ cfg.StrOpt('catalog_type', default='metering', help="Catalog type of the Telemetry service."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the telemetry service."), ] dashboard_group = cfg.OptGroup(name="dashboard", title="Dashboard options") DashboardGroup = [ cfg.StrOpt('dashboard_url', default='http://localhost/', help="Where the dashboard can be found"), cfg.StrOpt('login_url', default='http://localhost/auth/login/', help="Login page for the dashboard"), ] data_processing_group = cfg.OptGroup(name="data_processing", title="Data Processing options") DataProcessingGroup = [ cfg.StrOpt('catalog_type', default='data_processing', help="Catalog type of the data processing service."), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the data processing " "service."), ] boto_group = cfg.OptGroup(name='boto', title='EC2/S3 options') BotoGroup = [ cfg.StrOpt('ec2_url', default="http://localhost:8773/services/Cloud", help="EC2 URL"), cfg.StrOpt('s3_url', default="http://localhost:8080", help="S3 URL"), cfg.StrOpt('aws_secret', default=None, help="AWS Secret Key", secret=True), cfg.StrOpt('aws_access', default=None, help="AWS Access Key"), cfg.StrOpt('aws_zone', default="nova", help="AWS Zone for EC2 tests"), cfg.StrOpt('s3_materials_path', default="/opt/stack/devstack/files/images/" "s3-materials/cirros-0.3.0", help="S3 Materials Path"), cfg.StrOpt('ari_manifest', default="cirros-0.3.0-x86_64-initrd.manifest.xml", help="ARI Ramdisk Image manifest"), cfg.StrOpt('ami_manifest', default="cirros-0.3.0-x86_64-blank.img.manifest.xml", help="AMI Machine Image manifest"), cfg.StrOpt('aki_manifest', default="cirros-0.3.0-x86_64-vmlinuz.manifest.xml", help="AKI Kernel Image manifest"), cfg.StrOpt('instance_type', default="m1.tiny", help="Instance type"), cfg.IntOpt('http_socket_timeout', default=3, help="boto Http socket timeout"), cfg.IntOpt('num_retries', default=1, help="boto num_retries on error"), cfg.IntOpt('build_timeout', default=60, help="Status Change Timeout"), cfg.IntOpt('build_interval', default=1, help="Status Change Test Interval"), ] stress_group = cfg.OptGroup(name='stress', title='Stress Test Options') StressGroup = [ cfg.StrOpt('nova_logdir', default=None, help='Directory containing log files on the compute nodes'), cfg.IntOpt('max_instances', default=16, help='Maximum number of instances to create during test.'), cfg.StrOpt('controller', default=None, help='Controller host.'), # new stress options cfg.StrOpt('target_controller', default=None, help='Controller host.'), cfg.StrOpt('target_ssh_user', default=None, help='ssh user.'), cfg.StrOpt('target_private_key_path', default=None, help='Path to private key.'), cfg.StrOpt('target_logfiles', default=None, help='regexp for list of log files.'), cfg.IntOpt('log_check_interval', default=60, help='time (in seconds) between log file error checks.'), cfg.IntOpt('default_thread_number_per_action', default=4, help='The number of threads created while stress test.'), cfg.BoolOpt('leave_dirty_stack', default=False, help='Prevent the cleaning (tearDownClass()) between' ' each stress test run if an exception occurs' ' during this run.'), cfg.BoolOpt('full_clean_stack', default=False, help='Allows a full cleaning process after a stress test.' ' Caution : this cleanup will remove every objects of' ' every tenant.') ] scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options') ScenarioGroup = [ cfg.StrOpt('img_dir', default='/opt/stack/new/devstack/files/images/' 'cirros-0.3.1-x86_64-uec', help='Directory containing image files'), cfg.StrOpt('qcow2_img_file', default='cirros-0.3.1-x86_64-disk.img', help='QCOW2 image file name'), cfg.StrOpt('ami_img_file', default='cirros-0.3.1-x86_64-blank.img', help='AMI image file name'), cfg.StrOpt('ari_img_file', default='cirros-0.3.1-x86_64-initrd', help='ARI image file name'), cfg.StrOpt('aki_img_file', default='cirros-0.3.1-x86_64-vmlinuz', help='AKI image file name'), cfg.StrOpt('ssh_user', default='cirros', help='ssh username for the image file'), cfg.IntOpt( 'large_ops_number', default=0, help="specifies how many resources to request at once. Used " "for large operations testing.") ] service_available_group = cfg.OptGroup(name="service_available", title="Available OpenStack Services") ServiceAvailableGroup = [ cfg.BoolOpt('cinder', default=True, help="Whether or not cinder is expected to be available"), cfg.BoolOpt('neutron', default=False, help="Whether or not neutron is expected to be available"), cfg.BoolOpt('glance', default=True, help="Whether or not glance is expected to be available"), cfg.BoolOpt('swift', default=True, help="Whether or not swift is expected to be available"), cfg.BoolOpt('nova', default=True, help="Whether or not nova is expected to be available"), cfg.BoolOpt('heat', default=False, help="Whether or not Heat is expected to be available"), cfg.BoolOpt('ceilometer', default=True, help="Whether or not Ceilometer is expected to be available"), cfg.BoolOpt('horizon', default=True, help="Whether or not Horizon is expected to be available"), cfg.BoolOpt('sahara', default=False, help="Whether or not Sahara is expected to be available"), cfg.BoolOpt('ironic', default=False, help="Whether or not Ironic is expected to be available"), cfg.BoolOpt('trove', default=False, help="Whether or not Trove is expected to be available"), cfg.BoolOpt('marconi', default=False, help="Whether or not Marconi is expected to be available"), ] debug_group = cfg.OptGroup(name="debug", title="Debug System") DebugGroup = [ cfg.BoolOpt('enable', default=True, help="Enable diagnostic commands"), cfg.StrOpt('trace_requests', default='', help="""A regex to determine which requests should be traced. This is a regex to match the caller for rest client requests to be able to selectively trace calls out of specific classes and methods. It largely exists for test development, and is not expected to be used in a real deploy of tempest. This will be matched against the discovered ClassName:method in the test environment. Expected values for this field are: * ClassName:test_method_name - traces one test_method * ClassName:setUp(Class) - traces specific setup functions * ClassName:tearDown(Class) - traces specific teardown functions * ClassName:_run_cleanups - traces the cleanup functions If nothing is specified, this feature is not enabled. To trace everything specify .* as the regex. """) ] input_scenario_group = cfg.OptGroup(name="input-scenario", title="Filters and values for" " input scenarios") InputScenarioGroup = [ cfg.StrOpt('image_regex', default='^cirros-0.3.1-x86_64-uec$', help="Matching images become parameters for scenario tests"), cfg.StrOpt('flavor_regex', default='^m1.nano$', help="Matching flavors become parameters for scenario tests"), cfg.StrOpt('non_ssh_image_regex', default='^.*[Ww]in.*$', help="SSH verification in tests is skipped" "for matching images"), cfg.StrOpt('ssh_user_regex', default="[[\"^.*[Cc]irros.*$\", \"root\"]]", help="List of user mapped to regex " "to matching image names."), ] baremetal_group = cfg.OptGroup(name='baremetal', title='Baremetal provisioning service options') BaremetalGroup = [ cfg.StrOpt('catalog_type', default='baremetal', help="Catalog type of the baremetal provisioning service"), cfg.BoolOpt('driver_enabled', default=False, help="Whether the Ironic nova-compute driver is enabled"), cfg.StrOpt('endpoint_type', default='publicURL', choices=['public', 'admin', 'internal', 'publicURL', 'adminURL', 'internalURL'], help="The endpoint type to use for the baremetal provisioning " "service"), cfg.IntOpt('active_timeout', default=300, help="Timeout for Ironic node to completely provision"), cfg.IntOpt('association_timeout', default=10, help="Timeout for association of Nova instance and Ironic " "node"), cfg.IntOpt('power_timeout', default=20, help="Timeout for Ironic power transitions."), cfg.IntOpt('unprovision_timeout', default=20, help="Timeout for unprovisioning an Ironic node.") ] cli_group = cfg.OptGroup(name='cli', title="cli Configuration Options") CLIGroup = [ cfg.BoolOpt('enabled', default=True, help="enable cli tests"), cfg.StrOpt('cli_dir', default='/usr/local/bin', help="directory where python client binaries are located"), cfg.BoolOpt('has_manage', default=True, help=("Whether the tempest run location has access to the " "*-manage commands. In a pure blackbox environment " "it will not.")), cfg.IntOpt('timeout', default=15, help="Number of seconds to wait on a CLI timeout"), ] negative_group = cfg.OptGroup(name='negative', title="Negative Test Options") NegativeGroup = [ cfg.StrOpt('test_generator', default='tempest.common.' + 'generator.negative_generator.NegativeTestGenerator', help="Test generator class for all negative tests"), ] def register_opts(): register_opt_group(cfg.CONF, compute_group, ComputeGroup) register_opt_group(cfg.CONF, compute_features_group, ComputeFeaturesGroup) register_opt_group(cfg.CONF, identity_group, IdentityGroup) register_opt_group(cfg.CONF, identity_feature_group, IdentityFeatureGroup) register_opt_group(cfg.CONF, image_group, ImageGroup) register_opt_group(cfg.CONF, image_feature_group, ImageFeaturesGroup) register_opt_group(cfg.CONF, network_group, NetworkGroup) register_opt_group(cfg.CONF, network_feature_group, NetworkFeaturesGroup) register_opt_group(cfg.CONF, queuing_group, QueuingGroup) register_opt_group(cfg.CONF, volume_group, VolumeGroup) register_opt_group(cfg.CONF, volume_feature_group, VolumeFeaturesGroup) register_opt_group(cfg.CONF, object_storage_group, ObjectStoreGroup) register_opt_group(cfg.CONF, object_storage_feature_group, ObjectStoreFeaturesGroup) register_opt_group(cfg.CONF, database_group, DatabaseGroup) register_opt_group(cfg.CONF, orchestration_group, OrchestrationGroup) register_opt_group(cfg.CONF, telemetry_group, TelemetryGroup) register_opt_group(cfg.CONF, dashboard_group, DashboardGroup) register_opt_group(cfg.CONF, data_processing_group, DataProcessingGroup) register_opt_group(cfg.CONF, boto_group, BotoGroup) register_opt_group(cfg.CONF, compute_admin_group, ComputeAdminGroup) register_opt_group(cfg.CONF, stress_group, StressGroup) register_opt_group(cfg.CONF, scenario_group, ScenarioGroup) register_opt_group(cfg.CONF, service_available_group, ServiceAvailableGroup) register_opt_group(cfg.CONF, debug_group, DebugGroup) register_opt_group(cfg.CONF, baremetal_group, BaremetalGroup) register_opt_group(cfg.CONF, input_scenario_group, InputScenarioGroup) register_opt_group(cfg.CONF, cli_group, CLIGroup) register_opt_group(cfg.CONF, negative_group, NegativeGroup) # this should never be called outside of this class class TempestConfigPrivate(object): """Provides OpenStack configuration information.""" DEFAULT_CONFIG_DIR = os.path.join( os.path.abspath(os.path.dirname(os.path.dirname(__file__))), "etc") DEFAULT_CONFIG_FILE = "tempest.conf" def _set_attrs(self): self.compute = cfg.CONF.compute self.compute_feature_enabled = cfg.CONF['compute-feature-enabled'] self.identity = cfg.CONF.identity self.identity_feature_enabled = cfg.CONF['identity-feature-enabled'] self.image = cfg.CONF.image self.image_feature_enabled = cfg.CONF['image-feature-enabled'] self.network = cfg.CONF.network self.network_feature_enabled = cfg.CONF['network-feature-enabled'] self.volume = cfg.CONF.volume self.volume_feature_enabled = cfg.CONF['volume-feature-enabled'] self.object_storage = cfg.CONF['object-storage'] self.object_storage_feature_enabled = cfg.CONF[ 'object-storage-feature-enabled'] self.database = cfg.CONF.database self.orchestration = cfg.CONF.orchestration self.queuing = cfg.CONF.queuing self.telemetry = cfg.CONF.telemetry self.dashboard = cfg.CONF.dashboard self.data_processing = cfg.CONF.data_processing self.boto = cfg.CONF.boto self.compute_admin = cfg.CONF['compute-admin'] self.stress = cfg.CONF.stress self.scenario = cfg.CONF.scenario self.service_available = cfg.CONF.service_available self.debug = cfg.CONF.debug self.baremetal = cfg.CONF.baremetal self.input_scenario = cfg.CONF['input-scenario'] self.cli = cfg.CONF.cli self.negative = cfg.CONF.negative if not self.compute_admin.username: self.compute_admin.username = self.identity.admin_username self.compute_admin.password = self.identity.admin_password self.compute_admin.tenant_name = self.identity.admin_tenant_name cfg.CONF.set_default('domain_name', self.identity.admin_domain_name, group='identity') cfg.CONF.set_default('alt_domain_name', self.identity.admin_domain_name, group='identity') cfg.CONF.set_default('domain_name', self.identity.admin_domain_name, group='compute-admin') def __init__(self, parse_conf=True): """Initialize a configuration from a conf directory and conf file.""" super(TempestConfigPrivate, self).__init__() config_files = [] failsafe_path = "/etc/tempest/" + self.DEFAULT_CONFIG_FILE # Environment variables override defaults... conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', self.DEFAULT_CONFIG_DIR) conf_file = os.environ.get('TEMPEST_CONFIG', self.DEFAULT_CONFIG_FILE) path = os.path.join(conf_dir, conf_file) if not os.path.isfile(path): path = failsafe_path # only parse the config file if we expect one to exist. This is needed # to remove an issue with the config file up to date checker. if parse_conf: config_files.append(path) cfg.CONF([], project='tempest', default_config_files=config_files) logging.setup('tempest') LOG = logging.getLogger('tempest') LOG.info("Using tempest config file %s" % path) register_opts() self._set_attrs() if parse_conf: cfg.CONF.log_opt_values(LOG, std_logging.DEBUG) class TempestConfigProxy(object): _config = None def __getattr__(self, attr): if not self._config: self._config = TempestConfigPrivate() return getattr(self._config, attr) CONF = TempestConfigProxy() tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/0000775000175000017500000000000012332757136021364 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_server_basic_ops.py0000664000175000017500000001132712332757070026326 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest.scenario import utils as test_utils from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) load_tests = test_utils.load_tests_input_scenario_utils class TestServerBasicOps(manager.OfficialClientTest): """ This smoke test case follows this basic set of operations: * Create a keypair for use in launching an instance * Create a security group to control network access in instance * Add simple permissive rules to the security group * Launch an instance * Pause/unpause the instance * Suspend/resume the instance * Terminate the instance """ def setUp(self): super(TestServerBasicOps, self).setUp() # Setup image and flavor the test instance # Support both configured and injected values if not hasattr(self, 'image_ref'): self.image_ref = CONF.compute.image_ref if not hasattr(self, 'flavor_ref'): self.flavor_ref = CONF.compute.flavor_ref self.image_utils = test_utils.ImageUtils() if not self.image_utils.is_flavor_enough(self.flavor_ref, self.image_ref): raise self.skipException( '{image} does not fit in {flavor}'.format( image=self.image_ref, flavor=self.flavor_ref ) ) self.run_ssh = CONF.compute.run_ssh and \ self.image_utils.is_sshable_image(self.image_ref) self.ssh_user = self.image_utils.ssh_user(self.image_ref) LOG.debug('Starting test for i:{image}, f:{flavor}. ' 'Run ssh: {ssh}, user: {ssh_user}'.format( image=self.image_ref, flavor=self.flavor_ref, ssh=self.run_ssh, ssh_user=self.ssh_user)) def add_keypair(self): self.keypair = self.create_keypair() def create_security_group(self): sg_name = data_utils.rand_name('secgroup-smoke') sg_desc = sg_name + " description" self.secgroup = self.compute_client.security_groups.create(sg_name, sg_desc) self.assertEqual(self.secgroup.name, sg_name) self.assertEqual(self.secgroup.description, sg_desc) self.set_resource('secgroup', self.secgroup) # Add rules to the security group self._create_loginable_secgroup_rule_nova(secgroup_id=self.secgroup.id) def boot_instance(self): # Create server with image and flavor from input scenario create_kwargs = { 'key_name': self.keypair.id } instance = self.create_server(image=self.image_ref, flavor=self.flavor_ref, create_kwargs=create_kwargs) self.set_resource('instance', instance) def terminate_instance(self): instance = self.get_resource('instance') instance.delete() self.remove_resource('instance') def verify_ssh(self): if self.run_ssh: # Obtain a floating IP floating_ip = self.compute_client.floating_ips.create() # Attach a floating IP instance = self.get_resource('instance') instance.add_floating_ip(floating_ip) # Check ssh try: linux_client = self.get_remote_client( server_or_ip=floating_ip.ip, username=self.image_utils.ssh_user(self.image_ref), private_key=self.keypair.private_key) linux_client.validate_authentication() except Exception: LOG.exception('ssh to server failed') self._log_console_output() raise @test.services('compute', 'network') def test_server_basicops(self): self.add_keypair() self.create_security_group() self.boot_instance() self.verify_ssh() self.terminate_instance() tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_network_advanced_server_ops.py0000664000175000017500000002065112332757070030563 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.common import debug from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest.test import services CONF = config.CONF LOG = logging.getLogger(__name__) class TestNetworkAdvancedServerOps(manager.NetworkScenarioTest): """ This test case checks VM connectivity after some advanced instance operations executed: * Stop/Start an instance * Reboot an instance * Rebuild an instance * Pause/Unpause an instance * Suspend/Resume an instance * Resize an instance """ @classmethod def setUpClass(cls): super(TestNetworkAdvancedServerOps, cls).setUpClass() cls.check_preconditions() if not (CONF.network.tenant_networks_reachable or CONF.network.public_network_id): msg = ('Either tenant_networks_reachable must be "true", or ' 'public_network_id must be defined.') cls.enabled = False raise cls.skipException(msg) def cleanup_wrapper(self, resource): self.cleanup_resource(resource, self.__class__.__name__) def setUp(self): super(TestNetworkAdvancedServerOps, self).setUp() key_name = data_utils.rand_name('keypair-smoke-') self.keypair = self.create_keypair(name=key_name) self.addCleanup(self.cleanup_wrapper, self.keypair) security_group =\ self._create_security_group_neutron(tenant_id=self.tenant_id) self.addCleanup(self.cleanup_wrapper, security_group) network = self._create_network(self.tenant_id) self.addCleanup(self.cleanup_wrapper, network) router = self._get_router(self.tenant_id) self.addCleanup(self.cleanup_wrapper, router) subnet = self._create_subnet(network) self.addCleanup(self.cleanup_wrapper, subnet) subnet.add_to_router(router.id) public_network_id = CONF.network.public_network_id create_kwargs = { 'nics': [ {'net-id': network.id}, ], 'key_name': self.keypair.name, 'security_groups': [security_group.name], } server_name = data_utils.rand_name('server-smoke-%d-') self.server = self.create_server(name=server_name, create_kwargs=create_kwargs) self.addCleanup(self.cleanup_wrapper, self.server) self.floating_ip = self._create_floating_ip(self.server, public_network_id) self.addCleanup(self.cleanup_wrapper, self.floating_ip) def _check_tenant_network_connectivity(self, server, username, private_key, should_connect=True): if not CONF.network.tenant_networks_reachable: msg = 'Tenant networks not configured to be reachable.' LOG.info(msg) return # The target login is assumed to have been configured for # key-based authentication by cloud-init. try: for net_name, ip_addresses in server.networks.iteritems(): for ip_address in ip_addresses: self._check_vm_connectivity(ip_address, username, private_key, should_connect=should_connect) except Exception: LOG.exception('Tenant network connectivity check failed') self._log_console_output(servers=[server]) debug.log_ip_ns() raise def _check_public_network_connectivity(self, floating_ip, username, private_key, should_connect=True): # The target login is assumed to have been configured for # key-based authentication by cloud-init. try: self._check_vm_connectivity(floating_ip, username, private_key, should_connect=should_connect) except Exception: LOG.exception("Public network connectivity check failed") debug.log_ip_ns() raise def _check_network_connectivity(self, should_connect=True): username = CONF.compute.image_ssh_user private_key = self.keypair.private_key self._check_tenant_network_connectivity(self.server, username, private_key, should_connect=should_connect) floating_ip = self.floating_ip.floating_ip_address self._check_public_network_connectivity(floating_ip, username, private_key, should_connect=should_connect) def _wait_server_status_and_check_network_connectivity(self): self.status_timeout(self.compute_client.servers, self.server.id, 'ACTIVE') self._check_network_connectivity() @services('compute', 'network') def test_server_connectivity_stop_start(self): self.server.stop() self.status_timeout(self.compute_client.servers, self.server.id, 'SHUTOFF') self._check_network_connectivity(should_connect=False) self.server.start() self._wait_server_status_and_check_network_connectivity() @services('compute', 'network') def test_server_connectivity_reboot(self): self.server.reboot() self._wait_server_status_and_check_network_connectivity() @services('compute', 'network') def test_server_connectivity_rebuild(self): image_ref_alt = CONF.compute.image_ref_alt self.server.rebuild(image_ref_alt) self._wait_server_status_and_check_network_connectivity() @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @services('compute', 'network') def test_server_connectivity_pause_unpause(self): self.server.pause() self.status_timeout(self.compute_client.servers, self.server.id, 'PAUSED') self._check_network_connectivity(should_connect=False) self.server.unpause() self._wait_server_status_and_check_network_connectivity() @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @services('compute', 'network') def test_server_connectivity_suspend_resume(self): self.server.suspend() self.status_timeout(self.compute_client.servers, self.server.id, 'SUSPENDED') self._check_network_connectivity(should_connect=False) self.server.resume() self._wait_server_status_and_check_network_connectivity() @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize is not available.') @services('compute', 'network') def test_server_connectivity_resize(self): resize_flavor = CONF.compute.flavor_ref_alt if resize_flavor == CONF.compute.flavor_ref: msg = "Skipping test - flavor_ref and flavor_ref_alt are identical" raise self.skipException(msg) resize_flavor = CONF.compute.flavor_ref_alt self.server.resize(resize_flavor) self.status_timeout(self.compute_client.servers, self.server.id, 'VERIFY_RESIZE') self.server.confirm_resize() self._wait_server_status_and_check_network_connectivity() tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_load_balancer_basic.py0000664000175000017500000003224112332757070026703 0ustar chuckchuck00000000000000# Copyright 2014 Mirantis.inc # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time import urllib from tempest.api.network import common as net_common from tempest import config from tempest import exceptions from tempest.scenario import manager from tempest import test config = config.CONF class TestLoadBalancerBasic(manager.NetworkScenarioTest): """ This test checks basic load balancing. The following is the scenario outline: 1. Create an instance 2. SSH to the instance and start two servers 3. Create a load balancer with two members and with ROUND_ROBIN algorithm associate the VIP with a floating ip 4. Send 10 requests to the floating ip and check that they are shared between the two servers and that both of them get equal portions of the requests """ @classmethod def check_preconditions(cls): super(TestLoadBalancerBasic, cls).check_preconditions() cfg = config.network if not test.is_extension_enabled('lbaas', 'network'): msg = 'LBaaS Extension is not enabled' cls.enabled = False raise cls.skipException(msg) if not (cfg.tenant_networks_reachable or cfg.public_network_id): msg = ('Either tenant_networks_reachable must be "true", or ' 'public_network_id must be defined.') cls.enabled = False raise cls.skipException(msg) @classmethod def setUpClass(cls): super(TestLoadBalancerBasic, cls).setUpClass() cls.check_preconditions() cls.servers_keypairs = {} cls.members = [] cls.floating_ips = {} cls.server_ips = {} cls.port1 = 80 cls.port2 = 88 def setUp(self): super(TestLoadBalancerBasic, self).setUp() self.server_ips = {} self._create_security_group() def cleanup_wrapper(self, resource): self.cleanup_resource(resource, self.__class__.__name__) def _create_security_group(self): self.security_group = self._create_security_group_neutron( tenant_id=self.tenant_id) self._create_security_group_rules_for_port(self.port1) self._create_security_group_rules_for_port(self.port2) self.addCleanup(self.cleanup_wrapper, self.security_group) def _create_security_group_rules_for_port(self, port): rule = { 'direction': 'ingress', 'protocol': 'tcp', 'port_range_min': port, 'port_range_max': port, } self._create_security_group_rule( client=self.network_client, secgroup=self.security_group, tenant_id=self.tenant_id, **rule) def _create_server(self, name): keypair = self.create_keypair(name='keypair-%s' % name) self.addCleanup(self.cleanup_wrapper, keypair) security_groups = [self.security_group.name] net = self._list_networks(tenant_id=self.tenant_id)[0] create_kwargs = { 'nics': [ {'net-id': net['id']}, ], 'key_name': keypair.name, 'security_groups': security_groups, } server = self.create_server(name=name, create_kwargs=create_kwargs) self.addCleanup(self.cleanup_wrapper, server) self.servers_keypairs[server.id] = keypair if (config.network.public_network_id and not config.network.tenant_networks_reachable): public_network_id = config.network.public_network_id floating_ip = self._create_floating_ip( server, public_network_id) self.addCleanup(self.cleanup_wrapper, floating_ip) self.floating_ips[floating_ip] = server self.server_ips[server.id] = floating_ip.floating_ip_address else: self.server_ips[server.id] = server.networks[net['name']][0] self.assertTrue(self.servers_keypairs) return server def _create_servers(self): for count in range(2): self._create_server(name=("server%s" % (count + 1))) self.assertEqual(len(self.servers_keypairs), 2) def _start_servers(self): """ Start two backends 1. SSH to the instance 2. Start two http backends listening on ports 80 and 88 respectively In case there are two instances, each backend is created on a separate instance. The backends are the inetd services. To start them we need to edit /etc/inetd.conf in the following way: www stream tcp nowait root /bin/sh sh /home/cirros/script_name Where /home/cirros/script_name is a path to a script which echoes the responses: echo -e 'HTTP/1.0 200 OK\r\n\r\nserver_name If we want the server to listen on port 88, then we use "kerberos" instead of "www". """ for server_id, ip in self.server_ips.iteritems(): private_key = self.servers_keypairs[server_id].private_key server_name = self.compute_client.servers.get(server_id).name ssh_client = self.get_remote_client( server_or_ip=ip, private_key=private_key) ssh_client.validate_authentication() # Create service for inetd create_script = """sudo sh -c "echo -e \\"echo -e 'HTTP/1.0 """ \ """200 OK\\\\\\r\\\\\\n\\\\\\r\\\\\\n""" \ """%(server)s'\\" >>/home/cirros/%(script)s\"""" cmd = create_script % { 'server': server_name, 'script': 'script1'} ssh_client.exec_command(cmd) # Configure inetd configure_inetd = """sudo sh -c "echo -e \\"%(service)s """ \ """stream tcp nowait root /bin/sh sh """ \ """/home/cirros/%(script)s\\" >> """ \ """/etc/inetd.conf\"""" # "www" stands for port 80 cmd = configure_inetd % {'service': 'www', 'script': 'script1'} ssh_client.exec_command(cmd) if len(self.server_ips) == 1: cmd = create_script % {'server': 'server2', 'script': 'script2'} ssh_client.exec_command(cmd) # "kerberos" stands for port 88 cmd = configure_inetd % {'service': 'kerberos', 'script': 'script2'} ssh_client.exec_command(cmd) # Get PIDs of inetd pids = ssh_client.get_pids('inetd') if pids != ['']: # If there are any inetd processes, reload them kill_cmd = "sudo kill -HUP %s" % ' '.join(pids) ssh_client.exec_command(kill_cmd) else: # In other case start inetd start_inetd = "sudo /usr/sbin/inetd /etc/inetd.conf" ssh_client.exec_command(start_inetd) def _check_connection(self, check_ip, port=80): def try_connect(ip, port): try: resp = urllib.urlopen("http://{0}:{1}/".format(ip, port)) if resp.getcode() == 200: return True return False except IOError: return False timeout = config.compute.ping_timeout start = time.time() while not try_connect(check_ip, port): if (time.time() - start) > timeout: message = "Timed out trying to connect to %s" % check_ip raise exceptions.TimeoutException(message) def _create_pool(self): """Create a pool with ROUND_ROBIN algorithm.""" # get tenant subnet and verify there's only one subnet = self._list_subnets(tenant_id=self.tenant_id)[0] self.subnet = net_common.DeletableSubnet(client=self.network_client, **subnet) self.pool = super(TestLoadBalancerBasic, self)._create_pool( lb_method='ROUND_ROBIN', protocol='HTTP', subnet_id=self.subnet.id) self.addCleanup(self.cleanup_wrapper, self.pool) self.assertTrue(self.pool) def _create_members(self): """ Create two members. In case there is only one server, create both members with the same ip but with different ports to listen on. """ for server_id, ip in self.server_ips.iteritems(): if len(self.server_ips) == 1: member1 = self._create_member(address=ip, protocol_port=self.port1, pool_id=self.pool.id) self.addCleanup(self.cleanup_wrapper, member1) member2 = self._create_member(address=ip, protocol_port=self.port2, pool_id=self.pool.id) self.addCleanup(self.cleanup_wrapper, member2) self.members.extend([member1, member2]) else: member = self._create_member(address=ip, protocol_port=self.port1, pool_id=self.pool.id) self.addCleanup(self.cleanup_wrapper, member) self.members.append(member) self.assertTrue(self.members) def _assign_floating_ip_to_vip(self, vip): public_network_id = config.network.public_network_id port_id = vip.port_id floating_ip = self._create_floating_ip(vip, public_network_id, port_id=port_id) self.addCleanup(self.cleanup_wrapper, floating_ip) self.floating_ips.setdefault(vip.id, []) self.floating_ips[vip.id].append(floating_ip) def _create_load_balancer(self): self._create_pool() self._create_members() self.vip = self._create_vip(protocol='HTTP', protocol_port=80, subnet_id=self.subnet.id, pool_id=self.pool.id) self.addCleanup(self.cleanup_wrapper, self.vip) self.status_timeout(NeutronRetriever(self.network_client, self.network_client.vip_path, net_common.DeletableVip), self.vip.id, expected_status='ACTIVE') if (config.network.public_network_id and not config.network.tenant_networks_reachable): self._assign_floating_ip_to_vip(self.vip) self.vip_ip = self.floating_ips[ self.vip.id][0]['floating_ip_address'] else: self.vip_ip = self.vip.address def _check_load_balancing(self): """ 1. Send 100 requests on the floating ip associated with the VIP 2. Check that the requests are shared between the two servers and that both of them get equal portions of the requests """ self._check_connection(self.vip_ip) resp = self._send_requests(self.vip_ip) self.assertEqual(set(["server1\n", "server2\n"]), set(resp)) self.assertEqual(50, resp.count("server1\n")) self.assertEqual(50, resp.count("server2\n")) def _send_requests(self, vip_ip): resp = [] for count in range(100): resp.append( urllib.urlopen( "http://{0}/".format(vip_ip)).read()) return resp @test.skip_because(bug='1295165') @test.attr(type='smoke') @test.services('compute', 'network') def test_load_balancer_basic(self): self._create_server('server1') self._start_servers() self._create_load_balancer() self._check_load_balancing() class NeutronRetriever(object): """ Helper class to make possible handling neutron objects returned by GET requests as attribute dicts. Whet get() method is called, the returned dictionary is wrapped into a corresponding DeletableResource class which provides attribute access to dictionary values. Usage: This retriever is used to allow using status_timeout from tempest.manager with Neutron objects. """ def __init__(self, network_client, path, resource): self.network_client = network_client self.path = path self.resource = resource def get(self, thing_id): obj = self.network_client.get(self.path % thing_id) return self.resource(client=self.network_client, **obj.values()[0]) tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/orchestration/0000775000175000017500000000000012332757136024250 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/orchestration/test_autoscaling.py0000664000175000017500000001072712332757070030176 0ustar chuckchuck00000000000000# 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 heatclient.exc as heat_exceptions import time from tempest import config from tempest.scenario import manager from tempest import test CONF = config.CONF class AutoScalingTest(manager.OrchestrationScenarioTest): def setUp(self): super(AutoScalingTest, self).setUp() if not CONF.orchestration.image_ref: raise self.skipException("No image available to test") self.client = self.orchestration_client def assign_keypair(self): self.stack_name = self._stack_rand_name() if CONF.orchestration.keypair_name: self.keypair_name = CONF.orchestration.keypair_name else: self.keypair = self.create_keypair() self.keypair_name = self.keypair.id def launch_stack(self): net = self._get_default_network() self.parameters = { 'KeyName': self.keypair_name, 'InstanceType': CONF.orchestration.instance_type, 'ImageId': CONF.orchestration.image_ref, 'StackStart': str(time.time()), 'Subnet': net['subnets'][0] } # create the stack self.template = self._load_template(__file__, 'test_autoscaling.yaml') self.client.stacks.create( stack_name=self.stack_name, template=self.template, parameters=self.parameters) self.stack = self.client.stacks.get(self.stack_name) self.stack_identifier = '%s/%s' % (self.stack_name, self.stack.id) # if a keypair was set, do not delete the stack on exit to allow # for manual post-mortums if not CONF.orchestration.keypair_name: self.set_resource('stack', self.stack) @test.skip_because(bug="1257575") @test.attr(type='slow') @test.services('orchestration', 'compute') def test_scale_up_then_down(self): self.assign_keypair() self.launch_stack() sid = self.stack_identifier timeout = CONF.orchestration.build_timeout interval = 10 self.assertEqual('CREATE', self.stack.action) # wait for create to complete. self.status_timeout(self.client.stacks, sid, 'COMPLETE', error_status='FAILED') self.stack.get() self.assertEqual('CREATE_COMPLETE', self.stack.stack_status) # the resource SmokeServerGroup is implemented as a nested # stack, so servers can be counted by counting the resources # inside that nested stack resource = self.client.resources.get(sid, 'SmokeServerGroup') nested_stack_id = resource.physical_resource_id def server_count(): # the number of servers is the number of resources # in the nested stack self.server_count = len( self.client.resources.list(nested_stack_id)) return self.server_count def assertScale(from_servers, to_servers): test.call_until_true(lambda: server_count() == to_servers, timeout, interval) self.assertEqual(to_servers, self.server_count, 'Failed scaling from %d to %d servers. ' 'Current server count: %s' % ( from_servers, to_servers, self.server_count)) # he marched them up to the top of the hill assertScale(1, 2) assertScale(2, 3) # and he marched them down again assertScale(3, 2) assertScale(2, 1) # delete stack on completion self.stack.delete() self.status_timeout(self.client.stacks, sid, 'COMPLETE', error_status='FAILED', not_found_exception=heat_exceptions.NotFound) try: self.stack.get() self.assertEqual('DELETE_COMPLETE', self.stack.stack_status) except heat_exceptions.NotFound: pass tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/orchestration/__init__.py0000664000175000017500000000000012332757070026344 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/orchestration/test_autoscaling.yaml0000664000175000017500000001435312332757070030507 0ustar chuckchuck00000000000000HeatTemplateFormatVersion: '2012-12-12' Description: | Template which tests autoscaling and load balancing Parameters: KeyName: Type: String InstanceType: Type: String ImageId: Type: String Subnet: Type: String StackStart: Description: Epoch seconds when the stack was launched Type: Number ConsumeStartSeconds: Description: Seconds after invocation when memory should be consumed Type: Number Default: '60' ConsumeStopSeconds: Description: Seconds after StackStart when memory should be released Type: Number Default: '420' ScaleUpThreshold: Description: Memory percentage threshold to scale up on Type: String Default: '70' ScaleDownThreshold: Description: Memory percentage threshold to scale down on Type: String Default: '60' ConsumeMemoryLimit: Description: Memory percentage threshold to consume Type: Number Default: '71' Resources: SmokeServerGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: AvailabilityZones: {'Fn::GetAZs': ''} LaunchConfigurationName: {Ref: LaunchConfig} MinSize: '1' MaxSize: '3' VPCZoneIdentifier: [{Ref: Subnet}] SmokeServerScaleUpPolicy: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: {Ref: SmokeServerGroup} Cooldown: '60' ScalingAdjustment: '1' SmokeServerScaleDownPolicy: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: {Ref: SmokeServerGroup} Cooldown: '60' ScalingAdjustment: '-1' MEMAlarmHigh: Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: Scale-up if MEM > ScaleUpThreshold% for 10 seconds MetricName: MemoryUtilization Namespace: system/linux Statistic: Average Period: '10' EvaluationPeriods: '1' Threshold: {Ref: ScaleUpThreshold} AlarmActions: [{Ref: SmokeServerScaleUpPolicy}] Dimensions: - Name: AutoScalingGroupName Value: {Ref: SmokeServerGroup} ComparisonOperator: GreaterThanThreshold MEMAlarmLow: Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: Scale-down if MEM < ScaleDownThreshold% for 10 seconds MetricName: MemoryUtilization Namespace: system/linux Statistic: Average Period: '10' EvaluationPeriods: '1' Threshold: {Ref: ScaleDownThreshold} AlarmActions: [{Ref: SmokeServerScaleDownPolicy}] Dimensions: - Name: AutoScalingGroupName Value: {Ref: SmokeServerGroup} ComparisonOperator: LessThanThreshold CfnUser: Type: AWS::IAM::User SmokeKeys: Type: AWS::IAM::AccessKey Properties: UserName: {Ref: CfnUser} SmokeSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Standard firewall rules SecurityGroupIngress: - {IpProtocol: tcp, FromPort: '22', ToPort: '22', CidrIp: 0.0.0.0/0} - {IpProtocol: tcp, FromPort: '80', ToPort: '80', CidrIp: 0.0.0.0/0} LaunchConfig: Type: AWS::AutoScaling::LaunchConfiguration Metadata: AWS::CloudFormation::Init: config: files: /etc/cfn/cfn-credentials: content: Fn::Replace: - $AWSAccessKeyId: {Ref: SmokeKeys} $AWSSecretKey: {'Fn::GetAtt': [SmokeKeys, SecretAccessKey]} - | AWSAccessKeyId=$AWSAccessKeyId AWSSecretKey=$AWSSecretKey mode: '000400' owner: root group: root /root/watch_loop: content: Fn::Replace: - _hi_: {Ref: MEMAlarmHigh} _lo_: {Ref: MEMAlarmLow} - | #!/bin/bash while : do /opt/aws/bin/cfn-push-stats --watch _hi_ --mem-util /opt/aws/bin/cfn-push-stats --watch _lo_ --mem-util sleep 4 done mode: '000700' owner: root group: root /root/consume_memory: content: Fn::Replace: - StackStart: {Ref: StackStart} ConsumeStopSeconds: {Ref: ConsumeStopSeconds} ConsumeStartSeconds: {Ref: ConsumeStartSeconds} ConsumeMemoryLimit: {Ref: ConsumeMemoryLimit} - | #!/usr/bin/env python import psutil import time import datetime import sys a = [] sleep_until_consume = ConsumeStartSeconds stack_start = StackStart consume_stop_time = stack_start + ConsumeStopSeconds memory_limit = ConsumeMemoryLimit if sleep_until_consume > 0: sys.stdout.flush() time.sleep(sleep_until_consume) while psutil.virtual_memory().percent < memory_limit: sys.stdout.flush() a.append(' ' * 10**5) time.sleep(0.1) sleep_until_exit = consume_stop_time - time.time() if sleep_until_exit > 0: time.sleep(sleep_until_exit) mode: '000700' owner: root group: root Properties: ImageId: {Ref: ImageId} InstanceType: {Ref: InstanceType} KeyName: {Ref: KeyName} SecurityGroups: [{Ref: SmokeSecurityGroup}] UserData: Fn::Base64: Fn::Replace: - ConsumeStopSeconds: {Ref: ConsumeStopSeconds} ConsumeStartSeconds: {Ref: ConsumeStartSeconds} ConsumeMemoryLimit: {Ref: ConsumeMemoryLimit} - | #!/bin/bash -v /opt/aws/bin/cfn-init # report on memory consumption every 4 seconds /root/watch_loop & # wait ConsumeStartSeconds then ramp up memory consumption # until it is over ConsumeMemoryLimit% # then exits ConsumeStopSeconds seconds after stack launch /root/consume_memory > /root/consume_memory.log & tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_large_ops.py0000664000175000017500000000451612332757070024753 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class TestLargeOpsScenario(manager.NetworkScenarioTest): """ Test large operations. This test below: * Spin up multiple instances in one nova call, and repeat three times * as a regular user * TODO: same thing for cinder """ @classmethod def setUpClass(cls): cls.set_network_resources() super(TestLargeOpsScenario, cls).setUpClass() def _wait_for_server_status(self, status): for server in self.servers: self.status_timeout( self.compute_client.servers, server.id, status) def nova_boot(self): name = data_utils.rand_name('scenario-server-') client = self.compute_client flavor_id = CONF.compute.flavor_ref secgroup = self._create_security_group_nova() self.servers = client.servers.create( name=name, image=self.image, flavor=flavor_id, min_count=CONF.scenario.large_ops_number, security_groups=[secgroup.name]) # needed because of bug 1199788 self.servers = [x for x in client.servers.list() if name in x.name] for server in self.servers: self.set_resource(server.name, server) self._wait_for_server_status('ACTIVE') @test.services('compute', 'image') def test_large_ops_scenario(self): if CONF.scenario.large_ops_number < 1: return self.glance_image_create() self.nova_boot() self.nova_boot() self.nova_boot() tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_security_groups_basic_ops.py0000664000175000017500000004432412332757070030271 0ustar chuckchuck00000000000000# Copyright 2013 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import clients from tempest.common import debug from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest): """ This test suite assumes that Nova has been configured to boot VM's with Neutron-managed networking, and attempts to verify cross tenant connectivity as follows ssh: in order to overcome "ip namespace", each tenant has an "access point" VM with floating-ip open to incoming ssh connection allowing network commands (ping/ssh) to be executed from within the tenant-network-namespace Tempest host performs key-based authentication to the ssh server via floating IP address connectivity test is done by pinging destination server via source server ssh connection. success - ping returns failure - ping_timeout reached setup: for primary tenant: 1. create a network&subnet 2. create a router (if public router isn't configured) 3. connect tenant network to public network via router 4. create an access point: a. a security group open to incoming ssh connection b. a VM with a floating ip 5. create a general empty security group (same as "default", but without rules allowing in-tenant traffic) tests: 1. _verify_network_details 2. _verify_mac_addr: for each access point verify that (subnet, fix_ip, mac address) are as defined in the port list 3. _test_in_tenant_block: test that in-tenant traffic is disabled without rules allowing it 4. _test_in_tenant_allow: test that in-tenant traffic is enabled once an appropriate rule has been created 5. _test_cross_tenant_block: test that cross-tenant traffic is disabled without a rule allowing it on destination tenant 6. _test_cross_tenant_allow: * test that cross-tenant traffic is enabled once an appropriate rule has been created on destination tenant. * test that reverse traffic is still blocked * test than revesre traffic is enabled once an appropriate rule has been created on source tenant assumptions: 1. alt_tenant/user existed and is different from primary_tenant/user 2. Public network is defined and reachable from the Tempest host 3. Public router can either be: * defined, in which case all tenants networks can connect directly to it, and cross tenant check will be done on the private IP of the destination tenant or * not defined (empty string), in which case each tanant will have its own router connected to the public network """ class TenantProperties(): """ helper class to save tenant details id credentials network subnet security groups servers access point """ def __init__(self, credentials): self.manager = clients.OfficialClientManager(credentials) # Credentials from manager are filled with both names and IDs self.creds = self.manager.credentials self.network = None self.subnet = None self.router = None self.security_groups = {} self.servers = list() def set_network(self, network, subnet, router): self.network = network self.subnet = subnet self.router = router def _get_tenant_credentials(self): # FIXME(andreaf) Unused method return self.creds @classmethod def check_preconditions(cls): super(TestSecurityGroupsBasicOps, cls).check_preconditions() if (cls.alt_creds is None) or \ (cls.tenant_id is cls.alt_creds.tenant_id): msg = 'No alt_tenant defined' cls.enabled = False raise cls.skipException(msg) if not (CONF.network.tenant_networks_reachable or CONF.network.public_network_id): msg = ('Either tenant_networks_reachable must be "true", or ' 'public_network_id must be defined.') cls.enabled = False raise cls.skipException(msg) @classmethod def setUpClass(cls): super(TestSecurityGroupsBasicOps, cls).setUpClass() cls.alt_creds = cls.alt_credentials() cls.alt_manager = clients.OfficialClientManager(cls.alt_creds) # Credentials from the manager are filled with both IDs and Names cls.alt_creds = cls.alt_manager.credentials cls.check_preconditions() # TODO(mnewby) Consider looking up entities as needed instead # of storing them as collections on the class. cls.floating_ips = {} cls.tenants = {} creds = cls.credentials() cls.primary_tenant = cls.TenantProperties(creds) cls.alt_tenant = cls.TenantProperties(cls.alt_creds) for tenant in [cls.primary_tenant, cls.alt_tenant]: cls.tenants[tenant.creds.tenant_id] = tenant cls.floating_ip_access = not CONF.network.public_router_id def cleanup_wrapper(self, resource): self.cleanup_resource(resource, self.__class__.__name__) def setUp(self): super(TestSecurityGroupsBasicOps, self).setUp() self._deploy_tenant(self.primary_tenant) self._verify_network_details(self.primary_tenant) self._verify_mac_addr(self.primary_tenant) def _create_tenant_keypairs(self, tenant_id): keypair = self.create_keypair( name=data_utils.rand_name('keypair-smoke-')) self.addCleanup(self.cleanup_wrapper, keypair) self.tenants[tenant_id].keypair = keypair def _create_tenant_security_groups(self, tenant): access_sg = self._create_empty_security_group( namestart='secgroup_access-', tenant_id=tenant.creds.tenant_id ) self.addCleanup(self.cleanup_wrapper, access_sg) # don't use default secgroup since it allows in-tenant traffic def_sg = self._create_empty_security_group( namestart='secgroup_general-', tenant_id=tenant.creds.tenant_id ) self.addCleanup(self.cleanup_wrapper, def_sg) tenant.security_groups.update(access=access_sg, default=def_sg) ssh_rule = dict( protocol='tcp', port_range_min=22, port_range_max=22, direction='ingress', ) rule = self._create_security_group_rule(secgroup=access_sg, **ssh_rule) self.addCleanup(self.cleanup_wrapper, rule) def _verify_network_details(self, tenant): # Checks that we see the newly created network/subnet/router via # checking the result of list_[networks,routers,subnets] # Check that (router, subnet) couple exist in port_list seen_nets = self._list_networks() seen_names = [n['name'] for n in seen_nets] seen_ids = [n['id'] for n in seen_nets] self.assertIn(tenant.network.name, seen_names) self.assertIn(tenant.network.id, seen_ids) seen_subnets = [(n['id'], n['cidr'], n['network_id']) for n in self._list_subnets()] mysubnet = (tenant.subnet.id, tenant.subnet.cidr, tenant.network.id) self.assertIn(mysubnet, seen_subnets) seen_routers = self._list_routers() seen_router_ids = [n['id'] for n in seen_routers] seen_router_names = [n['name'] for n in seen_routers] self.assertIn(tenant.router.name, seen_router_names) self.assertIn(tenant.router.id, seen_router_ids) myport = (tenant.router.id, tenant.subnet.id) router_ports = [(i['device_id'], i['fixed_ips'][0]['subnet_id']) for i in self.network_client.list_ports()['ports'] if i['device_owner'] == 'network:router_interface'] self.assertIn(myport, router_ports) def _create_server(self, name, tenant, security_groups=None): """ creates a server and assigns to security group """ self._set_compute_context(tenant) if security_groups is None: security_groups = [tenant.security_groups['default'].name] create_kwargs = { 'nics': [ {'net-id': tenant.network.id}, ], 'key_name': tenant.keypair.name, 'security_groups': security_groups, 'tenant_id': tenant.creds.tenant_id } server = self.create_server(name=name, create_kwargs=create_kwargs) self.addCleanup(self.cleanup_wrapper, server) return server def _create_tenant_servers(self, tenant, num=1): for i in range(num): name = 'server-{tenant}-gen-{num}-'.format( tenant=tenant.creds.tenant_name, num=i ) name = data_utils.rand_name(name) server = self._create_server(name, tenant) tenant.servers.append(server) def _set_access_point(self, tenant): """ creates a server in a secgroup with rule allowing external ssh in order to access tenant internal network workaround ip namespace """ secgroups = [sg.name for sg in tenant.security_groups.values()] name = 'server-{tenant}-access_point-'.format( tenant=tenant.creds.tenant_name) name = data_utils.rand_name(name) server = self._create_server(name, tenant, security_groups=secgroups) tenant.access_point = server self._assign_floating_ips(server) def _assign_floating_ips(self, server): public_network_id = CONF.network.public_network_id floating_ip = self._create_floating_ip(server, public_network_id) self.addCleanup(self.cleanup_wrapper, floating_ip) self.floating_ips.setdefault(server, floating_ip) def _create_tenant_network(self, tenant): network, subnet, router = self._create_networks(tenant.creds.tenant_id) for r in [network, router, subnet]: self.addCleanup(self.cleanup_wrapper, r) tenant.set_network(network, subnet, router) def _set_compute_context(self, tenant): self.compute_client = tenant.manager.compute_client return self.compute_client def _deploy_tenant(self, tenant_or_id): """ creates: network subnet router (if public not defined) access security group access-point server """ if not isinstance(tenant_or_id, self.TenantProperties): tenant = self.tenants[tenant_or_id] tenant_id = tenant_or_id else: tenant = tenant_or_id tenant_id = tenant.creds.tenant_id self._set_compute_context(tenant) self._create_tenant_keypairs(tenant_id) self._create_tenant_network(tenant) self._create_tenant_security_groups(tenant) self._set_access_point(tenant) def _get_server_ip(self, server, floating=False): """ returns the ip (floating/internal) of a server """ if floating: server_ip = self.floating_ips[server].floating_ip_address else: server_ip = None network_name = self.tenants[server.tenant_id].network.name if network_name in server.networks: server_ip = server.networks[network_name][0] return server_ip def _connect_to_access_point(self, tenant): """ create ssh connection to tenant access point """ access_point_ssh = \ self.floating_ips[tenant.access_point].floating_ip_address private_key = tenant.keypair.private_key access_point_ssh = self._ssh_to_server(access_point_ssh, private_key=private_key) return access_point_ssh def _check_connectivity(self, access_point, ip, should_succeed=True): if should_succeed: msg = "Timed out waiting for %s to become reachable" % ip else: msg = "%s is reachable" % ip try: self.assertTrue(self._check_remote_connectivity(access_point, ip, should_succeed), msg) except Exception: debug.log_net_debug() raise def _test_in_tenant_block(self, tenant): access_point_ssh = self._connect_to_access_point(tenant) for server in tenant.servers: self._check_connectivity(access_point=access_point_ssh, ip=self._get_server_ip(server), should_succeed=False) def _test_in_tenant_allow(self, tenant): ruleset = dict( protocol='icmp', remote_group_id=tenant.security_groups['default'].id, direction='ingress' ) rule = self._create_security_group_rule( secgroup=tenant.security_groups['default'], **ruleset ) self.addCleanup(self.cleanup_wrapper, rule) access_point_ssh = self._connect_to_access_point(tenant) for server in tenant.servers: self._check_connectivity(access_point=access_point_ssh, ip=self._get_server_ip(server)) def _test_cross_tenant_block(self, source_tenant, dest_tenant): """ if public router isn't defined, then dest_tenant access is via floating-ip """ access_point_ssh = self._connect_to_access_point(source_tenant) ip = self._get_server_ip(dest_tenant.access_point, floating=self.floating_ip_access) self._check_connectivity(access_point=access_point_ssh, ip=ip, should_succeed=False) def _test_cross_tenant_allow(self, source_tenant, dest_tenant): """ check for each direction: creating rule for tenant incoming traffic enables only 1way traffic """ ruleset = dict( protocol='icmp', direction='ingress' ) rule_s2d = self._create_security_group_rule( secgroup=dest_tenant.security_groups['default'], **ruleset ) self.addCleanup(self.cleanup_wrapper, rule_s2d) access_point_ssh = self._connect_to_access_point(source_tenant) ip = self._get_server_ip(dest_tenant.access_point, floating=self.floating_ip_access) self._check_connectivity(access_point_ssh, ip) # test that reverse traffic is still blocked self._test_cross_tenant_block(dest_tenant, source_tenant) # allow reverse traffic and check rule_d2s = self._create_security_group_rule( secgroup=source_tenant.security_groups['default'], **ruleset ) self.addCleanup(self.cleanup_wrapper, rule_d2s) access_point_ssh_2 = self._connect_to_access_point(dest_tenant) ip = self._get_server_ip(source_tenant.access_point, floating=self.floating_ip_access) self._check_connectivity(access_point_ssh_2, ip) def _verify_mac_addr(self, tenant): """ verify that VM (tenant's access point) has the same ip,mac as listed in port list """ access_point_ssh = self._connect_to_access_point(tenant) mac_addr = access_point_ssh.get_mac_address() mac_addr = mac_addr.strip().lower() # Get the fixed_ips and mac_address fields of all ports. Select # only those two columns to reduce the size of the response. port_list = self.network_client.list_ports( fields=['fixed_ips', 'mac_address'])['ports'] port_detail_list = [ (port['fixed_ips'][0]['subnet_id'], port['fixed_ips'][0]['ip_address'], port['mac_address'].lower()) for port in port_list if port['fixed_ips'] ] server_ip = self._get_server_ip(tenant.access_point) subnet_id = tenant.subnet.id self.assertIn((subnet_id, server_ip, mac_addr), port_detail_list) @test.attr(type='smoke') @test.services('compute', 'network') def test_cross_tenant_traffic(self): try: # deploy new tenant self._deploy_tenant(self.alt_tenant) self._verify_network_details(self.alt_tenant) self._verify_mac_addr(self.alt_tenant) # cross tenant check source_tenant = self.primary_tenant dest_tenant = self.alt_tenant self._test_cross_tenant_block(source_tenant, dest_tenant) self._test_cross_tenant_allow(source_tenant, dest_tenant) except Exception: for tenant in self.tenants.values(): self._log_console_output(servers=tenant.servers) raise @test.attr(type='smoke') @test.services('compute', 'network') def test_in_tenant_traffic(self): try: self._create_tenant_servers(self.primary_tenant, num=1) # in-tenant check self._test_in_tenant_block(self.primary_tenant) self._test_in_tenant_allow(self.primary_tenant) except Exception: for tenant in self.tenants.values(): self._log_console_output(servers=tenant.servers) raise tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_dashboard_basic_ops.py0000664000175000017500000000512312332757070026744 0ustar chuckchuck00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib import urllib2 from lxml import html from tempest import config from tempest.scenario import manager from tempest import test CONF = config.CONF class TestDashboardBasicOps(manager.OfficialClientTest): """ This is a basic scenario test: * checks that the login page is available * logs in as a regular user * checks that the user home page loads without error """ @classmethod def setUpClass(cls): cls.set_network_resources() super(TestDashboardBasicOps, cls).setUpClass() if not CONF.service_available.horizon: raise cls.skipException("Horizon support is required") def check_login_page(self): response = urllib2.urlopen(CONF.dashboard.dashboard_url) self.assertIn("

Log In

", response.read()) def user_login(self): self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor()) response = self.opener.open(CONF.dashboard.dashboard_url).read() # Grab the CSRF token and default region csrf_token = html.fromstring(response).xpath( '//input[@name="csrfmiddlewaretoken"]/@value')[0] region = html.fromstring(response).xpath( '//input[@name="region"]/@value')[0] # Prepare login form request req = urllib2.Request(CONF.dashboard.login_url) req.add_header('Content-type', 'application/x-www-form-urlencoded') req.add_header('Referer', CONF.dashboard.dashboard_url) params = {'username': CONF.identity.username, 'password': CONF.identity.password, 'region': region, 'csrfmiddlewaretoken': csrf_token} self.opener.open(req, urllib.urlencode(params)) def check_home_page(self): response = self.opener.open(CONF.dashboard.dashboard_url) self.assertIn('Overview', response.read()) @test.services('dashboard') def test_basic_scenario(self): self.check_login_page() self.user_login() self.check_home_page() tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/manager.py0000664000175000017500000012275212332757070023356 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import os import six import subprocess import netaddr from neutronclient.common import exceptions as exc from novaclient import exceptions as nova_exceptions from tempest.api.network import common as net_common from tempest import auth from tempest import clients from tempest.common import isolated_creds from tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config from tempest import exceptions from tempest.openstack.common import log import tempest.test CONF = config.CONF LOG = log.getLogger(__name__) # NOTE(afazekas): Workaround for the stdout logging LOG_nova_client = logging.getLogger('novaclient.client') LOG_nova_client.addHandler(log.NullHandler()) LOG_cinder_client = logging.getLogger('cinderclient.client') LOG_cinder_client.addHandler(log.NullHandler()) class OfficialClientTest(tempest.test.BaseTestCase): """ Official Client test base class for scenario testing. Official Client tests are tests that have the following characteristics: * Test basic operations of an API, typically in an order that a regular user would perform those operations * Test only the correct inputs and action paths -- no fuzz or random input data is sent, only valid inputs. * Use only the default client tool for calling an API """ @classmethod def setUpClass(cls): super(OfficialClientTest, cls).setUpClass() cls.isolated_creds = isolated_creds.IsolatedCreds( cls.__name__, tempest_client=False, network_resources=cls.network_resources) cls.manager = clients.OfficialClientManager( credentials=cls.credentials()) cls.compute_client = cls.manager.compute_client cls.image_client = cls.manager.image_client cls.baremetal_client = cls.manager.baremetal_client cls.identity_client = cls.manager.identity_client cls.network_client = cls.manager.network_client cls.volume_client = cls.manager.volume_client cls.object_storage_client = cls.manager.object_storage_client cls.orchestration_client = cls.manager.orchestration_client cls.data_processing_client = cls.manager.data_processing_client cls.resource_keys = {} cls.os_resources = [] @classmethod def _get_credentials(cls, get_creds, ctype): if CONF.compute.allow_tenant_isolation: creds = get_creds() else: creds = auth.get_default_credentials(ctype) return creds @classmethod def credentials(cls): return cls._get_credentials(cls.isolated_creds.get_primary_creds, 'user') @classmethod def alt_credentials(cls): return cls._get_credentials(cls.isolated_creds.get_alt_creds, 'alt_user') @classmethod def admin_credentials(cls): return cls._get_credentials(cls.isolated_creds.get_admin_creds, 'identity_admin') @staticmethod def cleanup_resource(resource, test_name): LOG.debug("Deleting %r from shared resources of %s" % (resource, test_name)) try: # OpenStack resources are assumed to have a delete() # method which destroys the resource... resource.delete() except Exception as e: # If the resource is already missing, mission accomplished. # add status code as workaround for bug 1247568 if (e.__class__.__name__ == 'NotFound' or (hasattr(e, 'status_code') and e.status_code == 404)): return raise def is_deletion_complete(): # Deletion testing is only required for objects whose # existence cannot be checked via retrieval. if isinstance(resource, dict): return True try: resource.get() except Exception as e: # Clients are expected to return an exception # called 'NotFound' if retrieval fails. if e.__class__.__name__ == 'NotFound': return True raise return False # Block until resource deletion has completed or timed-out tempest.test.call_until_true(is_deletion_complete, 10, 1) @classmethod def tearDownClass(cls): # NOTE(jaypipes): Because scenario tests are typically run in a # specific order, and because test methods in scenario tests # generally create resources in a particular order, we destroy # resources in the reverse order in which resources are added to # the scenario test class object while cls.os_resources: thing = cls.os_resources.pop() cls.cleanup_resource(thing, cls.__name__) cls.isolated_creds.clear_isolated_creds() super(OfficialClientTest, cls).tearDownClass() @classmethod def set_resource(cls, key, thing): LOG.debug("Adding %r to shared resources of %s" % (thing, cls.__name__)) cls.resource_keys[key] = thing cls.os_resources.append(thing) @classmethod def get_resource(cls, key): return cls.resource_keys[key] @classmethod def remove_resource(cls, key): thing = cls.resource_keys[key] cls.os_resources.remove(thing) del cls.resource_keys[key] def status_timeout(self, things, thing_id, expected_status, error_status='ERROR', not_found_exception=nova_exceptions.NotFound): """ Given a thing and an expected status, do a loop, sleeping for a configurable amount of time, checking for the expected status to show. At any time, if the returned status of the thing is ERROR, fail out. """ self._status_timeout(things, thing_id, expected_status=expected_status, error_status=error_status, not_found_exception=not_found_exception) def delete_timeout(self, things, thing_id, error_status='ERROR', not_found_exception=nova_exceptions.NotFound): """ Given a thing, do a loop, sleeping for a configurable amount of time, checking for the deleted status to show. At any time, if the returned status of the thing is ERROR, fail out. """ self._status_timeout(things, thing_id, allow_notfound=True, error_status=error_status, not_found_exception=not_found_exception) def _status_timeout(self, things, thing_id, expected_status=None, allow_notfound=False, error_status='ERROR', not_found_exception=nova_exceptions.NotFound): log_status = expected_status if expected_status else '' if allow_notfound: log_status += ' or NotFound' if log_status != '' else 'NotFound' def check_status(): # python-novaclient has resources available to its client # that all implement a get() method taking an identifier # for the singular resource to retrieve. try: thing = things.get(thing_id) except not_found_exception: if allow_notfound: return True else: raise new_status = thing.status # Some components are reporting error status in lower case # so case sensitive comparisons can really mess things # up. if new_status.lower() == error_status.lower(): message = ("%s failed to get to expected status (%s). " "In %s state.") % (thing, expected_status, new_status) raise exceptions.BuildErrorException(message, server_id=thing_id) elif new_status == expected_status and expected_status is not None: return True # All good. LOG.debug("Waiting for %s to get to %s status. " "Currently in %s status", thing, log_status, new_status) if not tempest.test.call_until_true( check_status, CONF.compute.build_timeout, CONF.compute.build_interval): message = ("Timed out waiting for thing %s " "to become %s") % (thing_id, log_status) raise exceptions.TimeoutException(message) def _create_loginable_secgroup_rule_nova(self, client=None, secgroup_id=None): if client is None: client = self.compute_client if secgroup_id is None: sgs = client.security_groups.list() for sg in sgs: if sg.name == 'default': secgroup_id = sg.id # These rules are intended to permit inbound ssh and icmp # traffic from all sources, so no group_id is provided. # Setting a group_id would only permit traffic from ports # belonging to the same security group. rulesets = [ { # ssh 'ip_protocol': 'tcp', 'from_port': 22, 'to_port': 22, 'cidr': '0.0.0.0/0', }, { # ping 'ip_protocol': 'icmp', 'from_port': -1, 'to_port': -1, 'cidr': '0.0.0.0/0', } ] rules = list() for ruleset in rulesets: sg_rule = client.security_group_rules.create(secgroup_id, **ruleset) self.set_resource(sg_rule.id, sg_rule) rules.append(sg_rule) return rules def create_server(self, client=None, name=None, image=None, flavor=None, wait=True, create_kwargs={}): if client is None: client = self.compute_client if name is None: name = data_utils.rand_name('scenario-server-') if image is None: image = CONF.compute.image_ref if flavor is None: flavor = CONF.compute.flavor_ref fixed_network_name = CONF.compute.fixed_network_name if 'nics' not in create_kwargs and fixed_network_name: networks = client.networks.list() # If several networks found, set the NetID on which to connect the # server to avoid the following error "Multiple possible networks # found, use a Network ID to be more specific." # See Tempest #1250866 if len(networks) > 1: for network in networks: if network.label == fixed_network_name: create_kwargs['nics'] = [{'net-id': network.id}] break # If we didn't find the network we were looking for : else: msg = ("The network on which the NIC of the server must " "be connected can not be found : " "fixed_network_name=%s. Starting instance without " "specifying a network.") % fixed_network_name LOG.info(msg) LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)", name, image, flavor) server = client.servers.create(name, image, flavor, **create_kwargs) self.assertEqual(server.name, name) self.set_resource(name, server) if wait: self.status_timeout(client.servers, server.id, 'ACTIVE') # The instance retrieved on creation is missing network # details, necessitating retrieval after it becomes active to # ensure correct details. server = client.servers.get(server.id) self.set_resource(name, server) LOG.debug("Created server: %s", server) return server def create_volume(self, client=None, size=1, name=None, snapshot_id=None, imageRef=None): if client is None: client = self.volume_client if name is None: name = data_utils.rand_name('scenario-volume-') LOG.debug("Creating a volume (size: %s, name: %s)", size, name) volume = client.volumes.create(size=size, display_name=name, snapshot_id=snapshot_id, imageRef=imageRef) self.set_resource(name, volume) self.assertEqual(name, volume.display_name) self.status_timeout(client.volumes, volume.id, 'available') LOG.debug("Created volume: %s", volume) return volume def create_server_snapshot(self, server, compute_client=None, image_client=None, name=None): if compute_client is None: compute_client = self.compute_client if image_client is None: image_client = self.image_client if name is None: name = data_utils.rand_name('scenario-snapshot-') LOG.debug("Creating a snapshot image for server: %s", server.name) image_id = compute_client.servers.create_image(server, name) self.addCleanup(image_client.images.delete, image_id) self.status_timeout(image_client.images, image_id, 'active') snapshot_image = image_client.images.get(image_id) self.assertEqual(name, snapshot_image.name) LOG.debug("Created snapshot image %s for server %s", snapshot_image.name, server.name) return snapshot_image def create_keypair(self, client=None, name=None): if client is None: client = self.compute_client if name is None: name = data_utils.rand_name('scenario-keypair-') keypair = client.keypairs.create(name) self.assertEqual(keypair.name, name) self.set_resource(name, keypair) return keypair def get_remote_client(self, server_or_ip, username=None, private_key=None): if isinstance(server_or_ip, six.string_types): ip = server_or_ip else: network_name_for_ssh = CONF.compute.network_for_ssh ip = server_or_ip.networks[network_name_for_ssh][0] if username is None: username = CONF.scenario.ssh_user if private_key is None: private_key = self.keypair.private_key return remote_client.RemoteClient(ip, username, pkey=private_key) def _log_console_output(self, servers=None): if not servers: servers = self.compute_client.servers.list() for server in servers: LOG.debug('Console output for %s', server.id) LOG.debug(server.get_console_output()) def wait_for_volume_status(self, status): volume_id = self.volume.id self.status_timeout( self.volume_client.volumes, volume_id, status) def _image_create(self, name, fmt, path, properties={}): name = data_utils.rand_name('%s-' % name) image_file = open(path, 'rb') self.addCleanup(image_file.close) params = { 'name': name, 'container_format': fmt, 'disk_format': fmt, 'is_public': 'True', } params.update(properties) image = self.image_client.images.create(**params) self.addCleanup(self.image_client.images.delete, image) self.assertEqual("queued", image.status) image.update(data=image_file) return image.id def glance_image_create(self): qcow2_img_path = (CONF.scenario.img_dir + "/" + CONF.scenario.qcow2_img_file) aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s" % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path)) try: self.image = self._image_create('scenario-img', 'bare', qcow2_img_path, properties={'disk_format': 'qcow2'}) except IOError: LOG.debug("A qcow2 image was not found. Try to get a uec image.") kernel = self._image_create('scenario-aki', 'aki', aki_img_path) ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path) properties = { 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk} } self.image = self._image_create('scenario-ami', 'ami', path=ami_img_path, properties=properties) LOG.debug("image:%s" % self.image) class BaremetalScenarioTest(OfficialClientTest): @classmethod def setUpClass(cls): super(BaremetalScenarioTest, cls).setUpClass() if (not CONF.service_available.ironic or not CONF.baremetal.driver_enabled): msg = 'Ironic not available or Ironic compute driver not enabled' raise cls.skipException(msg) # use an admin client manager for baremetal client username, password, tenant = cls.admin_credentials() manager = clients.OfficialClientManager(username, password, tenant) cls.baremetal_client = manager.baremetal_client # allow any issues obtaining the node list to raise early cls.baremetal_client.node.list() def _node_state_timeout(self, node_id, state_attr, target_states, timeout=10, interval=1): if not isinstance(target_states, list): target_states = [target_states] def check_state(): node = self.get_node(node_id=node_id) if getattr(node, state_attr) in target_states: return True return False if not tempest.test.call_until_true( check_state, timeout, interval): msg = ("Timed out waiting for node %s to reach %s state(s) %s" % (node_id, state_attr, target_states)) raise exceptions.TimeoutException(msg) def wait_provisioning_state(self, node_id, state, timeout): self._node_state_timeout( node_id=node_id, state_attr='provision_state', target_states=state, timeout=timeout) def wait_power_state(self, node_id, state): self._node_state_timeout( node_id=node_id, state_attr='power_state', target_states=state, timeout=CONF.baremetal.power_timeout) def wait_node(self, instance_id): """Waits for a node to be associated with instance_id.""" from ironicclient import exc as ironic_exceptions def _get_node(): node = None try: node = self.get_node(instance_id=instance_id) except ironic_exceptions.HTTPNotFound: pass return node is not None if not tempest.test.call_until_true( _get_node, CONF.baremetal.association_timeout, 1): msg = ('Timed out waiting to get Ironic node by instance id %s' % instance_id) raise exceptions.TimeoutException(msg) def get_node(self, node_id=None, instance_id=None): if node_id: return self.baremetal_client.node.get(node_id) elif instance_id: return self.baremetal_client.node.get_by_instance_uuid(instance_id) def get_ports(self, node_id): ports = [] for port in self.baremetal_client.node.list_ports(node_id): ports.append(self.baremetal_client.port.get(port.uuid)) return ports class NetworkScenarioTest(OfficialClientTest): """ Base class for network scenario tests """ @classmethod def check_preconditions(cls): if (CONF.service_available.neutron): cls.enabled = True # verify that neutron_available is telling the truth try: cls.network_client.list_networks() except exc.EndpointNotFound: cls.enabled = False raise else: cls.enabled = False msg = 'Neutron not available' raise cls.skipException(msg) @classmethod def setUpClass(cls): super(NetworkScenarioTest, cls).setUpClass() cls.tenant_id = cls.manager.identity_client.tenant_id def _create_network(self, tenant_id, namestart='network-smoke-'): name = data_utils.rand_name(namestart) body = dict( network=dict( name=name, tenant_id=tenant_id, ), ) result = self.network_client.create_network(body=body) network = net_common.DeletableNetwork(client=self.network_client, **result['network']) self.assertEqual(network.name, name) self.set_resource(name, network) return network def _list_networks(self, **kwargs): nets = self.network_client.list_networks(**kwargs) return nets['networks'] def _list_subnets(self, **kwargs): subnets = self.network_client.list_subnets(**kwargs) return subnets['subnets'] def _list_routers(self, **kwargs): routers = self.network_client.list_routers(**kwargs) return routers['routers'] def _list_ports(self, **kwargs): ports = self.network_client.list_ports(**kwargs) return ports['ports'] def _get_tenant_own_network_num(self, tenant_id): nets = self._list_networks(tenant_id=tenant_id) return len(nets) def _get_tenant_own_subnet_num(self, tenant_id): subnets = self._list_subnets(tenant_id=tenant_id) return len(subnets) def _get_tenant_own_port_num(self, tenant_id): ports = self._list_ports(tenant_id=tenant_id) return len(ports) def _create_subnet(self, network, namestart='subnet-smoke-', **kwargs): """ Create a subnet for the given network within the cidr block configured for tenant networks. """ def cidr_in_use(cidr, tenant_id): """ :return True if subnet with cidr already exist in tenant False else """ cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr) return len(cidr_in_use) != 0 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr) result = None # Repeatedly attempt subnet creation with sequential cidr # blocks until an unallocated block is found. for subnet_cidr in tenant_cidr.subnet( CONF.network.tenant_network_mask_bits): str_cidr = str(subnet_cidr) if cidr_in_use(str_cidr, tenant_id=network.tenant_id): continue body = dict( subnet=dict( name=data_utils.rand_name(namestart), ip_version=4, network_id=network.id, tenant_id=network.tenant_id, cidr=str_cidr, ), ) body['subnet'].update(kwargs) try: result = self.network_client.create_subnet(body=body) break except exc.NeutronClientException as e: is_overlapping_cidr = 'overlaps with another subnet' in str(e) if not is_overlapping_cidr: raise self.assertIsNotNone(result, 'Unable to allocate tenant network') subnet = net_common.DeletableSubnet(client=self.network_client, **result['subnet']) self.assertEqual(subnet.cidr, str_cidr) self.set_resource(data_utils.rand_name(namestart), subnet) return subnet def _create_port(self, network, namestart='port-quotatest-'): name = data_utils.rand_name(namestart) body = dict( port=dict(name=name, network_id=network.id, tenant_id=network.tenant_id)) result = self.network_client.create_port(body=body) self.assertIsNotNone(result, 'Unable to allocate port') port = net_common.DeletablePort(client=self.network_client, **result['port']) self.set_resource(name, port) return port def _get_server_port_id(self, server, ip_addr=None): ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr) self.assertEqual(len(ports), 1, "Unable to determine which port to target.") return ports[0]['id'] def _create_floating_ip(self, thing, external_network_id, port_id=None): if not port_id: port_id = self._get_server_port_id(thing) body = dict( floatingip=dict( floating_network_id=external_network_id, port_id=port_id, tenant_id=thing.tenant_id, ) ) result = self.network_client.create_floatingip(body=body) floating_ip = net_common.DeletableFloatingIp( client=self.network_client, **result['floatingip']) self.set_resource(data_utils.rand_name('floatingip-'), floating_ip) return floating_ip def _associate_floating_ip(self, floating_ip, server): port_id = self._get_server_port_id(server) floating_ip.update(port_id=port_id) self.assertEqual(port_id, floating_ip.port_id) return floating_ip def _disassociate_floating_ip(self, floating_ip): """ :param floating_ip: type DeletableFloatingIp """ floating_ip.update(port_id=None) self.assertIsNone(floating_ip.port_id) return floating_ip def _ping_ip_address(self, ip_address, should_succeed=True): cmd = ['ping', '-c1', '-w1', ip_address] def ping(): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait() return (proc.returncode == 0) == should_succeed return tempest.test.call_until_true( ping, CONF.compute.ping_timeout, 1) def _create_pool(self, lb_method, protocol, subnet_id): """Wrapper utility that returns a test pool.""" name = data_utils.rand_name('pool-') body = { "pool": { "protocol": protocol, "name": name, "subnet_id": subnet_id, "lb_method": lb_method } } resp = self.network_client.create_pool(body=body) pool = net_common.DeletablePool(client=self.network_client, **resp['pool']) self.assertEqual(pool['name'], name) self.set_resource(name, pool) return pool def _create_member(self, address, protocol_port, pool_id): """Wrapper utility that returns a test member.""" body = { "member": { "protocol_port": protocol_port, "pool_id": pool_id, "address": address } } resp = self.network_client.create_member(body) member = net_common.DeletableMember(client=self.network_client, **resp['member']) self.set_resource(data_utils.rand_name('member-'), member) return member def _create_vip(self, protocol, protocol_port, subnet_id, pool_id): """Wrapper utility that returns a test vip.""" name = data_utils.rand_name('vip-') body = { "vip": { "protocol": protocol, "name": name, "subnet_id": subnet_id, "pool_id": pool_id, "protocol_port": protocol_port } } resp = self.network_client.create_vip(body) vip = net_common.DeletableVip(client=self.network_client, **resp['vip']) self.assertEqual(vip['name'], name) self.set_resource(name, vip) return vip def _check_vm_connectivity(self, ip_address, username=None, private_key=None, should_connect=True): """ :param ip_address: server to test against :param username: server's ssh username :param private_key: server's ssh private key to be used :param should_connect: True/False indicates positive/negative test positive - attempt ping and ssh negative - attempt ping and fail if succeed :raises: AssertError if the result of the connectivity check does not match the value of the should_connect param """ if should_connect: msg = "Timed out waiting for %s to become reachable" % ip_address else: msg = "ip address %s is reachable" % ip_address self.assertTrue(self._ping_ip_address(ip_address, should_succeed=should_connect), msg=msg) if should_connect: # no need to check ssh for negative connectivity linux_client = self.get_remote_client(ip_address, username, private_key) linux_client.validate_authentication() def _check_remote_connectivity(self, source, dest, should_succeed=True): """ check ping server via source ssh connection :param source: RemoteClient: an ssh connection from which to ping :param dest: and IP to ping against :param should_succeed: boolean should ping succeed or not :returns: boolean -- should_succeed == ping :returns: ping is false if ping failed """ def ping_remote(): try: source.ping_host(dest) except exceptions.SSHExecCommandFailed: LOG.exception('Failed to ping host via ssh connection') return not should_succeed return should_succeed return tempest.test.call_until_true(ping_remote, CONF.compute.ping_timeout, 1) def _create_security_group_nova(self, client=None, namestart='secgroup-smoke-', tenant_id=None): if client is None: client = self.compute_client # Create security group sg_name = data_utils.rand_name(namestart) sg_desc = sg_name + " description" secgroup = client.security_groups.create(sg_name, sg_desc) self.assertEqual(secgroup.name, sg_name) self.assertEqual(secgroup.description, sg_desc) self.set_resource(sg_name, secgroup) # Add rules to the security group self._create_loginable_secgroup_rule_nova(client, secgroup.id) return secgroup def _create_security_group_neutron(self, tenant_id, client=None, namestart='secgroup-smoke-'): if client is None: client = self.network_client secgroup = self._create_empty_security_group(namestart=namestart, client=client, tenant_id=tenant_id) # Add rules to the security group rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup) for rule in rules: self.assertEqual(tenant_id, rule.tenant_id) self.assertEqual(secgroup.id, rule.security_group_id) return secgroup def _create_empty_security_group(self, tenant_id, client=None, namestart='secgroup-smoke-'): """Create a security group without rules. Default rules will be created: - IPv4 egress to any - IPv6 egress to any :param tenant_id: secgroup will be created in this tenant :returns: DeletableSecurityGroup -- containing the secgroup created """ if client is None: client = self.network_client sg_name = data_utils.rand_name(namestart) sg_desc = sg_name + " description" sg_dict = dict(name=sg_name, description=sg_desc) sg_dict['tenant_id'] = tenant_id body = dict(security_group=sg_dict) result = client.create_security_group(body=body) secgroup = net_common.DeletableSecurityGroup( client=client, **result['security_group'] ) self.assertEqual(secgroup.name, sg_name) self.assertEqual(tenant_id, secgroup.tenant_id) self.assertEqual(secgroup.description, sg_desc) self.set_resource(sg_name, secgroup) return secgroup def _default_security_group(self, tenant_id, client=None): """Get default secgroup for given tenant_id. :returns: DeletableSecurityGroup -- default secgroup for given tenant """ if client is None: client = self.network_client sgs = [ sg for sg in client.list_security_groups().values()[0] if sg['tenant_id'] == tenant_id and sg['name'] == 'default' ] msg = "No default security group for tenant %s." % (tenant_id) self.assertTrue(len(sgs) > 0, msg) if len(sgs) > 1: msg = "Found %d default security groups" % len(sgs) raise exc.NeutronClientNoUniqueMatch(msg=msg) return net_common.DeletableSecurityGroup(client=client, **sgs[0]) def _create_security_group_rule(self, client=None, secgroup=None, tenant_id=None, **kwargs): """Create a rule from a dictionary of rule parameters. Create a rule in a secgroup. if secgroup not defined will search for default secgroup in tenant_id. :param secgroup: type DeletableSecurityGroup. :param secgroup_id: search for secgroup by id default -- choose default secgroup for given tenant_id :param tenant_id: if secgroup not passed -- the tenant in which to search for default secgroup :param kwargs: a dictionary containing rule parameters: for example, to allow incoming ssh: rule = { direction: 'ingress' protocol:'tcp', port_range_min: 22, port_range_max: 22 } """ if client is None: client = self.network_client if secgroup is None: secgroup = self._default_security_group(tenant_id) ruleset = dict(security_group_id=secgroup.id, tenant_id=secgroup.tenant_id, ) ruleset.update(kwargs) body = dict(security_group_rule=dict(ruleset)) sg_rule = client.create_security_group_rule(body=body) sg_rule = net_common.DeletableSecurityGroupRule( client=client, **sg_rule['security_group_rule'] ) self.set_resource(sg_rule.id, sg_rule) self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id) self.assertEqual(secgroup.id, sg_rule.security_group_id) return sg_rule def _create_loginable_secgroup_rule_neutron(self, client=None, secgroup=None): """These rules are intended to permit inbound ssh and icmp traffic from all sources, so no group_id is provided. Setting a group_id would only permit traffic from ports belonging to the same security group. """ if client is None: client = self.network_client rules = [] rulesets = [ dict( # ssh protocol='tcp', port_range_min=22, port_range_max=22, ), dict( # ping protocol='icmp', ) ] for ruleset in rulesets: for r_direction in ['ingress', 'egress']: ruleset['direction'] = r_direction try: sg_rule = self._create_security_group_rule( client=client, secgroup=secgroup, **ruleset) except exc.NeutronClientException as ex: # if rule already exist - skip rule and continue if not (ex.status_code is 409 and 'Security group rule' ' already exists' in ex.message): raise ex else: self.assertEqual(r_direction, sg_rule.direction) rules.append(sg_rule) return rules def _ssh_to_server(self, server, private_key): ssh_login = CONF.compute.image_ssh_user return self.get_remote_client(server, username=ssh_login, private_key=private_key) def _show_quota_network(self, tenant_id): quota = self.network_client.show_quota(tenant_id) return quota['quota']['network'] def _show_quota_subnet(self, tenant_id): quota = self.network_client.show_quota(tenant_id) return quota['quota']['subnet'] def _show_quota_port(self, tenant_id): quota = self.network_client.show_quota(tenant_id) return quota['quota']['port'] def _get_router(self, tenant_id): """Retrieve a router for the given tenant id. If a public router has been configured, it will be returned. If a public router has not been configured, but a public network has, a tenant router will be created and returned that routes traffic to the public network. """ router_id = CONF.network.public_router_id network_id = CONF.network.public_network_id if router_id: result = self.network_client.show_router(router_id) return net_common.AttributeDict(**result['router']) elif network_id: router = self._create_router(tenant_id) router.add_gateway(network_id) return router else: raise Exception("Neither of 'public_router_id' or " "'public_network_id' has been defined.") def _create_router(self, tenant_id, namestart='router-smoke-'): name = data_utils.rand_name(namestart) body = dict( router=dict( name=name, admin_state_up=True, tenant_id=tenant_id, ), ) result = self.network_client.create_router(body=body) router = net_common.DeletableRouter(client=self.network_client, **result['router']) self.assertEqual(router.name, name) self.set_resource(name, router) return router def _create_networks(self, tenant_id=None): """Create a network with a subnet connected to a router. :returns: network, subnet, router """ if tenant_id is None: tenant_id = self.tenant_id network = self._create_network(tenant_id) router = self._get_router(tenant_id) subnet = self._create_subnet(network) subnet.add_to_router(router.id) return network, subnet, router class OrchestrationScenarioTest(OfficialClientTest): """ Base class for orchestration scenario tests """ @classmethod def setUpClass(cls): super(OrchestrationScenarioTest, cls).setUpClass() if not CONF.service_available.heat: raise cls.skipException("Heat support is required") @classmethod def credentials(cls): admin_creds = auth.get_default_credentials('identity_admin') creds = auth.get_default_credentials('user') admin_creds.tenant_name = creds.tenant_name return admin_creds def _load_template(self, base_file, file_name): filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)), file_name) with open(filepath) as f: return f.read() @classmethod def _stack_rand_name(cls): return data_utils.rand_name(cls.__name__ + '-') @classmethod def _get_default_network(cls): networks = cls.network_client.list_networks() for net in networks['networks']: if net['name'] == CONF.compute.fixed_network_name: return net tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_aggregates_basic_ops.py0000664000175000017500000001266612332757070027140 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common import tempest_fixtures as fixtures from tempest.common.utils import data_utils from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test LOG = logging.getLogger(__name__) class TestAggregatesBasicOps(manager.OfficialClientTest): """ Creates an aggregate within an availability zone Adds a host to the aggregate Checks aggregate details Updates aggregate's name Removes host from aggregate Deletes aggregate """ @classmethod def credentials(cls): return cls.admin_credentials() def _create_aggregate(self, **kwargs): aggregate = self.compute_client.aggregates.create(**kwargs) aggregate_name = kwargs['name'] availability_zone = kwargs['availability_zone'] self.assertEqual(aggregate.name, aggregate_name) self.assertEqual(aggregate.availability_zone, availability_zone) self.set_resource(aggregate.id, aggregate) LOG.debug("Aggregate %s created." % (aggregate.name)) return aggregate def _delete_aggregate(self, aggregate): self.compute_client.aggregates.delete(aggregate.id) self.remove_resource(aggregate.id) LOG.debug("Aggregate %s deleted. " % (aggregate.name)) def _get_host_name(self): hosts = self.compute_client.hosts.list() self.assertTrue(len(hosts) >= 1) hostname = hosts[0].host_name return hostname def _add_host(self, aggregate_name, host): aggregate = self.compute_client.aggregates.add_host(aggregate_name, host) self.assertIn(host, aggregate.hosts) LOG.debug("Host %s added to Aggregate %s." % (host, aggregate.name)) def _remove_host(self, aggregate_name, host): aggregate = self.compute_client.aggregates.remove_host(aggregate_name, host) self.assertNotIn(host, aggregate.hosts) LOG.debug("Host %s removed to Aggregate %s." % (host, aggregate.name)) def _check_aggregate_details(self, aggregate, aggregate_name, azone, hosts, metadata): aggregate = self.compute_client.aggregates.get(aggregate.id) self.assertEqual(aggregate_name, aggregate.name) self.assertEqual(azone, aggregate.availability_zone) self.assertEqual(aggregate.hosts, hosts) for meta_key in metadata.keys(): self.assertIn(meta_key, aggregate.metadata) self.assertEqual(metadata[meta_key], aggregate.metadata[meta_key]) LOG.debug("Aggregate %s details match." % aggregate.name) def _set_aggregate_metadata(self, aggregate, meta): aggregate = self.compute_client.aggregates.set_metadata(aggregate.id, meta) for key, value in meta.items(): self.assertEqual(meta[key], aggregate.metadata[key]) LOG.debug("Aggregate %s metadata updated successfully." % aggregate.name) def _update_aggregate(self, aggregate, aggregate_name, availability_zone): values = {} if aggregate_name: values.update({'name': aggregate_name}) if availability_zone: values.update({'availability_zone': availability_zone}) if values.keys(): aggregate = self.compute_client.aggregates.update(aggregate.id, values) for key, values in values.items(): self.assertEqual(getattr(aggregate, key), values) return aggregate @test.services('compute') def test_aggregate_basic_ops(self): self.useFixture(fixtures.LockFixture('availability_zone')) az = 'foo_zone' aggregate_name = data_utils.rand_name('aggregate-scenario') aggregate = self._create_aggregate(name=aggregate_name, availability_zone=az) metadata = {'meta_key': 'meta_value'} self._set_aggregate_metadata(aggregate, metadata) host = self._get_host_name() self._add_host(aggregate, host) self._check_aggregate_details(aggregate, aggregate_name, az, [host], metadata) aggregate_name = data_utils.rand_name('renamed-aggregate-scenario') aggregate = self._update_aggregate(aggregate, aggregate_name, None) additional_metadata = {'foo': 'bar'} self._set_aggregate_metadata(aggregate, additional_metadata) metadata.update(additional_metadata) self._check_aggregate_details(aggregate, aggregate.name, az, [host], metadata) self._remove_host(aggregate, host) self._delete_aggregate(aggregate) tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/utils.py0000664000175000017500000001250512332757070023076 0ustar chuckchuck00000000000000# Copyright 2013 Hewlett-Packard, Ltd. # # 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 json import re import string import unicodedata import testscenarios import testtools from tempest import auth from tempest import clients from tempest.common.utils import misc from tempest import config CONF = config.CONF @misc.singleton class ImageUtils(object): default_ssh_user = 'root' def __init__(self): # Load configuration items self.ssh_users = json.loads(CONF.input_scenario.ssh_user_regex) self.non_ssh_image_pattern = \ CONF.input_scenario.non_ssh_image_regex # Setup clients ocm = clients.OfficialClientManager( auth.get_default_credentials('user')) self.client = ocm.compute_client def ssh_user(self, image_id): _image = self.client.images.get(image_id) for regex, user in self.ssh_users: # First match wins if re.match(regex, _image.name) is not None: return user else: return self.default_ssh_user def _is_sshable_image(self, image): return not re.search(pattern=self.non_ssh_image_pattern, string=str(image.name)) def is_sshable_image(self, image_id): _image = self.client.images.get(image_id) return self._is_sshable_image(_image) def _is_flavor_enough(self, flavor, image): return image.minDisk <= flavor.disk def is_flavor_enough(self, flavor_id, image_id): _image = self.client.images.get(image_id) _flavor = self.client.flavors.get(flavor_id) return self._is_flavor_enough(_flavor, _image) @misc.singleton class InputScenarioUtils(object): """ Example usage: import testscenarios (...) load_tests = testscenarios.load_tests_apply_scenarios class TestInputScenario(manager.OfficialClientTest): scenario_utils = utils.InputScenarioUtils() scenario_flavor = scenario_utils.scenario_flavors scenario_image = scenario_utils.scenario_images scenarios = testscenarios.multiply_scenarios(scenario_image, scenario_flavor) def test_create_server_metadata(self): name = rand_name('instance') _ = self.compute_client.servers.create(name=name, flavor=self.flavor_ref, image=self.image_ref) """ validchars = "-_.{ascii}{digit}".format(ascii=string.ascii_letters, digit=string.digits) def __init__(self): ocm = clients.OfficialClientManager( auth.get_default_credentials('user', fill_in=False)) self.client = ocm.compute_client self.image_pattern = CONF.input_scenario.image_regex self.flavor_pattern = CONF.input_scenario.flavor_regex def _normalize_name(self, name): nname = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore') nname = ''.join(c for c in nname if c in self.validchars) return nname @property def scenario_images(self): """ :return: a scenario with name and uuid of images """ if not CONF.service_available.glance: return [] if not hasattr(self, '_scenario_images'): images = self.client.images.list(detailed=False) self._scenario_images = [ (self._normalize_name(i.name), dict(image_ref=i.id)) for i in images if re.search(self.image_pattern, str(i.name)) ] return self._scenario_images @property def scenario_flavors(self): """ :return: a scenario with name and uuid of flavors """ if not hasattr(self, '_scenario_flavors'): flavors = self.client.flavors.list(detailed=False) self._scenario_flavors = [ (self._normalize_name(f.name), dict(flavor_ref=f.id)) for f in flavors if re.search(self.flavor_pattern, str(f.name)) ] return self._scenario_flavors def load_tests_input_scenario_utils(*args): """ Wrapper for testscenarios to set the scenarios to avoid running a getattr on the CONF object at import. """ if getattr(args[0], 'suiteClass', None) is not None: loader, standard_tests, pattern = args else: standard_tests, module, loader = args scenario_utils = InputScenarioUtils() scenario_flavor = scenario_utils.scenario_flavors scenario_image = scenario_utils.scenario_images for test in testtools.iterate_tests(standard_tests): setattr(test, 'scenarios', testscenarios.multiply_scenarios( scenario_image, scenario_flavor)) return testscenarios.load_tests_apply_scenarios(*args) tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_server_advanced_ops.py0000664000175000017500000000673312332757070027017 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class TestServerAdvancedOps(manager.OfficialClientTest): """ This test case stresses some advanced server instance operations: * Resizing an instance * Sequence suspend resume """ @classmethod def setUpClass(cls): cls.set_network_resources() super(TestServerAdvancedOps, cls).setUpClass() if CONF.compute.flavor_ref_alt == CONF.compute.flavor_ref: msg = "Skipping test - flavor_ref and flavor_ref_alt are identical" raise cls.skipException(msg) @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize is not available.') @test.services('compute') def test_resize_server_confirm(self): # We create an instance for use in this test instance = self.create_server() instance_id = instance.id resize_flavor = CONF.compute.flavor_ref_alt LOG.debug("Resizing instance %s from flavor %s to flavor %s", instance.id, instance.flavor, resize_flavor) instance.resize(resize_flavor) self.status_timeout(self.compute_client.servers, instance_id, 'VERIFY_RESIZE') LOG.debug("Confirming resize of instance %s", instance_id) instance.confirm_resize() self.status_timeout( self.compute_client.servers, instance_id, 'ACTIVE') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.services('compute') def test_server_sequence_suspend_resume(self): # We create an instance for use in this test instance = self.create_server() instance_id = instance.id LOG.debug("Suspending instance %s. Current status: %s", instance_id, instance.status) instance.suspend() self.status_timeout(self.compute_client.servers, instance_id, 'SUSPENDED') LOG.debug("Resuming instance %s. Current status: %s", instance_id, instance.status) instance.resume() self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE') LOG.debug("Suspending instance %s. Current status: %s", instance_id, instance.status) instance.suspend() self.status_timeout(self.compute_client.servers, instance_id, 'SUSPENDED') LOG.debug("Resuming instance %s. Current status: %s", instance_id, instance.status) instance.resume() self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE') tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_volume_boot_pattern.py0000664000175000017500000001670212332757070027067 0ustar chuckchuck00000000000000# 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 tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = log.getLogger(__name__) class TestVolumeBootPattern(manager.OfficialClientTest): """ This test case attempts to reproduce the following steps: * Create in Cinder some bootable volume importing a Glance image * Boot an instance from the bootable volume * Write content to the volume * Delete an instance and Boot a new instance from the volume * Check written content in the instance * Create a volume snapshot while the instance is running * Boot an additional instance from the new snapshot based volume * Check written content in the instance booted from snapshot """ @classmethod def setUpClass(cls): super(TestVolumeBootPattern, cls).setUpClass() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") def _create_volume_from_image(self): img_uuid = CONF.compute.image_ref vol_name = data_utils.rand_name('volume-origin') return self.create_volume(name=vol_name, imageRef=img_uuid) def _boot_instance_from_volume(self, vol_id, keypair): # NOTE(gfidente): the syntax for block_device_mapping is # dev_name=id:type:size:delete_on_terminate # where type needs to be "snap" if the server is booted # from a snapshot, size instead can be safely left empty bd_map = { 'vda': vol_id + ':::0' } create_kwargs = { 'block_device_mapping': bd_map, 'key_name': keypair.name } return self.create_server(image='', create_kwargs=create_kwargs) def _create_snapshot_from_volume(self, vol_id): volume_snapshots = self.volume_client.volume_snapshots snap_name = data_utils.rand_name('snapshot') snap = volume_snapshots.create(volume_id=vol_id, force=True, display_name=snap_name) self.set_resource(snap.id, snap) self.status_timeout(volume_snapshots, snap.id, 'available') return snap def _create_volume_from_snapshot(self, snap_id): vol_name = data_utils.rand_name('volume') return self.create_volume(name=vol_name, snapshot_id=snap_id) def _stop_instances(self, instances): # NOTE(gfidente): two loops so we do not wait for the status twice for i in instances: self.compute_client.servers.stop(i) for i in instances: self.status_timeout(self.compute_client.servers, i.id, 'SHUTOFF') def _detach_volumes(self, volumes): # NOTE(gfidente): two loops so we do not wait for the status twice for v in volumes: self.volume_client.volumes.detach(v) for v in volumes: self.status_timeout(self.volume_client.volumes, v.id, 'available') def _ssh_to_server(self, server, keypair): if CONF.compute.use_floatingip_for_ssh: floating_ip = self.compute_client.floating_ips.create() fip_name = data_utils.rand_name('scenario-fip') self.set_resource(fip_name, floating_ip) server.add_floating_ip(floating_ip) ip = floating_ip.ip else: network_name_for_ssh = CONF.compute.network_for_ssh ip = server.networks[network_name_for_ssh][0] try: return self.get_remote_client( ip, private_key=keypair.private_key) except Exception: LOG.exception('ssh to server failed') self._log_console_output() raise def _get_content(self, ssh_client): return ssh_client.exec_command('cat /tmp/text') def _write_text(self, ssh_client): text = data_utils.rand_name('text-') ssh_client.exec_command('echo "%s" > /tmp/text; sync' % (text)) return self._get_content(ssh_client) def _delete_server(self, server): self.compute_client.servers.delete(server) self.delete_timeout(self.compute_client.servers, server.id) def _check_content_of_written_file(self, ssh_client, expected): actual = self._get_content(ssh_client) self.assertEqual(expected, actual) @test.services('compute', 'volume', 'image') def test_volume_boot_pattern(self): keypair = self.create_keypair() self._create_loginable_secgroup_rule_nova() # create an instance from volume volume_origin = self._create_volume_from_image() instance_1st = self._boot_instance_from_volume(volume_origin.id, keypair) # write content to volume on instance ssh_client_for_instance_1st = self._ssh_to_server(instance_1st, keypair) text = self._write_text(ssh_client_for_instance_1st) # delete instance self._delete_server(instance_1st) # create a 2nd instance from volume instance_2nd = self._boot_instance_from_volume(volume_origin.id, keypair) # check the content of written file ssh_client_for_instance_2nd = self._ssh_to_server(instance_2nd, keypair) self._check_content_of_written_file(ssh_client_for_instance_2nd, text) # snapshot a volume snapshot = self._create_snapshot_from_volume(volume_origin.id) # create a 3rd instance from snapshot volume = self._create_volume_from_snapshot(snapshot.id) instance_from_snapshot = self._boot_instance_from_volume(volume.id, keypair) # check the content of written file ssh_client = self._ssh_to_server(instance_from_snapshot, keypair) self._check_content_of_written_file(ssh_client, text) # NOTE(gfidente): ensure resources are in clean state for # deletion operations to succeed self._stop_instances([instance_2nd, instance_from_snapshot]) self._detach_volumes([volume_origin, volume]) class TestVolumeBootPatternV2(TestVolumeBootPattern): def _boot_instance_from_volume(self, vol_id, keypair): bdms = [{'uuid': vol_id, 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': False}] create_kwargs = { 'block_device_mapping_v2': bdms, 'key_name': keypair.name } return self.create_server(image='', create_kwargs=create_kwargs) tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/__init__.py0000664000175000017500000000000012332757070023460 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_baremetal_basic_ops.py0000664000175000017500000001225112332757070026751 0ustar chuckchuck00000000000000# # Copyright 2014 Hewlett-Packard Development Company, L.P. # # 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 tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) # power/provision states as of icehouse class PowerStates(object): """Possible power states of an Ironic node.""" POWER_ON = 'power on' POWER_OFF = 'power off' REBOOT = 'rebooting' SUSPEND = 'suspended' class ProvisionStates(object): """Possible provision states of an Ironic node.""" NOSTATE = None INIT = 'initializing' ACTIVE = 'active' BUILDING = 'building' DEPLOYWAIT = 'wait call-back' DEPLOYING = 'deploying' DEPLOYFAIL = 'deploy failed' DEPLOYDONE = 'deploy complete' DELETING = 'deleting' DELETED = 'deleted' ERROR = 'error' class BaremetalBasicOptsPXESSH(manager.BaremetalScenarioTest): """ This smoke test tests the pxe_ssh Ironic driver. It follows this basic set of operations: * Creates a keypair * Boots an instance using the keypair * Monitors the associated Ironic node for power and expected state transitions * Validates Ironic node's driver_info has been properly updated * Validates Ironic node's port data has been properly updated * Verifies SSH connectivity using created keypair via fixed IP * Associates a floating ip * Verifies SSH connectivity using created keypair via floating IP * Deletes instance * Monitors the associated Ironic node for power and expected state transitions """ def add_keypair(self): self.keypair = self.create_keypair() def add_floating_ip(self): floating_ip = self.compute_client.floating_ips.create() self.instance.add_floating_ip(floating_ip) return floating_ip.ip def verify_connectivity(self, ip=None): if ip: dest = self.get_remote_client(ip) else: dest = self.get_remote_client(self.instance) dest.validate_authentication() def validate_driver_info(self): f_id = self.instance.flavor['id'] flavor_extra = self.compute_client.flavors.get(f_id).get_keys() driver_info = self.node.driver_info self.assertEqual(driver_info['pxe_deploy_kernel'], flavor_extra['baremetal:deploy_kernel_id']) self.assertEqual(driver_info['pxe_deploy_ramdisk'], flavor_extra['baremetal:deploy_ramdisk_id']) self.assertEqual(driver_info['pxe_image_source'], self.instance.image['id']) def validate_ports(self): for port in self.get_ports(self.node.uuid): n_port_id = port.extra['vif_port_id'] n_port = self.network_client.show_port(n_port_id)['port'] self.assertEqual(n_port['device_id'], self.instance.id) self.assertEqual(n_port['mac_address'], port.address) def boot_instance(self): create_kwargs = { 'key_name': self.keypair.id } self.instance = self.create_server( wait=False, create_kwargs=create_kwargs) self.set_resource('instance', self.instance) self.wait_node(self.instance.id) self.node = self.get_node(instance_id=self.instance.id) self.wait_power_state(self.node.uuid, PowerStates.POWER_ON) self.wait_provisioning_state( self.node.uuid, [ProvisionStates.DEPLOYWAIT, ProvisionStates.ACTIVE], timeout=15) self.wait_provisioning_state(self.node.uuid, ProvisionStates.ACTIVE, timeout=CONF.baremetal.active_timeout) self.status_timeout( self.compute_client.servers, self.instance.id, 'ACTIVE') self.node = self.get_node(instance_id=self.instance.id) self.instance = self.compute_client.servers.get(self.instance.id) def terminate_instance(self): self.instance.delete() self.remove_resource('instance') self.wait_power_state(self.node.uuid, PowerStates.POWER_OFF) self.wait_provisioning_state( self.node.uuid, ProvisionStates.NOSTATE, timeout=CONF.baremetal.unprovision_timeout) @test.services('baremetal', 'compute', 'image', 'network') def test_baremetal_server_ops(self): self.add_keypair() self.boot_instance() self.validate_driver_info() self.validate_ports() self.verify_connectivity() floating_ip = self.add_floating_ip() self.verify_connectivity(ip=floating_ip) self.terminate_instance() tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/README.rst0000664000175000017500000000303112332757070023045 0ustar chuckchuck00000000000000Tempest Field Guide to Scenario tests ===================================== What are these tests? --------------------- Scenario tests are "through path" tests of OpenStack function. Complicated setups where one part might depend on completion of a previous part. They ideally involve the integration between multiple OpenStack services to exercise the touch points between them. Any scenario test should have a real-life use case. An example would be: - "As operator I want to start with a blank environment": 1. upload a glance image 2. deploy a vm from it 3. ssh to the guest 4. create a snapshot of the vm Why are these tests in tempest? ------------------------------- This is one of tempests core purposes, testing the integration between projects. Scope of these tests -------------------- Scenario tests should use the official python client libraries for OpenStack, as they provide a more realistic approach in how people will interact with the services. Tests should be tagged with which services they exercise, as determined by which client libraries are used directly by the test. Example of a good test ---------------------- While we are looking for interaction of 2 or more services, be specific in your interactions. A giant "this is my data center" smoke test is hard to debug when it goes wrong. A flow of interactions between glance and nova, like in the introduction, is a good example. Especially if it involves a repeated interaction when a resource is setup, modified, detached, and then reused later again. tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_swift_basic_ops.py0000664000175000017500000001016312332757070026151 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class TestSwiftBasicOps(manager.OfficialClientTest): """ Test swift with the follow operations: * get swift stat. * create container. * upload a file to the created container. * list container's objects and assure that the uploaded file is present. * delete object from container. * list container's objects and assure that the deleted file is gone. * delete a container. * list containers and assure that the deleted container is gone. """ @classmethod def setUpClass(cls): cls.set_network_resources() super(TestSwiftBasicOps, cls).setUpClass() if not CONF.service_available.swift: skip_msg = ("%s skipped as swift is not available" % cls.__name__) raise cls.skipException(skip_msg) def _get_swift_stat(self): """get swift status for our user account.""" self.object_storage_client.get_account() LOG.debug('Swift status information obtained successfully') def _create_container(self, container_name=None): name = container_name or data_utils.rand_name( 'swift-scenario-container') self.object_storage_client.put_container(name) # look for the container to assure it is created self._list_and_check_container_objects(name) LOG.debug('Container %s created' % (name)) return name def _delete_container(self, container_name): self.object_storage_client.delete_container(container_name) LOG.debug('Container %s deleted' % (container_name)) def _upload_object_to_container(self, container_name, obj_name=None): obj_name = obj_name or data_utils.rand_name('swift-scenario-object') self.object_storage_client.put_object(container_name, obj_name, data_utils.rand_name('obj_data'), content_type='text/plain') return obj_name def _delete_object(self, container_name, filename): self.object_storage_client.delete_object(container_name, filename) self._list_and_check_container_objects(container_name, not_present_obj=[filename]) def _list_and_check_container_objects(self, container_name, present_obj=[], not_present_obj=[]): """ List objects for a given container and assert which are present and which are not. """ meta, response = self.object_storage_client.get_container( container_name) # create a list with file name only object_list = [obj['name'] for obj in response] if present_obj: for obj in present_obj: self.assertIn(obj, object_list) if not_present_obj: for obj in not_present_obj: self.assertNotIn(obj, object_list) @test.services('object_storage') def test_swift_basic_ops(self): self._get_swift_stat() container_name = self._create_container() obj_name = self._upload_object_to_container(container_name) self._list_and_check_container_objects(container_name, [obj_name]) self._delete_object(container_name, obj_name) self._delete_container(container_name) tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_snapshot_pattern.py0000664000175000017500000000714512332757070026375 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import config from tempest.openstack.common import log from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = log.getLogger(__name__) class TestSnapshotPattern(manager.OfficialClientTest): """ This test is for snapshotting an instance and booting with it. The following is the scenario outline: * boot a instance and create a timestamp file in it * snapshot the instance * boot a second instance from the snapshot * check the existence of the timestamp file in the second instance """ def _boot_image(self, image_id): create_kwargs = { 'key_name': self.keypair.name } return self.create_server(image=image_id, create_kwargs=create_kwargs) def _add_keypair(self): self.keypair = self.create_keypair() def _ssh_to_server(self, server_or_ip): try: return self.get_remote_client(server_or_ip) except Exception: LOG.exception() self._log_console_output() def _write_timestamp(self, server_or_ip): ssh_client = self._ssh_to_server(server_or_ip) ssh_client.exec_command('date > /tmp/timestamp; sync') self.timestamp = ssh_client.exec_command('cat /tmp/timestamp') def _check_timestamp(self, server_or_ip): ssh_client = self._ssh_to_server(server_or_ip) got_timestamp = ssh_client.exec_command('cat /tmp/timestamp') self.assertEqual(self.timestamp, got_timestamp) def _create_floating_ip(self): floating_ip = self.compute_client.floating_ips.create() self.addCleanup(floating_ip.delete) return floating_ip def _set_floating_ip_to_server(self, server, floating_ip): server.add_floating_ip(floating_ip) @test.services('compute', 'network', 'image') def test_snapshot_pattern(self): # prepare for booting a instance self._add_keypair() self._create_loginable_secgroup_rule_nova() # boot a instance and create a timestamp file in it server = self._boot_image(CONF.compute.image_ref) if CONF.compute.use_floatingip_for_ssh: fip_for_server = self._create_floating_ip() self._set_floating_ip_to_server(server, fip_for_server) self._write_timestamp(fip_for_server.ip) else: self._write_timestamp(server) # snapshot the instance snapshot_image = self.create_server_snapshot(server=server) # boot a second instance from the snapshot server_from_snapshot = self._boot_image(snapshot_image.id) # check the existence of the timestamp file in the second instance if CONF.compute.use_floatingip_for_ssh: fip_for_snapshot = self._create_floating_ip() self._set_floating_ip_to_server(server_from_snapshot, fip_for_snapshot) self._check_timestamp(fip_for_snapshot.ip) else: self._check_timestamp(server_from_snapshot) tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_minimum_basic.py0000664000175000017500000001065712332757070025617 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common import debug from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class TestMinimumBasicScenario(manager.OfficialClientTest): """ This is a basic minimum scenario test. This test below: * across the multiple components * as a regular user * with and without optional parameters * check command outputs """ def _wait_for_server_status(self, status): server_id = self.server.id self.status_timeout( self.compute_client.servers, server_id, status) def nova_keypair_add(self): self.keypair = self.create_keypair() def nova_boot(self): create_kwargs = {'key_name': self.keypair.name} self.server = self.create_server(image=self.image, create_kwargs=create_kwargs) def nova_list(self): servers = self.compute_client.servers.list() LOG.debug("server_list:%s" % servers) self.assertIn(self.server, servers) def nova_show(self): got_server = self.compute_client.servers.get(self.server) LOG.debug("got server:%s" % got_server) self.assertEqual(self.server, got_server) def cinder_create(self): self.volume = self.create_volume() def cinder_list(self): volumes = self.volume_client.volumes.list() self.assertIn(self.volume, volumes) def cinder_show(self): volume = self.volume_client.volumes.get(self.volume.id) self.assertEqual(self.volume, volume) def nova_volume_attach(self): attach_volume_client = self.compute_client.volumes.create_server_volume volume = attach_volume_client(self.server.id, self.volume.id, '/dev/vdb') self.assertEqual(self.volume.id, volume.id) self.wait_for_volume_status('in-use') def nova_reboot(self): self.server.reboot() self._wait_for_server_status('ACTIVE') def nova_floating_ip_create(self): self.floating_ip = self.compute_client.floating_ips.create() self.addCleanup(self.floating_ip.delete) def nova_floating_ip_add(self): self.server.add_floating_ip(self.floating_ip) def ssh_to_server(self): try: self.linux_client = self.get_remote_client(self.floating_ip.ip) self.linux_client.validate_authentication() except Exception: LOG.exception('ssh to server failed') self._log_console_output() debug.log_net_debug() raise def check_partitions(self): partitions = self.linux_client.get_partitions() self.assertEqual(1, partitions.count('vdb')) def nova_volume_detach(self): detach_volume_client = self.compute_client.volumes.delete_server_volume detach_volume_client(self.server.id, self.volume.id) self.wait_for_volume_status('available') volume = self.volume_client.volumes.get(self.volume.id) self.assertEqual('available', volume.status) @test.services('compute', 'volume', 'image', 'network') def test_minimum_basic_scenario(self): self.glance_image_create() self.nova_keypair_add() self.nova_boot() self.nova_list() self.nova_show() self.cinder_create() self.cinder_list() self.cinder_show() self.nova_volume_attach() self.addCleanup(self.nova_volume_detach) self.cinder_show() self.nova_floating_ip_create() self.nova_floating_ip_add() self._create_loginable_secgroup_rule_nova() self.ssh_to_server() self.nova_reboot() self.ssh_to_server() self.check_partitions() tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_stamp_pattern.py0000664000175000017500000001744212332757070025663 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time from cinderclient import exceptions as cinder_exceptions from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging from tempest.scenario import manager import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class TestStampPattern(manager.OfficialClientTest): """ This test is for snapshotting an instance/volume and attaching the volume created from snapshot to the instance booted from snapshot. The following is the scenario outline: 1. Boot an instance "instance1" 2. Create a volume "volume1" 3. Attach volume1 to instance1 4. Create a filesystem on volume1 5. Mount volume1 6. Create a file which timestamp is written in volume1 7. Unmount volume1 8. Detach volume1 from instance1 9. Get a snapshot "snapshot_from_volume" of volume1 10. Get a snapshot "snapshot_from_instance" of instance1 11. Boot an instance "instance2" from snapshot_from_instance 12. Create a volume "volume2" from snapshot_from_volume 13. Attach volume2 to instance2 14. Check the existence of a file which created at 6. in volume2 """ @classmethod def setUpClass(cls): super(TestStampPattern, cls).setUpClass() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") def _wait_for_volume_snapshot_status(self, volume_snapshot, status): self.status_timeout(self.volume_client.volume_snapshots, volume_snapshot.id, status) def _boot_image(self, image_id): create_kwargs = { 'key_name': self.keypair.name } return self.create_server(image=image_id, create_kwargs=create_kwargs) def _add_keypair(self): self.keypair = self.create_keypair() def _create_floating_ip(self): floating_ip = self.compute_client.floating_ips.create() self.addCleanup(floating_ip.delete) return floating_ip def _add_floating_ip(self, server, floating_ip): server.add_floating_ip(floating_ip) def _ssh_to_server(self, server_or_ip): return self.get_remote_client(server_or_ip) def _create_volume_snapshot(self, volume): snapshot_name = data_utils.rand_name('scenario-snapshot-') volume_snapshots = self.volume_client.volume_snapshots snapshot = volume_snapshots.create( volume.id, display_name=snapshot_name) def cleaner(): volume_snapshots.delete(snapshot) try: while volume_snapshots.get(snapshot.id): time.sleep(1) except cinder_exceptions.NotFound: pass self.addCleanup(cleaner) self._wait_for_volume_status(volume, 'available') self._wait_for_volume_snapshot_status(snapshot, 'available') self.assertEqual(snapshot_name, snapshot.display_name) return snapshot def _wait_for_volume_status(self, volume, status): self.status_timeout( self.volume_client.volumes, volume.id, status) def _create_volume(self, snapshot_id=None): return self.create_volume(snapshot_id=snapshot_id) def _attach_volume(self, server, volume): attach_volume_client = self.compute_client.volumes.create_server_volume attached_volume = attach_volume_client(server.id, volume.id, '/dev/vdb') self.assertEqual(volume.id, attached_volume.id) self._wait_for_volume_status(attached_volume, 'in-use') def _detach_volume(self, server, volume): detach_volume_client = self.compute_client.volumes.delete_server_volume detach_volume_client(server.id, volume.id) self._wait_for_volume_status(volume, 'available') def _wait_for_volume_available_on_the_system(self, server_or_ip): ssh = self.get_remote_client(server_or_ip) def _func(): part = ssh.get_partitions() LOG.debug("Partitions:%s" % part) return 'vdb' in part if not tempest.test.call_until_true(_func, CONF.compute.build_timeout, CONF.compute.build_interval): raise exceptions.TimeoutException def _create_timestamp(self, server_or_ip): ssh_client = self._ssh_to_server(server_or_ip) ssh_client.exec_command('sudo /usr/sbin/mkfs.ext4 /dev/vdb') ssh_client.exec_command('sudo mount /dev/vdb /mnt') ssh_client.exec_command('sudo sh -c "date > /mnt/timestamp;sync"') self.timestamp = ssh_client.exec_command('sudo cat /mnt/timestamp') ssh_client.exec_command('sudo umount /mnt') def _check_timestamp(self, server_or_ip): ssh_client = self._ssh_to_server(server_or_ip) ssh_client.exec_command('sudo mount /dev/vdb /mnt') got_timestamp = ssh_client.exec_command('sudo cat /mnt/timestamp') self.assertEqual(self.timestamp, got_timestamp) @tempest.test.skip_because(bug="1205344") @tempest.test.services('compute', 'network', 'volume', 'image') def test_stamp_pattern(self): # prepare for booting a instance self._add_keypair() self._create_loginable_secgroup_rule_nova() # boot an instance and create a timestamp file in it volume = self._create_volume() server = self._boot_image(CONF.compute.image_ref) # create and add floating IP to server1 if CONF.compute.use_floatingip_for_ssh: floating_ip_for_server = self._create_floating_ip() self._add_floating_ip(server, floating_ip_for_server) ip_for_server = floating_ip_for_server.ip else: ip_for_server = server self._attach_volume(server, volume) self._wait_for_volume_available_on_the_system(ip_for_server) self._create_timestamp(ip_for_server) self._detach_volume(server, volume) # snapshot the volume volume_snapshot = self._create_volume_snapshot(volume) # snapshot the instance snapshot_image = self.create_server_snapshot(server=server) # create second volume from the snapshot(volume2) volume_from_snapshot = self._create_volume( snapshot_id=volume_snapshot.id) # boot second instance from the snapshot(instance2) server_from_snapshot = self._boot_image(snapshot_image.id) # create and add floating IP to server_from_snapshot if CONF.compute.use_floatingip_for_ssh: floating_ip_for_snapshot = self._create_floating_ip() self._add_floating_ip(server_from_snapshot, floating_ip_for_snapshot) ip_for_snapshot = floating_ip_for_snapshot.ip else: ip_for_snapshot = server_from_snapshot # attach volume2 to instance2 self._attach_volume(server_from_snapshot, volume_from_snapshot) self._wait_for_volume_available_on_the_system(ip_for_snapshot) # check the existence of the timestamp file in the volume2 self._check_timestamp(ip_for_snapshot) tempest-2014.1.dev4108.gf22b6cc/tempest/scenario/test_network_basic_ops.py0000664000175000017500000004136312332757070026514 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import re from tempest.api.network import common as net_common from tempest.common import debug from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest.scenario import manager from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) Floating_IP_tuple = collections.namedtuple('Floating_IP_tuple', ['floating_ip', 'server']) class TestNetworkBasicOps(manager.NetworkScenarioTest): """ This smoke test suite assumes that Nova has been configured to boot VM's with Neutron-managed networking, and attempts to verify network connectivity as follows: There are presumed to be two types of networks: tenant and public. A tenant network may or may not be reachable from the Tempest host. A public network is assumed to be reachable from the Tempest host, and it should be possible to associate a public ('floating') IP address with a tenant ('fixed') IP address to facilitate external connectivity to a potentially unroutable tenant IP address. This test suite can be configured to test network connectivity to a VM via a tenant network, a public network, or both. If both networking types are to be evaluated, tests that need to be executed remotely on the VM (via ssh) will only be run against one of the networks (to minimize test execution time). Determine which types of networks to test as follows: * Configure tenant network checks (via the 'tenant_networks_reachable' key) if the Tempest host should have direct connectivity to tenant networks. This is likely to be the case if Tempest is running on the same host as a single-node devstack installation with IP namespaces disabled. * Configure checks for a public network if a public network has been configured prior to the test suite being run and if the Tempest host should have connectivity to that public network. Checking connectivity for a public network requires that a value be provided for 'public_network_id'. A value can optionally be provided for 'public_router_id' if tenants will use a shared router to access a public network (as is likely to be the case when IP namespaces are not enabled). If a value is not provided for 'public_router_id', a router will be created for each tenant and use the network identified by 'public_network_id' as its gateway. """ @classmethod def check_preconditions(cls): super(TestNetworkBasicOps, cls).check_preconditions() if not (CONF.network.tenant_networks_reachable or CONF.network.public_network_id): msg = ('Either tenant_networks_reachable must be "true", or ' 'public_network_id must be defined.') cls.enabled = False raise cls.skipException(msg) @classmethod def setUpClass(cls): super(TestNetworkBasicOps, cls).setUpClass() for ext in ['router', 'security-group']: if not test.is_extension_enabled(ext, 'network'): msg = "%s extension not enabled." % ext raise cls.skipException(msg) cls.check_preconditions() def cleanup_wrapper(self, resource): self.cleanup_resource(resource, self.__class__.__name__) def setUp(self): super(TestNetworkBasicOps, self).setUp() self.security_group = \ self._create_security_group_neutron(tenant_id=self.tenant_id) self.addCleanup(self.cleanup_wrapper, self.security_group) self.network, self.subnet, self.router = self._create_networks() for r in [self.network, self.router, self.subnet]: self.addCleanup(self.cleanup_wrapper, r) self.check_networks() self.servers = {} name = data_utils.rand_name('server-smoke') serv_dict = self._create_server(name, self.network) self.servers[serv_dict['server']] = serv_dict['keypair'] self._check_tenant_network_connectivity() self._create_and_associate_floating_ips() def check_networks(self): """ Checks that we see the newly created network/subnet/router via checking the result of list_[networks,routers,subnets] """ seen_nets = self._list_networks() seen_names = [n['name'] for n in seen_nets] seen_ids = [n['id'] for n in seen_nets] self.assertIn(self.network.name, seen_names) self.assertIn(self.network.id, seen_ids) seen_subnets = self._list_subnets() seen_net_ids = [n['network_id'] for n in seen_subnets] seen_subnet_ids = [n['id'] for n in seen_subnets] self.assertIn(self.network.id, seen_net_ids) self.assertIn(self.subnet.id, seen_subnet_ids) seen_routers = self._list_routers() seen_router_ids = [n['id'] for n in seen_routers] seen_router_names = [n['name'] for n in seen_routers] self.assertIn(self.router.name, seen_router_names) self.assertIn(self.router.id, seen_router_ids) def _create_server(self, name, network): keypair = self.create_keypair(name='keypair-%s' % name) self.addCleanup(self.cleanup_wrapper, keypair) security_groups = [self.security_group.name] create_kwargs = { 'nics': [ {'net-id': network.id}, ], 'key_name': keypair.name, 'security_groups': security_groups, } server = self.create_server(name=name, create_kwargs=create_kwargs) self.addCleanup(self.cleanup_wrapper, server) return dict(server=server, keypair=keypair) def _check_tenant_network_connectivity(self): if not CONF.network.tenant_networks_reachable: msg = 'Tenant networks not configured to be reachable.' LOG.info(msg) return # The target login is assumed to have been configured for # key-based authentication by cloud-init. ssh_login = CONF.compute.image_ssh_user try: for server, key in self.servers.iteritems(): for net_name, ip_addresses in server.networks.iteritems(): for ip_address in ip_addresses: self._check_vm_connectivity(ip_address, ssh_login, key.private_key) except Exception: LOG.exception('Tenant connectivity check failed') self._log_console_output(servers=self.servers.keys()) debug.log_net_debug() raise def _create_and_associate_floating_ips(self): public_network_id = CONF.network.public_network_id for server in self.servers.keys(): floating_ip = self._create_floating_ip(server, public_network_id) self.floating_ip_tuple = Floating_IP_tuple(floating_ip, server) self.addCleanup(self.cleanup_wrapper, floating_ip) def _check_public_network_connectivity(self, should_connect=True, msg=None): # The target login is assumed to have been configured for # key-based authentication by cloud-init. ssh_login = CONF.compute.image_ssh_user LOG.debug('checking network connections') floating_ip, server = self.floating_ip_tuple ip_address = floating_ip.floating_ip_address private_key = None if should_connect: private_key = self.servers[server].private_key try: self._check_vm_connectivity(ip_address, ssh_login, private_key, should_connect=should_connect) except Exception: ex_msg = 'Public network connectivity check failed' if msg: ex_msg += ": " + msg LOG.exception(ex_msg) self._log_console_output(servers=self.servers.keys()) debug.log_net_debug() raise def _disassociate_floating_ips(self): floating_ip, server = self.floating_ip_tuple self._disassociate_floating_ip(floating_ip) self.floating_ip_tuple = Floating_IP_tuple( floating_ip, None) def _reassociate_floating_ips(self): floating_ip, server = self.floating_ip_tuple name = data_utils.rand_name('new_server-smoke-') # create a new server for the floating ip serv_dict = self._create_server(name, self.network) self.servers[serv_dict['server']] = serv_dict['keypair'] self._associate_floating_ip(floating_ip, serv_dict['server']) self.floating_ip_tuple = Floating_IP_tuple( floating_ip, serv_dict['server']) def _create_new_network(self): self.new_net = self._create_network(self.tenant_id) self.addCleanup(self.cleanup_wrapper, self.new_net) self.new_subnet = self._create_subnet( network=self.new_net, gateway_ip=None) self.addCleanup(self.cleanup_wrapper, self.new_subnet) def _hotplug_server(self): old_floating_ip, server = self.floating_ip_tuple ip_address = old_floating_ip.floating_ip_address private_key = self.servers[server].private_key ssh_client = self.get_remote_client(ip_address, private_key=private_key) old_nic_list = self._get_server_nics(ssh_client) # get a port from a list of one item port_list = self._list_ports(device_id=server.id) self.assertEqual(1, len(port_list)) old_port = port_list[0] self.compute_client.servers.interface_attach(server=server, net_id=self.new_net.id, port_id=None, fixed_ip=None) # move server to the head of the cleanup list self.addCleanup(self.cleanup_wrapper, server) def check_ports(): port_list = [port for port in self._list_ports(device_id=server.id) if port != old_port] return len(port_list) == 1 test.call_until_true(check_ports, 60, 1) new_port_list = [p for p in self._list_ports(device_id=server.id) if p != old_port] self.assertEqual(1, len(new_port_list)) new_port = new_port_list[0] new_port = net_common.DeletablePort(client=self.network_client, **new_port) new_nic_list = self._get_server_nics(ssh_client) diff_list = [n for n in new_nic_list if n not in old_nic_list] self.assertEqual(1, len(diff_list)) num, new_nic = diff_list[0] ssh_client.assign_static_ip(nic=new_nic, addr=new_port.fixed_ips[0]['ip_address']) ssh_client.turn_nic_on(nic=new_nic) def _get_server_nics(self, ssh_client): reg = re.compile(r'(?P\d+): (?P\w+):') ipatxt = ssh_client.get_ip_list() return reg.findall(ipatxt) def _check_network_internal_connectivity(self, network): """ via ssh check VM internal connectivity: - ping internal gateway and DHCP port, implying in-tenant connectivity pinging both, because L3 and DHCP agents might be on different nodes """ floating_ip, server = self.floating_ip_tuple # get internal ports' ips: # get all network ports in the new network internal_ips = (p['fixed_ips'][0]['ip_address'] for p in self._list_ports(tenant_id=server.tenant_id, network_id=network.id) if p['device_owner'].startswith('network')) self._check_server_connectivity(floating_ip, internal_ips) def _check_network_external_connectivity(self): """ ping public network default gateway to imply external connectivity """ if not CONF.network.public_network_id: msg = 'public network not defined.' LOG.info(msg) return subnet = self.network_client.list_subnets( network_id=CONF.network.public_network_id)['subnets'] self.assertEqual(1, len(subnet), "Found %d subnets" % len(subnet)) external_ips = [subnet[0]['gateway_ip']] self._check_server_connectivity(self.floating_ip_tuple.floating_ip, external_ips) def _check_server_connectivity(self, floating_ip, address_list): ip_address = floating_ip.floating_ip_address private_key = self.servers[self.floating_ip_tuple.server].private_key ssh_source = self._ssh_to_server(ip_address, private_key) for remote_ip in address_list: try: self.assertTrue(self._check_remote_connectivity(ssh_source, remote_ip), "Timed out waiting for %s to become " "reachable" % remote_ip) except Exception: LOG.exception("Unable to access {dest} via ssh to " "floating-ip {src}".format(dest=remote_ip, src=floating_ip)) debug.log_ip_ns() raise @test.attr(type='smoke') @test.services('compute', 'network') def test_network_basic_ops(self): """ For a freshly-booted VM with an IP address ("port") on a given network: - the Tempest host can ping the IP address. This implies, but does not guarantee (see the ssh check that follows), that the VM has been assigned the correct IP address and has connectivity to the Tempest host. - the Tempest host can perform key-based authentication to an ssh server hosted at the IP address. This check guarantees that the IP address is associated with the target VM. - the Tempest host can ssh into the VM via the IP address and successfully execute the following: - ping an external IP address, implying external connectivity. - ping an external hostname, implying that dns is correctly configured. - ping an internal IP address, implying connectivity to another VM on the same network. - detach the floating-ip from the VM and verify that it becomes unreachable - associate detached floating ip to a new VM and verify connectivity. VMs are created with unique keypair so connectivity also asserts that floating IP is associated with the new VM instead of the old one """ self._check_public_network_connectivity(should_connect=True) self._check_network_internal_connectivity(network=self.network) self._check_network_external_connectivity() self._disassociate_floating_ips() self._check_public_network_connectivity(should_connect=False, msg="after disassociate " "floating ip") self._reassociate_floating_ips() self._check_public_network_connectivity(should_connect=True, msg="after re-associate " "floating ip") @test.attr(type='smoke') @test.services('compute', 'network') def test_hotplug_nic(self): """ 1. create a new network, with no gateway (to prevent overwriting VM's gateway) 2. connect VM to new network 3. set static ip and bring new nic up 4. check VM can ping new network dhcp port """ self._check_public_network_connectivity(should_connect=True) self._create_new_network() self._hotplug_server() self._check_network_internal_connectivity(network=self.new_net) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/0000775000175000017500000000000012332757136020723 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_wrappers.py0000664000175000017500000000754012332757070024202 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 shutil import StringIO import subprocess import tempfile from tempest.tests import base DEVNULL = open(os.devnull, 'wb') class TestWrappers(base.TestCase): def setUp(self): super(TestWrappers, self).setUp() # Setup test dirs self.directory = tempfile.mkdtemp(prefix='tempest-unit') self.addCleanup(shutil.rmtree, self.directory) self.test_dir = os.path.join(self.directory, 'tests') os.mkdir(self.test_dir) # Setup Test files self.testr_conf_file = os.path.join(self.directory, '.testr.conf') self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg') self.subunit_trace = os.path.join(self.directory, 'subunit-trace.py') self.passing_file = os.path.join(self.test_dir, 'test_passing.py') self.failing_file = os.path.join(self.test_dir, 'test_failing.py') self.init_file = os.path.join(self.test_dir, '__init__.py') self.setup_py = os.path.join(self.directory, 'setup.py') shutil.copy('tempest/tests/files/testr-conf', self.testr_conf_file) shutil.copy('tempest/tests/files/passing-tests', self.passing_file) shutil.copy('tempest/tests/files/failing-tests', self.failing_file) shutil.copy('setup.py', self.setup_py) shutil.copy('tempest/tests/files/setup.cfg', self.setup_cfg_file) shutil.copy('tempest/tests/files/__init__.py', self.init_file) shutil.copy('tools/subunit-trace.py', self.subunit_trace) # copy over the pretty_tox scripts shutil.copy('tools/pretty_tox.sh', os.path.join(self.directory, 'pretty_tox.sh')) shutil.copy('tools/pretty_tox_serial.sh', os.path.join(self.directory, 'pretty_tox_serial.sh')) self.stdout = StringIO.StringIO() self.stderr = StringIO.StringIO() # Change directory, run wrapper and check result self.addCleanup(os.chdir, os.path.abspath(os.curdir)) os.chdir(self.directory) def assertRunExit(self, cmd, expected): p = subprocess.Popen( "bash %s" % cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # wait in the general case is dangerous, however the amount of # data coming back on those pipes is small enough it shouldn't be # a problem. p.wait() self.assertEqual( p.returncode, expected, "Stdout: %s; Stderr: %s" % (p.stdout, p.stderr)) def test_pretty_tox(self): # Git init is required for the pbr testr command. pbr requires a git # version or an sdist to work. so make the test directory a git repo # too. subprocess.call(['git', 'init'], stderr=DEVNULL) self.assertRunExit('pretty_tox.sh tests.passing', 0) def test_pretty_tox_fails(self): # Git init is required for the pbr testr command. pbr requires a git # version or an sdist to work. so make the test directory a git repo # too. subprocess.call(['git', 'init'], stderr=DEVNULL) self.assertRunExit('pretty_tox.sh', 1) def test_pretty_tox_serial(self): self.assertRunExit('pretty_tox_serial.sh tests.passing', 0) def test_pretty_tox_serial_fails(self): self.assertRunExit('pretty_tox_serial.sh', 1) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/0000775000175000017500000000000012332757136022213 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/test_debug.py0000664000175000017500000001250512332757070024712 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from tempest.common import debug from tempest import config from tempest.openstack.common.fixture import mockpatch from tempest import test from tempest.tests import base from tempest.tests import fake_config class TestDebug(base.TestCase): def setUp(self): super(TestDebug, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) common_pre = 'tempest.common.commands' self.ip_addr_raw_mock = self.patch(common_pre + '.ip_addr_raw') self.ip_route_raw_mock = self.patch(common_pre + '.ip_route_raw') self.iptables_raw_mock = self.patch(common_pre + '.iptables_raw') self.ip_ns_list_mock = self.patch(common_pre + '.ip_ns_list') self.ip_ns_addr_mock = self.patch(common_pre + '.ip_ns_addr') self.ip_ns_route_mock = self.patch(common_pre + '.ip_ns_route') self.iptables_ns_mock = self.patch(common_pre + '.iptables_ns') self.ovs_db_dump_mock = self.patch(common_pre + '.ovs_db_dump') self.log_mock = self.patch('tempest.common.debug.LOG') def test_log_ip_ns_debug_disabled(self): self.useFixture(mockpatch.PatchObject(test.CONF.debug, 'enable', False)) debug.log_ip_ns() self.assertFalse(self.ip_addr_raw_mock.called) self.assertFalse(self.log_mock.info.called) def test_log_ip_ns_debug_enabled(self): self.useFixture(mockpatch.PatchObject(test.CONF.debug, 'enable', True)) self.ip_ns_list_mock.return_value = [1, 2] debug.log_ip_ns() self.ip_addr_raw_mock.assert_called_with() self.assertTrue(self.log_mock.info.called) self.ip_route_raw_mock.assert_called_with() self.assertEqual(len(debug.TABLES), self.iptables_raw_mock.call_count) for table in debug.TABLES: self.assertIn(mock.call(table), self.iptables_raw_mock.call_args_list) self.ip_ns_list_mock.assert_called_with() self.assertEqual(len(self.ip_ns_list_mock.return_value), self.ip_ns_addr_mock.call_count) self.assertEqual(len(self.ip_ns_list_mock.return_value), self.ip_ns_route_mock.call_count) for ns in self.ip_ns_list_mock.return_value: self.assertIn(mock.call(ns), self.ip_ns_addr_mock.call_args_list) self.assertIn(mock.call(ns), self.ip_ns_route_mock.call_args_list) self.assertEqual(len(debug.TABLES) * len(self.ip_ns_list_mock.return_value), self.iptables_ns_mock.call_count) for ns in self.ip_ns_list_mock.return_value: for table in debug.TABLES: self.assertIn(mock.call(ns, table), self.iptables_ns_mock.call_args_list) def test_log_ovs_db_debug_disabled(self): self.useFixture(mockpatch.PatchObject(test.CONF.debug, 'enable', False)) self.useFixture(mockpatch.PatchObject(test.CONF.service_available, 'neutron', False)) debug.log_ovs_db() self.assertFalse(self.ovs_db_dump_mock.called) self.useFixture(mockpatch.PatchObject(test.CONF.debug, 'enable', True)) self.useFixture(mockpatch.PatchObject(test.CONF.service_available, 'neutron', False)) debug.log_ovs_db() self.assertFalse(self.ovs_db_dump_mock.called) self.useFixture(mockpatch.PatchObject(test.CONF.debug, 'enable', False)) self.useFixture(mockpatch.PatchObject(test.CONF.service_available, 'neutron', True)) debug.log_ovs_db() self.assertFalse(self.ovs_db_dump_mock.called) def test_log_ovs_db_debug_enabled(self): self.useFixture(mockpatch.PatchObject(test.CONF.debug, 'enable', True)) self.useFixture(mockpatch.PatchObject(test.CONF.service_available, 'neutron', True)) debug.log_ovs_db() self.ovs_db_dump_mock.assert_called_with() def test_log_net_debug(self): self.log_ip_ns_mock = self.patch('tempest.common.debug.log_ip_ns') self.log_ovs_db_mock = self.patch('tempest.common.debug.log_ovs_db') debug.log_net_debug() self.log_ip_ns_mock.assert_called_with() self.log_ovs_db_mock.assert_called_with() tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/__init__.py0000664000175000017500000000000012332757070024307 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/utils/0000775000175000017500000000000012332757136023353 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/utils/test_file_utils.py0000664000175000017500000000216512332757070027124 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from mock import patch from tempest.common.utils import file_utils from tempest.tests import base class TestFileUtils(base.TestCase): def test_have_effective_read_path(self): with patch('__builtin__.open', mock.mock_open(), create=True): result = file_utils.have_effective_read_access('fake_path') self.assertTrue(result) def test_not_effective_read_path(self): result = file_utils.have_effective_read_access('fake_path') self.assertFalse(result) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/utils/test_misc.py0000664000175000017500000000524112332757070025716 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import misc from tempest.tests import base @misc.singleton class TestFoo(object): count = 0 def increment(self): self.count += 1 return self.count @misc.singleton class TestBar(object): count = 0 def increment(self): self.count += 1 return self.count class TestMisc(base.TestCase): def test_singleton(self): test = TestFoo() self.assertEqual(0, test.count) self.assertEqual(1, test.increment()) test2 = TestFoo() self.assertEqual(1, test.count) self.assertEqual(1, test2.count) self.assertEqual(test, test2) test3 = TestBar() self.assertNotEqual(test, test3) def test_find_test_caller_test_case(self): # Calling it from here should give us the method we're in. self.assertEqual('TestMisc:test_find_test_caller_test_case', misc.find_test_caller()) def test_find_test_caller_setup_self(self): def setUp(self): return misc.find_test_caller() self.assertEqual('TestMisc:setUp', setUp(self)) def test_find_test_caller_setup_no_self(self): def setUp(): return misc.find_test_caller() self.assertEqual(':setUp', setUp()) def test_find_test_caller_setupclass_cls(self): def setUpClass(cls): # noqa return misc.find_test_caller() self.assertEqual('TestMisc:setUpClass', setUpClass(self.__class__)) def test_find_test_caller_teardown_self(self): def tearDown(self): return misc.find_test_caller() self.assertEqual('TestMisc:tearDown', tearDown(self)) def test_find_test_caller_teardown_no_self(self): def tearDown(): return misc.find_test_caller() self.assertEqual(':tearDown', tearDown()) def test_find_test_caller_teardown_class(self): def tearDownClass(cls): return misc.find_test_caller() self.assertEqual('TestMisc:tearDownClass', tearDownClass(self.__class__)) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/utils/test_data_utils.py0000664000175000017500000000550012332757070027112 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import data_utils from tempest.tests import base class TestDataUtils(base.TestCase): def test_rand_uuid(self): actual = data_utils.rand_uuid() self.assertIsInstance(actual, str) self.assertRegexpMatches(actual, "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]" "{4}-[0-9a-f]{4}-[0-9a-f]{12}$") actual2 = data_utils.rand_uuid() self.assertNotEqual(actual, actual2) def test_rand_uuid_hex(self): actual = data_utils.rand_uuid_hex() self.assertIsInstance(actual, str) self.assertRegexpMatches(actual, "^[0-9a-f]{32}$") actual2 = data_utils.rand_uuid_hex() self.assertNotEqual(actual, actual2) def test_rand_name(self): actual = data_utils.rand_name() self.assertIsInstance(actual, str) actual2 = data_utils.rand_name() self.assertNotEqual(actual, actual2) actual = data_utils.rand_name('foo') self.assertTrue(actual.startswith('foo')) actual2 = data_utils.rand_name('foo') self.assertTrue(actual.startswith('foo')) self.assertNotEqual(actual, actual2) def test_rand_int(self): actual = data_utils.rand_int_id() self.assertIsInstance(actual, int) actual2 = data_utils.rand_int_id() self.assertNotEqual(actual, actual2) def test_rand_mac_address(self): actual = data_utils.rand_mac_address() self.assertIsInstance(actual, str) self.assertRegexpMatches(actual, "^([0-9a-f][0-9a-f]:){5}" "[0-9a-f][0-9a-f]$") actual2 = data_utils.rand_mac_address() self.assertNotEqual(actual, actual2) def test_parse_image_id(self): actual = data_utils.parse_image_id("/foo/bar/deadbeaf") self.assertEqual("deadbeaf", actual) def test_arbitrary_string(self): actual = data_utils.arbitrary_string() self.assertEqual(actual, "test") actual = data_utils.arbitrary_string(size=30, base_text="abc") self.assertEqual(actual, "abc" * (30 / len("abc"))) actual = data_utils.arbitrary_string(size=5, base_text="deadbeaf") self.assertEqual(actual, "deadb") tempest-2014.1.dev4108.gf22b6cc/tempest/tests/common/utils/__init__.py0000664000175000017500000000000012332757070025447 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/fake_identity.py0000664000175000017500000001060312332757070024111 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import httplib2 import json TOKEN = "fake_token" ALT_TOKEN = "alt_fake_token" # Fake Identity v2 constants COMPUTE_ENDPOINTS_V2 = { "endpoints": [ { "adminURL": "http://fake_url/v2/first_endpoint/admin", "region": "NoMatchRegion", "internalURL": "http://fake_url/v2/first_endpoint/internal", "publicURL": "http://fake_url/v2/first_endpoint/public" }, { "adminURL": "http://fake_url/v2/second_endpoint/admin", "region": "FakeRegion", "internalURL": "http://fake_url/v2/second_endpoint/internal", "publicURL": "http://fake_url/v2/second_endpoint/public" }, ], "type": "compute", "name": "nova" } CATALOG_V2 = [COMPUTE_ENDPOINTS_V2, ] ALT_IDENTITY_V2_RESPONSE = { "access": { "token": { "expires": "2020-01-01T00:00:10Z", "id": ALT_TOKEN, "tenant": { "id": "fake_tenant_id" }, }, "user": { "id": "fake_user_id", }, "serviceCatalog": CATALOG_V2, }, } IDENTITY_V2_RESPONSE = { "access": { "token": { "expires": "2020-01-01T00:00:10Z", "id": TOKEN, "tenant": { "id": "fake_tenant_id" }, }, "user": { "id": "fake_user_id", }, "serviceCatalog": CATALOG_V2, }, } # Fake Identity V3 constants COMPUTE_ENDPOINTS_V3 = { "endpoints": [ { "id": "first_compute_fake_service", "interface": "public", "region": "NoMatchRegion", "url": "http://fake_url/v3/first_endpoint/api" }, { "id": "second_fake_service", "interface": "public", "region": "FakeRegion", "url": "http://fake_url/v3/second_endpoint/api" }, { "id": "third_fake_service", "interface": "admin", "region": "MiddleEarthRegion", "url": "http://fake_url/v3/third_endpoint/api" } ], "type": "compute", "id": "fake_compute_endpoint" } CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ] IDENTITY_V3_RESPONSE = { "token": { "methods": [ "token", "password" ], "expires_at": "2020-01-01T00:00:10.000123Z", "project": { "domain": { "id": "fake_domain_id", "name": "fake" }, "id": "project_id", "name": "project_name" }, "user": { "domain": { "id": "fake_domain_id", "name": "domain_name" }, "id": "fake_user_id", "name": "username" }, "issued_at": "2013-05-29T16:55:21.468960Z", "catalog": CATALOG_V3 } } ALT_IDENTITY_V3 = IDENTITY_V3_RESPONSE def _fake_v3_response(self, uri, method="GET", body=None, headers=None, redirections=5, connection_type=None): fake_headers = { "status": "201", "x-subject-token": TOKEN } return (httplib2.Response(fake_headers), json.dumps(IDENTITY_V3_RESPONSE)) def _fake_v2_response(self, uri, method="GET", body=None, headers=None, redirections=5, connection_type=None): return (httplib2.Response({"status": "200"}), json.dumps(IDENTITY_V2_RESPONSE)) def _fake_auth_failure_response(): # the response body isn't really used in this case, but lets send it anyway # to have a safe check in some future change on the rest client. body = { "unauthorized": { "message": "Unauthorized", "code": "401" } } return httplib2.Response({"status": "401"}), json.dumps(body) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_decorators.py0000664000175000017500000002377412332757070024513 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 mock import testtools from oslo.config import cfg from tempest import config from tempest import exceptions from tempest.openstack.common.fixture import mockpatch from tempest import test from tempest.tests import base from tempest.tests import fake_config class BaseDecoratorsTest(base.TestCase): def setUp(self): super(BaseDecoratorsTest, self).setUp() self.config_fixture = self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) class TestAttrDecorator(BaseDecoratorsTest): def _test_attr_helper(self, expected_attrs, **decorator_args): @test.attr(**decorator_args) def foo(): pass # By our test.attr decorator the attribute __testtools_attrs will be # set only for 'type' argument, so we test it first. if 'type' in decorator_args: # this is what testtools sets self.assertEqual(getattr(foo, '__testtools_attrs'), set(expected_attrs)) def test_attr_without_type(self): self._test_attr_helper(expected_attrs='baz', bar='baz') def test_attr_decorator_with_smoke_type(self): # smoke passed as type, so smoke and gate must have been set. self._test_attr_helper(expected_attrs=['smoke', 'gate'], type='smoke') def test_attr_decorator_with_list_type(self): # if type is 'smoke' we'll get the original list of types plus 'gate' self._test_attr_helper(expected_attrs=['smoke', 'foo', 'gate'], type=['smoke', 'foo']) def test_attr_decorator_with_unknown_type(self): self._test_attr_helper(expected_attrs=['foo'], type='foo') def test_attr_decorator_with_duplicated_type(self): self._test_attr_helper(expected_attrs=['foo'], type=['foo', 'foo']) class TestServicesDecorator(BaseDecoratorsTest): def _test_services_helper(self, *decorator_args): class TestFoo(test.BaseTestCase): @test.services(*decorator_args) def test_bar(self): return 0 t = TestFoo('test_bar') self.assertEqual(set(decorator_args), getattr(t.test_bar, '__testtools_attrs')) self.assertEqual(t.test_bar(), 0) def test_services_decorator_with_single_service(self): self._test_services_helper('compute') def test_services_decorator_with_multiple_services(self): self._test_services_helper('compute', 'network') def test_services_decorator_with_duplicated_service(self): self._test_services_helper('compute', 'compute') def test_services_decorator_with_invalid_service(self): self.assertRaises(exceptions.InvalidServiceTag, self._test_services_helper, 'compute', 'bad_service') def test_services_decorator_with_service_valid_and_unavailable(self): self.useFixture(mockpatch.PatchObject(test.CONF.service_available, 'cinder', False)) self.assertRaises(testtools.TestCase.skipException, self._test_services_helper, 'compute', 'volume') class TestStressDecorator(BaseDecoratorsTest): def _test_stresstest_helper(self, expected_frequency='process', expected_inheritance=False, **decorator_args): @test.stresstest(**decorator_args) def foo(): pass self.assertEqual(getattr(foo, 'st_class_setup_per'), expected_frequency) self.assertEqual(getattr(foo, 'st_allow_inheritance'), expected_inheritance) self.assertEqual(set(['stress']), getattr(foo, '__testtools_attrs')) def test_stresstest_decorator_default(self): self._test_stresstest_helper() def test_stresstest_decorator_class_setup_frequency(self): self._test_stresstest_helper('process', class_setup_per='process') def test_stresstest_decorator_class_setup_frequency_non_default(self): self._test_stresstest_helper(expected_frequency='application', class_setup_per='application') def test_stresstest_decorator_set_frequency_and_inheritance(self): self._test_stresstest_helper(expected_frequency='application', expected_inheritance=True, class_setup_per='application', allow_inheritance=True) class TestSkipBecauseDecorator(BaseDecoratorsTest): def _test_skip_because_helper(self, expected_to_skip=True, **decorator_args): class TestFoo(test.BaseTestCase): _interface = 'json' @test.skip_because(**decorator_args) def test_bar(self): return 0 t = TestFoo('test_bar') if expected_to_skip: self.assertRaises(testtools.TestCase.skipException, t.test_bar) else: # assert that test_bar returned 0 self.assertEqual(TestFoo('test_bar').test_bar(), 0) def test_skip_because_bug(self): self._test_skip_because_helper(bug='12345') def test_skip_because_bug_and_interface_match(self): self._test_skip_because_helper(bug='12346', interface='json') def test_skip_because_bug_interface_not_match(self): self._test_skip_because_helper(expected_to_skip=False, bug='12347', interface='xml') def test_skip_because_bug_and_condition_true(self): self._test_skip_because_helper(bug='12348', condition=True) def test_skip_because_bug_and_condition_false(self): self._test_skip_because_helper(expected_to_skip=False, bug='12349', condition=False) def test_skip_because_bug_condition_false_and_interface_match(self): """ Assure that only condition will be evaluated if both parameters are passed. """ self._test_skip_because_helper(expected_to_skip=False, bug='12350', condition=False, interface='json') def test_skip_because_bug_condition_true_and_interface_not_match(self): """ Assure that only condition will be evaluated if both parameters are passed. """ self._test_skip_because_helper(bug='12351', condition=True, interface='xml') def test_skip_because_bug_without_bug_never_skips(self): """Never skip without a bug parameter.""" self._test_skip_because_helper(expected_to_skip=False, condition=True) self._test_skip_because_helper(expected_to_skip=False, interface='json') def test_skip_because_invalid_bug_number(self): """Raise ValueError if with an invalid bug number""" self.assertRaises(ValueError, self._test_skip_because_helper, bug='critical_bug') class TestRequiresExtDecorator(BaseDecoratorsTest): def setUp(self): super(TestRequiresExtDecorator, self).setUp() cfg.CONF.set_default('api_extensions', ['enabled_ext', 'another_ext'], 'compute-feature-enabled') def _test_requires_ext_helper(self, expected_to_skip=True, **decorator_args): class TestFoo(test.BaseTestCase): @test.requires_ext(**decorator_args) def test_bar(self): return 0 t = TestFoo('test_bar') if expected_to_skip: self.assertRaises(testtools.TestCase.skipException, t.test_bar) else: self.assertEqual(t.test_bar(), 0) def test_requires_ext_decorator(self): self._test_requires_ext_helper(expected_to_skip=False, extension='enabled_ext', service='compute') def test_requires_ext_decorator_disabled_ext(self): self._test_requires_ext_helper(extension='disabled_ext', service='compute') def test_requires_ext_decorator_with_all_ext_enabled(self): # disable fixture so the default (all) is used. self.config_fixture.cleanUp() self._test_requires_ext_helper(expected_to_skip=False, extension='random_ext', service='compute') def test_requires_ext_decorator_bad_service(self): self.assertRaises(KeyError, self._test_requires_ext_helper, extension='enabled_ext', service='bad_service') class TestSimpleNegativeDecorator(BaseDecoratorsTest): @test.SimpleNegativeAutoTest class FakeNegativeJSONTest(test.NegativeAutoTest): _schema_file = 'fake/schemas/file.json' def test_testfunc_exist(self): self.assertIn("test_fake_negative", dir(self.FakeNegativeJSONTest)) @mock.patch('tempest.test.NegativeAutoTest.execute') def test_testfunc_calls_execute(self, mock): obj = self.FakeNegativeJSONTest("test_fake_negative") self.assertIn("test_fake_negative", dir(obj)) obj.test_fake_negative() mock.assert_called_once_with(self.FakeNegativeJSONTest._schema_file) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/stress/0000775000175000017500000000000012332757136022246 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/stress/test_stressaction.py0000664000175000017500000000435612332757070026405 0ustar chuckchuck00000000000000# Copyright 2013 Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import tempest.stress.stressaction as stressaction import tempest.test class FakeStressAction(stressaction.StressAction): def __init__(self, manager, max_runs=None, stop_on_error=False): super(self.__class__, self).__init__(manager, max_runs, stop_on_error) self._run_called = False def run(self): self._run_called = True @property def run_called(self): return self._run_called class FakeStressActionFailing(stressaction.StressAction): def run(self): raise Exception('FakeStressActionFailing raise exception') class TestStressAction(tempest.test.BaseTestCase): def _bulid_stats_dict(self, runs=0, fails=0): return {'runs': runs, 'fails': fails} def testStressTestRun(self): stressAction = FakeStressAction(manager=None, max_runs=1) stats = self._bulid_stats_dict() stressAction.execute(stats) self.assertTrue(stressAction.run_called) self.assertEqual(stats['runs'], 1) self.assertEqual(stats['fails'], 0) def testStressMaxTestRuns(self): stressAction = FakeStressAction(manager=None, max_runs=500) stats = self._bulid_stats_dict(runs=499) stressAction.execute(stats) self.assertTrue(stressAction.run_called) self.assertEqual(stats['runs'], 500) self.assertEqual(stats['fails'], 0) def testStressTestRunWithException(self): stressAction = FakeStressActionFailing(manager=None, max_runs=1) stats = self._bulid_stats_dict() stressAction.execute(stats) self.assertEqual(stats['runs'], 1) self.assertEqual(stats['fails'], 1) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/stress/test_stress.py0000664000175000017500000000355412332757070025206 0ustar chuckchuck00000000000000# Copyright 2013 Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import shlex import subprocess import tempest.cli as cli from tempest.openstack.common import log as logging import tempest.test LOG = logging.getLogger(__name__) class StressFrameworkTest(tempest.test.BaseTestCase): """Basic test for the stress test framework. """ def _cmd(self, cmd, param): """Executes specified command.""" cmd = ' '.join([cmd, param]) LOG.info("running: '%s'" % cmd) cmd_str = cmd cmd = shlex.split(cmd) result = '' result_err = '' try: stdout = subprocess.PIPE stderr = subprocess.PIPE proc = subprocess.Popen( cmd, stdout=stdout, stderr=stderr) result, result_err = proc.communicate() if proc.returncode != 0: LOG.debug('error of %s:\n%s' % (cmd_str, result_err)) raise cli.CommandFailed(proc.returncode, cmd, result) finally: LOG.debug('output of %s:\n%s' % (cmd_str, result)) return proc.returncode def test_help_function(self): result = self._cmd("python", "-m tempest.stress.run_stress -h") self.assertEqual(0, result) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/stress/__init__.py0000664000175000017500000000000012332757070024342 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_auth.py0000664000175000017500000003726212332757070023304 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import datetime from tempest import auth from tempest.common import http from tempest import config from tempest import exceptions from tempest.openstack.common.fixture import mockpatch from tempest.tests import base from tempest.tests import fake_auth_provider from tempest.tests import fake_config from tempest.tests import fake_credentials from tempest.tests import fake_http from tempest.tests import fake_identity class BaseAuthTestsSetUp(base.TestCase): _auth_provider_class = None credentials = fake_credentials.FakeCredentials() def _auth(self, credentials, **params): """ returns auth method according to keystone """ return self._auth_provider_class(credentials, **params) def setUp(self): super(BaseAuthTestsSetUp, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.fake_http = fake_http.fake_httplib2(return_type=200) self.stubs.Set(http.ClosingHttp, 'request', self.fake_http.request) self.stubs.Set(auth, 'get_credentials', fake_auth_provider.get_credentials) self.stubs.Set(auth, 'get_default_credentials', fake_auth_provider.get_default_credentials) self.auth_provider = self._auth(self.credentials) class TestBaseAuthProvider(BaseAuthTestsSetUp): """ This tests auth.AuthProvider class which is base for the other so we obviously don't test not implemented method or the ones which strongly depends on them. """ _auth_provider_class = auth.AuthProvider def test_check_credentials_class(self): self.assertRaises(NotImplementedError, self.auth_provider.check_credentials, auth.Credentials()) def test_check_credentials_bad_type(self): self.assertFalse(self.auth_provider.check_credentials([])) def test_instantiate_with_dict(self): # Dict credentials are only supported for backward compatibility auth_provider = self._auth(credentials={}) self.assertIsInstance(auth_provider.credentials, auth.Credentials) def test_instantiate_with_bad_credentials_type(self): """ Assure that credentials with bad type fail with TypeError """ self.assertRaises(TypeError, self._auth, []) def test_auth_data_property(self): self.assertRaises(NotImplementedError, getattr, self.auth_provider, 'auth_data') def test_auth_data_property_when_cache_exists(self): self.auth_provider.cache = 'foo' self.useFixture(mockpatch.PatchObject(self.auth_provider, 'is_expired', return_value=False)) self.assertEqual('foo', getattr(self.auth_provider, 'auth_data')) def test_delete_auth_data_property_through_deleter(self): self.auth_provider.cache = 'foo' del self.auth_provider.auth_data self.assertIsNone(self.auth_provider.cache) def test_delete_auth_data_property_through_clear_auth(self): self.auth_provider.cache = 'foo' self.auth_provider.clear_auth() self.assertIsNone(self.auth_provider.cache) def test_set_and_reset_alt_auth_data(self): self.auth_provider.set_alt_auth_data('foo', 'bar') self.assertEqual(self.auth_provider.alt_part, 'foo') self.assertEqual(self.auth_provider.alt_auth_data, 'bar') self.auth_provider.reset_alt_auth_data() self.assertIsNone(self.auth_provider.alt_part) self.assertIsNone(self.auth_provider.alt_auth_data) def test_fill_credentials(self): self.assertRaises(NotImplementedError, self.auth_provider.fill_credentials) class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp): _endpoints = fake_identity.IDENTITY_V2_RESPONSE['access']['serviceCatalog'] _auth_provider_class = auth.KeystoneV2AuthProvider credentials = fake_credentials.FakeKeystoneV2Credentials() def setUp(self): super(TestKeystoneV2AuthProvider, self).setUp() self.stubs.Set(http.ClosingHttp, 'request', fake_identity._fake_v2_response) self.target_url = 'test_api' def _get_fake_alt_identity(self): return fake_identity.ALT_IDENTITY_V2_RESPONSE['access'] def _get_result_url_from_endpoint(self, ep, endpoint_type='publicURL', replacement=None): if replacement: return ep[endpoint_type].replace('v2', replacement) return ep[endpoint_type] def _get_token_from_fake_identity(self): return fake_identity.TOKEN def _get_from_fake_identity(self, attr): access = fake_identity.IDENTITY_V2_RESPONSE['access'] if attr == 'user_id': return access['user']['id'] elif attr == 'tenant_id': return access['token']['tenant']['id'] def _test_request_helper(self, filters, expected): url, headers, body = self.auth_provider.auth_request('GET', self.target_url, filters=filters) self.assertEqual(expected['url'], url) self.assertEqual(expected['token'], headers['X-Auth-Token']) self.assertEqual(expected['body'], body) def _auth_data_with_expiry(self, date_as_string): token, access = self.auth_provider.auth_data access['token']['expires'] = date_as_string return token, access def test_request(self): filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion' } url = self._get_result_url_from_endpoint( self._endpoints[0]['endpoints'][1]) + '/' + self.target_url expected = { 'body': None, 'url': url, 'token': self._get_token_from_fake_identity(), } self._test_request_helper(filters, expected) def test_request_with_alt_auth_cleans_alt(self): self.auth_provider.set_alt_auth_data( 'body', (fake_identity.ALT_TOKEN, self._get_fake_alt_identity())) self.test_request() # Assert alt auth data is clear after it self.assertIsNone(self.auth_provider.alt_part) self.assertIsNone(self.auth_provider.alt_auth_data) def test_request_with_alt_part_without_alt_data(self): """ Assert that when alt_part is defined, the corresponding original request element is kept the same. """ filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'fakeRegion' } self.auth_provider.set_alt_auth_data('url', None) url, headers, body = self.auth_provider.auth_request('GET', self.target_url, filters=filters) self.assertEqual(url, self.target_url) self.assertEqual(self._get_token_from_fake_identity(), headers['X-Auth-Token']) self.assertEqual(body, None) def test_request_with_bad_service(self): filters = { 'service': 'BAD_SERVICE', 'endpoint_type': 'publicURL', 'region': 'fakeRegion' } self.assertRaises(exceptions.EndpointNotFound, self.auth_provider.auth_request, 'GET', self.target_url, filters=filters) def test_request_without_service(self): filters = { 'service': None, 'endpoint_type': 'publicURL', 'region': 'fakeRegion' } self.assertRaises(exceptions.EndpointNotFound, self.auth_provider.auth_request, 'GET', self.target_url, filters=filters) def test_check_credentials_missing_attribute(self): for attr in ['username', 'password']: cred = copy.copy(self.credentials) del cred[attr] self.assertFalse(self.auth_provider.check_credentials(cred)) def test_fill_credentials(self): self.auth_provider.fill_credentials() creds = self.auth_provider.credentials for attr in ['user_id', 'tenant_id']: self.assertEqual(self._get_from_fake_identity(attr), getattr(creds, attr)) def _test_base_url_helper(self, expected_url, filters, auth_data=None): url = self.auth_provider.base_url(filters, auth_data) self.assertEqual(url, expected_url) def test_base_url(self): self.filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion' } expected = self._get_result_url_from_endpoint( self._endpoints[0]['endpoints'][1]) self._test_base_url_helper(expected, self.filters) def test_base_url_to_get_admin_endpoint(self): self.filters = { 'service': 'compute', 'endpoint_type': 'adminURL', 'region': 'FakeRegion' } expected = self._get_result_url_from_endpoint( self._endpoints[0]['endpoints'][1], endpoint_type='adminURL') self._test_base_url_helper(expected, self.filters) def test_base_url_unknown_region(self): """ Assure that if the region is unknow the first endpoint is returned. """ self.filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'AintNoBodyKnowThisRegion' } expected = self._get_result_url_from_endpoint( self._endpoints[0]['endpoints'][0]) self._test_base_url_helper(expected, self.filters) def test_base_url_with_non_existent_service(self): self.filters = { 'service': 'BAD_SERVICE', 'endpoint_type': 'publicURL', 'region': 'FakeRegion' } self.assertRaises(exceptions.EndpointNotFound, self._test_base_url_helper, None, self.filters) def test_base_url_without_service(self): self.filters = { 'endpoint_type': 'publicURL', 'region': 'FakeRegion' } self.assertRaises(exceptions.EndpointNotFound, self._test_base_url_helper, None, self.filters) def test_base_url_with_api_version_filter(self): self.filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'api_version': 'v12' } expected = self._get_result_url_from_endpoint( self._endpoints[0]['endpoints'][1], replacement='v12') self._test_base_url_helper(expected, self.filters) def test_base_url_with_skip_path_filter(self): self.filters = { 'service': 'compute', 'endpoint_type': 'publicURL', 'region': 'FakeRegion', 'skip_path': True } expected = 'http://fake_url/' self._test_base_url_helper(expected, self.filters) def test_token_not_expired(self): expiry_data = datetime.datetime.utcnow() + datetime.timedelta(days=1) auth_data = self._auth_data_with_expiry( expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT)) self.assertFalse(self.auth_provider.is_expired(auth_data)) def test_token_expired(self): expiry_data = datetime.datetime.utcnow() - datetime.timedelta(hours=1) auth_data = self._auth_data_with_expiry( expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT)) self.assertTrue(self.auth_provider.is_expired(auth_data)) def test_token_not_expired_to_be_renewed(self): expiry_data = datetime.datetime.utcnow() + \ self.auth_provider.token_expiry_threshold / 2 auth_data = self._auth_data_with_expiry( expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT)) self.assertTrue(self.auth_provider.is_expired(auth_data)) class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider): _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog'] _auth_provider_class = auth.KeystoneV3AuthProvider credentials = fake_credentials.FakeKeystoneV3Credentials() def setUp(self): super(TestKeystoneV3AuthProvider, self).setUp() self.stubs.Set(http.ClosingHttp, 'request', fake_identity._fake_v3_response) def _get_fake_alt_identity(self): return fake_identity.ALT_IDENTITY_V3['token'] def _get_result_url_from_endpoint(self, ep, replacement=None): if replacement: return ep['url'].replace('v3', replacement) return ep['url'] def _auth_data_with_expiry(self, date_as_string): token, access = self.auth_provider.auth_data access['expires_at'] = date_as_string return token, access def _get_from_fake_identity(self, attr): token = fake_identity.IDENTITY_V3_RESPONSE['token'] if attr == 'user_id': return token['user']['id'] elif attr == 'project_id': return token['project']['id'] elif attr == 'user_domain_id': return token['user']['domain']['id'] elif attr == 'project_domain_id': return token['project']['domain']['id'] def test_check_credentials_missing_attribute(self): # reset credentials to fresh ones self.credentials.reset() for attr in ['username', 'password', 'user_domain_name', 'project_domain_name']: cred = copy.copy(self.credentials) del cred[attr] self.assertFalse(self.auth_provider.check_credentials(cred), "Credentials should be invalid without %s" % attr) def test_check_domain_credentials_missing_attribute(self): # reset credentials to fresh ones self.credentials.reset() domain_creds = fake_credentials.FakeKeystoneV3DomainCredentials() for attr in ['username', 'password', 'user_domain_name']: cred = copy.copy(domain_creds) del cred[attr] self.assertFalse(self.auth_provider.check_credentials(cred), "Credentials should be invalid without %s" % attr) def test_fill_credentials(self): self.auth_provider.fill_credentials() creds = self.auth_provider.credentials for attr in ['user_id', 'project_id', 'user_domain_id', 'project_domain_id']: self.assertEqual(self._get_from_fake_identity(attr), getattr(creds, attr)) # Overwrites v2 test def test_base_url_to_get_admin_endpoint(self): self.filters = { 'service': 'compute', 'endpoint_type': 'admin', 'region': 'MiddleEarthRegion' } expected = self._get_result_url_from_endpoint( self._endpoints[0]['endpoints'][2]) self._test_base_url_helper(expected, self.filters) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_tenant_isolation.py0000664000175000017500000005565412332757070025722 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # # 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 keystoneclient.v2_0.client as keystoneclient from mock import patch import neutronclient.v2_0.client as neutronclient from oslo.config import cfg from tempest import clients from tempest.common import http from tempest.common import isolated_creds from tempest import config from tempest import exceptions from tempest.openstack.common.fixture import mockpatch from tempest.services.identity.json import identity_client as json_iden_client from tempest.services.identity.xml import identity_client as xml_iden_client from tempest.services.network.json import network_client as json_network_client from tempest.services.network.xml import network_client as xml_network_client from tempest.tests import base from tempest.tests import fake_config from tempest.tests import fake_http from tempest.tests import fake_identity class TestTenantIsolation(base.TestCase): def setUp(self): super(TestTenantIsolation, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.fake_http = fake_http.fake_httplib2(return_type=200) self.stubs.Set(http.ClosingHttp, 'request', fake_identity._fake_v2_response) def test_tempest_client(self): iso_creds = isolated_creds.IsolatedCreds('test class') self.assertTrue(isinstance(iso_creds.identity_admin_client, json_iden_client.IdentityClientJSON)) self.assertTrue(isinstance(iso_creds.network_admin_client, json_network_client.NetworkClientJSON)) def test_official_client(self): self.useFixture(mockpatch.PatchObject(keystoneclient.Client, 'authenticate')) self.useFixture(mockpatch.PatchObject(clients.OfficialClientManager, '_get_image_client')) self.useFixture(mockpatch.PatchObject(clients.OfficialClientManager, '_get_object_storage_client')) self.useFixture(mockpatch.PatchObject(clients.OfficialClientManager, '_get_orchestration_client')) iso_creds = isolated_creds.IsolatedCreds('test class', tempest_client=False) self.assertTrue(isinstance(iso_creds.identity_admin_client, keystoneclient.Client)) self.assertTrue(isinstance(iso_creds.network_admin_client, neutronclient.Client)) def test_tempest_client_xml(self): iso_creds = isolated_creds.IsolatedCreds('test class', interface='xml') self.assertEqual(iso_creds.interface, 'xml') self.assertTrue(isinstance(iso_creds.identity_admin_client, xml_iden_client.IdentityClientXML)) self.assertTrue(isinstance(iso_creds.network_admin_client, xml_network_client.NetworkClientXML)) def _mock_user_create(self, id, name): user_fix = self.useFixture(mockpatch.PatchObject( json_iden_client.IdentityClientJSON, 'create_user', return_value=({'status': 200}, {'id': id, 'name': name}))) return user_fix def _mock_tenant_create(self, id, name): tenant_fix = self.useFixture(mockpatch.PatchObject( json_iden_client.IdentityClientJSON, 'create_tenant', return_value=({'status': 200}, {'id': id, 'name': name}))) return tenant_fix def _mock_network_create(self, iso_creds, id, name): net_fix = self.useFixture(mockpatch.PatchObject( iso_creds.network_admin_client, 'create_network', return_value=({'status': 200}, {'network': {'id': id, 'name': name}}))) return net_fix def _mock_subnet_create(self, iso_creds, id, name): subnet_fix = self.useFixture(mockpatch.PatchObject( iso_creds.network_admin_client, 'create_subnet', return_value=({'status': 200}, {'subnet': {'id': id, 'name': name}}))) return subnet_fix def _mock_router_create(self, id, name): router_fix = self.useFixture(mockpatch.PatchObject( json_network_client.NetworkClientJSON, 'create_router', return_value=({'status': 200}, {'router': {'id': id, 'name': name}}))) return router_fix @patch('tempest.common.rest_client.RestClient') def test_primary_creds(self, MockRestClient): cfg.CONF.set_default('neutron', False, 'service_available') iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') self._mock_tenant_create('1234', 'fake_prim_tenant') self._mock_user_create('1234', 'fake_prim_user') primary_creds = iso_creds.get_primary_creds(old_style=False) self.assertEqual(primary_creds.username, 'fake_prim_user') self.assertEqual(primary_creds.tenant_name, 'fake_prim_tenant') # Verify helper methods tenant = iso_creds.get_primary_tenant() user = iso_creds.get_primary_user() self.assertEqual(tenant['id'], '1234') self.assertEqual(user['id'], '1234') @patch('tempest.common.rest_client.RestClient') def test_admin_creds(self, MockRestClient): cfg.CONF.set_default('neutron', False, 'service_available') iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') self._mock_user_create('1234', 'fake_admin_user') self._mock_tenant_create('1234', 'fake_admin_tenant') self.useFixture(mockpatch.PatchObject( json_iden_client.IdentityClientJSON, 'list_roles', return_value=({'status': 200}, [{'id': '1234', 'name': 'admin'}]))) user_mock = patch.object(json_iden_client.IdentityClientJSON, 'assign_user_role') user_mock.start() self.addCleanup(user_mock.stop) with patch.object(json_iden_client.IdentityClientJSON, 'assign_user_role') as user_mock: admin_creds = iso_creds.get_admin_creds() user_mock.assert_called_once_with('1234', '1234', '1234') self.assertEqual(admin_creds.username, 'fake_admin_user') self.assertEqual(admin_creds.tenant_name, 'fake_admin_tenant') # Verify helper methods tenant = iso_creds.get_admin_tenant() user = iso_creds.get_admin_user() self.assertEqual(tenant['id'], '1234') self.assertEqual(user['id'], '1234') @patch('tempest.common.rest_client.RestClient') def test_all_cred_cleanup(self, MockRestClient): cfg.CONF.set_default('neutron', False, 'service_available') iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') tenant_fix = self._mock_tenant_create('1234', 'fake_prim_tenant') user_fix = self._mock_user_create('1234', 'fake_prim_user') iso_creds.get_primary_creds(old_style=False) tenant_fix.cleanUp() user_fix.cleanUp() tenant_fix = self._mock_tenant_create('12345', 'fake_alt_tenant') user_fix = self._mock_user_create('12345', 'fake_alt_user') iso_creds.get_alt_creds(old_style=False) tenant_fix.cleanUp() user_fix.cleanUp() tenant_fix = self._mock_tenant_create('123456', 'fake_admin_tenant') user_fix = self._mock_user_create('123456', 'fake_admin_user') self.useFixture(mockpatch.PatchObject( json_iden_client.IdentityClientJSON, 'list_roles', return_value=({'status': 200}, [{'id': '123456', 'name': 'admin'}]))) with patch.object(json_iden_client.IdentityClientJSON, 'assign_user_role'): iso_creds.get_admin_creds() user_mock = self.patch( 'tempest.services.identity.json.identity_client.' 'IdentityClientJSON.delete_user') tenant_mock = self.patch( 'tempest.services.identity.json.identity_client.' 'IdentityClientJSON.delete_tenant') iso_creds.clear_isolated_creds() # Verify user delete calls calls = user_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) # Verify tenant delete calls calls = tenant_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) @patch('tempest.common.rest_client.RestClient') def test_alt_creds(self, MockRestClient): cfg.CONF.set_default('neutron', False, 'service_available') iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') self._mock_user_create('1234', 'fake_alt_user') self._mock_tenant_create('1234', 'fake_alt_tenant') alt_creds = iso_creds.get_alt_creds(old_style=False) self.assertEqual(alt_creds.username, 'fake_alt_user') self.assertEqual(alt_creds.tenant_name, 'fake_alt_tenant') # Verify helper methods tenant = iso_creds.get_alt_tenant() user = iso_creds.get_alt_user() self.assertEqual(tenant['id'], '1234') self.assertEqual(user['id'], '1234') @patch('tempest.common.rest_client.RestClient') def test_network_creation(self, MockRestClient): iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self._mock_network_create(iso_creds, '1234', 'fake_net') self._mock_subnet_create(iso_creds, '1234', 'fake_subnet') self._mock_router_create('1234', 'fake_router') router_interface_mock = self.patch( 'tempest.services.network.json.network_client.NetworkClientJSON.' 'add_router_interface_with_subnet_id') iso_creds.get_primary_creds(old_style=False) router_interface_mock.called_once_with('1234', '1234') network = iso_creds.get_primary_network() subnet = iso_creds.get_primary_subnet() router = iso_creds.get_primary_router() self.assertEqual(network['id'], '1234') self.assertEqual(network['name'], 'fake_net') self.assertEqual(subnet['id'], '1234') self.assertEqual(subnet['name'], 'fake_subnet') self.assertEqual(router['id'], '1234') self.assertEqual(router['name'], 'fake_router') @patch('tempest.common.rest_client.RestClient') def test_network_cleanup(self, MockRestClient): iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') # Create primary tenant and network user_fix = self._mock_user_create('1234', 'fake_prim_user') tenant_fix = self._mock_tenant_create('1234', 'fake_prim_tenant') net_fix = self._mock_network_create(iso_creds, '1234', 'fake_net') subnet_fix = self._mock_subnet_create(iso_creds, '1234', 'fake_subnet') router_fix = self._mock_router_create('1234', 'fake_router') router_interface_mock = self.patch( 'tempest.services.network.json.network_client.NetworkClientJSON.' 'add_router_interface_with_subnet_id') iso_creds.get_primary_creds(old_style=False) router_interface_mock.called_once_with('1234', '1234') router_interface_mock.reset_mock() tenant_fix.cleanUp() user_fix.cleanUp() net_fix.cleanUp() subnet_fix.cleanUp() router_fix.cleanUp() # Create alternate tenant and network user_fix = self._mock_user_create('12345', 'fake_alt_user') tenant_fix = self._mock_tenant_create('12345', 'fake_alt_tenant') net_fix = self._mock_network_create(iso_creds, '12345', 'fake_alt_net') subnet_fix = self._mock_subnet_create(iso_creds, '12345', 'fake_alt_subnet') router_fix = self._mock_router_create('12345', 'fake_alt_router') iso_creds.get_alt_creds(old_style=False) router_interface_mock.called_once_with('12345', '12345') router_interface_mock.reset_mock() tenant_fix.cleanUp() user_fix.cleanUp() net_fix.cleanUp() subnet_fix.cleanUp() router_fix.cleanUp() # Create admin tenant and networks user_fix = self._mock_user_create('123456', 'fake_admin_user') tenant_fix = self._mock_tenant_create('123456', 'fake_admin_tenant') net_fix = self._mock_network_create(iso_creds, '123456', 'fake_admin_net') subnet_fix = self._mock_subnet_create(iso_creds, '123456', 'fake_admin_subnet') router_fix = self._mock_router_create('123456', 'fake_admin_router') self.useFixture(mockpatch.PatchObject( json_iden_client.IdentityClientJSON, 'list_roles', return_value=({'status': 200}, [{'id': '123456', 'name': 'admin'}]))) with patch.object(json_iden_client.IdentityClientJSON, 'assign_user_role'): iso_creds.get_admin_creds() self.patch('tempest.services.identity.json.identity_client.' 'IdentityClientJSON.delete_user') self.patch('tempest.services.identity.json.identity_client.' 'IdentityClientJSON.delete_tenant') net = patch.object(iso_creds.network_admin_client, 'delete_network') net_mock = net.start() subnet = patch.object(iso_creds.network_admin_client, 'delete_subnet') subnet_mock = subnet.start() router = patch.object(iso_creds.network_admin_client, 'delete_router') router_mock = router.start() remove_router_interface_mock = self.patch( 'tempest.services.network.json.network_client.NetworkClientJSON.' 'remove_router_interface_with_subnet_id') port_list_mock = patch.object(iso_creds.network_admin_client, 'list_ports', return_value=( {'status': 200}, {'ports': []})) port_list_mock.start() iso_creds.clear_isolated_creds() # Verify remove router interface calls calls = remove_router_interface_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1], calls) self.assertIn(('1234', '1234'), args) self.assertIn(('12345', '12345'), args) self.assertIn(('123456', '123456'), args) # Verify network delete calls calls = net_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) # Verify subnet delete calls calls = subnet_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) # Verify router delete calls calls = router_mock.mock_calls self.assertEqual(len(calls), 3) args = map(lambda x: x[1][0], calls) self.assertIn('1234', args) self.assertIn('12345', args) self.assertIn('123456', args) @patch('tempest.common.rest_client.RestClient') def test_network_alt_creation(self, MockRestClient): iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') self._mock_user_create('1234', 'fake_alt_user') self._mock_tenant_create('1234', 'fake_alt_tenant') self._mock_network_create(iso_creds, '1234', 'fake_alt_net') self._mock_subnet_create(iso_creds, '1234', 'fake_alt_subnet') self._mock_router_create('1234', 'fake_alt_router') router_interface_mock = self.patch( 'tempest.services.network.json.network_client.NetworkClientJSON.' 'add_router_interface_with_subnet_id') iso_creds.get_alt_creds() router_interface_mock.called_once_with('1234', '1234') network = iso_creds.get_alt_network() subnet = iso_creds.get_alt_subnet() router = iso_creds.get_alt_router() self.assertEqual(network['id'], '1234') self.assertEqual(network['name'], 'fake_alt_net') self.assertEqual(subnet['id'], '1234') self.assertEqual(subnet['name'], 'fake_alt_subnet') self.assertEqual(router['id'], '1234') self.assertEqual(router['name'], 'fake_alt_router') @patch('tempest.common.rest_client.RestClient') def test_network_admin_creation(self, MockRestClient): iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password') self._mock_user_create('1234', 'fake_admin_user') self._mock_tenant_create('1234', 'fake_admin_tenant') self._mock_network_create(iso_creds, '1234', 'fake_admin_net') self._mock_subnet_create(iso_creds, '1234', 'fake_admin_subnet') self._mock_router_create('1234', 'fake_admin_router') router_interface_mock = self.patch( 'tempest.services.network.json.network_client.NetworkClientJSON.' 'add_router_interface_with_subnet_id') self.useFixture(mockpatch.PatchObject( json_iden_client.IdentityClientJSON, 'list_roles', return_value=({'status': 200}, [{'id': '123456', 'name': 'admin'}]))) with patch.object(json_iden_client.IdentityClientJSON, 'assign_user_role'): iso_creds.get_admin_creds() router_interface_mock.called_once_with('1234', '1234') network = iso_creds.get_admin_network() subnet = iso_creds.get_admin_subnet() router = iso_creds.get_admin_router() self.assertEqual(network['id'], '1234') self.assertEqual(network['name'], 'fake_admin_net') self.assertEqual(subnet['id'], '1234') self.assertEqual(subnet['name'], 'fake_admin_subnet') self.assertEqual(router['id'], '1234') self.assertEqual(router['name'], 'fake_admin_router') @patch('tempest.common.rest_client.RestClient') def test_no_network_resources(self, MockRestClient): net_dict = { 'network': False, 'router': False, 'subnet': False, 'dhcp': False, } iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password', network_resources=net_dict) self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') net = patch.object(iso_creds.network_admin_client, 'delete_network') net_mock = net.start() subnet = patch.object(iso_creds.network_admin_client, 'delete_subnet') subnet_mock = subnet.start() router = patch.object(iso_creds.network_admin_client, 'delete_router') router_mock = router.start() iso_creds.get_primary_creds() self.assertEqual(net_mock.mock_calls, []) self.assertEqual(subnet_mock.mock_calls, []) self.assertEqual(router_mock.mock_calls, []) network = iso_creds.get_primary_network() subnet = iso_creds.get_primary_subnet() router = iso_creds.get_primary_router() self.assertIsNone(network) self.assertIsNone(subnet) self.assertIsNone(router) @patch('tempest.common.rest_client.RestClient') def test_router_without_network(self, MockRestClient): net_dict = { 'network': False, 'router': True, 'subnet': False, 'dhcp': False, } iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password', network_resources=net_dict) self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self.assertRaises(exceptions.InvalidConfiguration, iso_creds.get_primary_creds) @patch('tempest.common.rest_client.RestClient') def test_subnet_without_network(self, MockRestClient): net_dict = { 'network': False, 'router': False, 'subnet': True, 'dhcp': False, } iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password', network_resources=net_dict) self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self.assertRaises(exceptions.InvalidConfiguration, iso_creds.get_primary_creds) @patch('tempest.common.rest_client.RestClient') def test_dhcp_without_subnet(self, MockRestClient): net_dict = { 'network': False, 'router': False, 'subnet': False, 'dhcp': True, } iso_creds = isolated_creds.IsolatedCreds('test class', password='fake_password', network_resources=net_dict) self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') self.assertRaises(exceptions.InvalidConfiguration, iso_creds.get_primary_creds) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_ssh.py0000664000175000017500000001621612332757070023134 0ustar chuckchuck00000000000000# Copyright 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 contextlib import socket import mock import testtools from tempest.common import ssh from tempest import exceptions from tempest.tests import base class TestSshClient(base.TestCase): def test_pkey_calls_paramiko_RSAKey(self): with contextlib.nested( mock.patch('paramiko.RSAKey.from_private_key'), mock.patch('cStringIO.StringIO')) as (rsa_mock, cs_mock): cs_mock.return_value = mock.sentinel.csio pkey = 'mykey' ssh.Client('localhost', 'root', pkey=pkey) rsa_mock.assert_called_once_with(mock.sentinel.csio) cs_mock.assert_called_once_with('mykey') rsa_mock.reset_mock() cs_mock.rest_mock() pkey = mock.sentinel.pkey # Shouldn't call out to load a file from RSAKey, since # a sentinel isn't a basestring... ssh.Client('localhost', 'root', pkey=pkey) rsa_mock.assert_not_called() cs_mock.assert_not_called() def test_get_ssh_connection(self): c_mock = self.patch('paramiko.SSHClient') aa_mock = self.patch('paramiko.AutoAddPolicy') s_mock = self.patch('time.sleep') t_mock = self.patch('time.time') aa_mock.return_value = mock.sentinel.aa def reset_mocks(): aa_mock.reset_mock() c_mock.reset_mock() s_mock.reset_mock() t_mock.reset_mock() # Test normal case for successful connection on first try client_mock = mock.MagicMock() c_mock.return_value = client_mock client_mock.connect.return_value = True client = ssh.Client('localhost', 'root', timeout=2) client._get_ssh_connection(sleep=1) aa_mock.assert_called_once_with() client_mock.set_missing_host_key_policy.assert_called_once_with( mock.sentinel.aa) expected_connect = [mock.call( 'localhost', username='root', pkey=None, key_filename=None, look_for_keys=False, timeout=10.0, password=None )] self.assertEqual(expected_connect, client_mock.connect.mock_calls) s_mock.assert_not_called() t_mock.assert_called_once_with() reset_mocks() # Test case when connection fails on first two tries and # succeeds on third try (this validates retry logic) client_mock.connect.side_effect = [socket.error, socket.error, True] t_mock.side_effect = [ 1000, # Start time 1000, # LOG.warning() calls time.time() loop 1 1001, # Sleep loop 1 1001, # LOG.warning() calls time.time() loop 2 1002 # Sleep loop 2 ] client._get_ssh_connection(sleep=1) expected_sleeps = [ mock.call(2), mock.call(3) ] self.assertEqual(expected_sleeps, s_mock.mock_calls) reset_mocks() # Test case when connection fails on first three tries and # exceeds the timeout, so expect to raise a Timeout exception client_mock.connect.side_effect = [ socket.error, socket.error, socket.error ] t_mock.side_effect = [ 1000, # Start time 1000, # LOG.warning() calls time.time() loop 1 1001, # Sleep loop 1 1001, # LOG.warning() calls time.time() loop 2 1002, # Sleep loop 2 1003, # Sleep loop 3 1004 # LOG.error() calls time.time() ] with testtools.ExpectedException(exceptions.SSHTimeout): client._get_ssh_connection() def test_exec_command(self): gsc_mock = self.patch('tempest.common.ssh.Client._get_ssh_connection') ito_mock = self.patch('tempest.common.ssh.Client._is_timed_out') select_mock = self.patch('select.poll') client_mock = mock.MagicMock() tran_mock = mock.MagicMock() chan_mock = mock.MagicMock() poll_mock = mock.MagicMock() def reset_mocks(): gsc_mock.reset_mock() ito_mock.reset_mock() select_mock.reset_mock() poll_mock.reset_mock() client_mock.reset_mock() tran_mock.reset_mock() chan_mock.reset_mock() select_mock.return_value = poll_mock gsc_mock.return_value = client_mock ito_mock.return_value = True client_mock.get_transport.return_value = tran_mock tran_mock.open_session.return_value = chan_mock poll_mock.poll.side_effect = [ [0, 0, 0] ] # Test for a timeout condition immediately raised client = ssh.Client('localhost', 'root', timeout=2) with testtools.ExpectedException(exceptions.TimeoutException): client.exec_command("test") chan_mock.fileno.assert_called_once_with() chan_mock.exec_command.assert_called_once_with("test") chan_mock.shutdown_write.assert_called_once_with() SELECT_POLLIN = 1 poll_mock.register.assert_called_once_with(chan_mock, SELECT_POLLIN) poll_mock.poll.assert_called_once_with(10) # Test for proper reading of STDOUT and STDERROR and closing # of all file descriptors. reset_mocks() select_mock.return_value = poll_mock gsc_mock.return_value = client_mock ito_mock.return_value = False client_mock.get_transport.return_value = tran_mock tran_mock.open_session.return_value = chan_mock poll_mock.poll.side_effect = [ [1, 0, 0] ] closed_prop = mock.PropertyMock(return_value=True) type(chan_mock).closed = closed_prop chan_mock.recv_exit_status.return_value = 0 chan_mock.recv.return_value = '' chan_mock.recv_stderr.return_value = '' client = ssh.Client('localhost', 'root', timeout=2) client.exec_command("test") chan_mock.fileno.assert_called_once_with() chan_mock.exec_command.assert_called_once_with("test") chan_mock.shutdown_write.assert_called_once_with() SELECT_POLLIN = 1 poll_mock.register.assert_called_once_with(chan_mock, SELECT_POLLIN) poll_mock.poll.assert_called_once_with(10) chan_mock.recv_ready.assert_called_once_with() chan_mock.recv.assert_called_once_with(1024) chan_mock.recv_stderr_ready.assert_called_once_with() chan_mock.recv_stderr.assert_called_once_with(1024) chan_mock.recv_exit_status.assert_called_once_with() closed_prop.assert_called_once_with() tempest-2014.1.dev4108.gf22b6cc/tempest/tests/fake_auth_provider.py0000664000175000017500000000206012332757070025131 0ustar chuckchuck00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.tests import fake_credentials def get_default_credentials(credential_type, fill_in=True): return fake_credentials.FakeCredentials() def get_credentials(credential_type=None, fill_in=True, **kwargs): return fake_credentials.FakeCredentials() class FakeAuthProvider(object): def auth_request(self, method, url, headers=None, body=None, filters=None): return url, headers, body tempest-2014.1.dev4108.gf22b6cc/tempest/tests/fake_config.py0000664000175000017500000000523212332757070023527 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 from oslo.config import cfg from tempest import config from tempest.openstack.common.fixture import config as conf_fixture from tempest.openstack.common import importutils class ConfigFixture(conf_fixture.Config): def __init__(self): config.register_opts() # Register locking options importutils.import_module('tempest.openstack.common.lockutils') super(ConfigFixture, self).__init__() def setUp(self): super(ConfigFixture, self).setUp() self.conf.set_default('build_interval', 10, group='compute') self.conf.set_default('build_timeout', 10, group='compute') self.conf.set_default('disable_ssl_certificate_validation', True, group='identity') self.conf.set_default('uri', 'http://fake_uri.com/auth', group='identity') self.conf.set_default('uri_v3', 'http://fake_uri_v3.com/auth', group='identity') self.conf.set_default('neutron', True, group='service_available') self.conf.set_default('heat', True, group='service_available') if not os.path.exists(str(os.environ.get('OS_TEST_LOCK_PATH'))): os.mkdir(str(os.environ.get('OS_TEST_LOCK_PATH'))) self.conf.set_default('lock_path', str(os.environ.get('OS_TEST_LOCK_PATH'))) self.conf.set_default('auth_version', 'v2', group='identity') for config_option in ['username', 'password', 'tenant_name']: # Identity group items for prefix in ['', 'alt_', 'admin_']: self.conf.set_default(prefix + config_option, 'fake_' + config_option, group='identity') # Compute Admin group items self.conf.set_default(config_option, 'fake_' + config_option, group='compute-admin') class FakePrivate(config.TempestConfigPrivate): def __init__(self): cfg.CONF([], default_config_files=[]) self._set_attrs() tempest-2014.1.dev4108.gf22b6cc/tempest/tests/files/0000775000175000017500000000000012332757136022025 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/files/testr-conf0000664000175000017500000000026512332757070024034 0ustar chuckchuck00000000000000[DEFAULT] test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list group_regex=([^\.]*\.)* tempest-2014.1.dev4108.gf22b6cc/tempest/tests/files/setup.cfg0000664000175000017500000000114212332757070023641 0ustar chuckchuck00000000000000[metadata] name = tempest_unit_tests version = 1 summary = Fake Project for testing wrapper scripts author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 [global] setup-hooks = pbr.hooks.setup_hook tempest-2014.1.dev4108.gf22b6cc/tempest/tests/files/passing-tests0000664000175000017500000000147312332757070024556 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 testtools class FakeTestClass(testtools.TestCase): def test_pass(self): self.assertTrue(True) def test_pass_list(self): test_list = ['test', 'a', 'b'] self.assertIn('test', test_list) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/files/__init__.py0000664000175000017500000000000012332757070024121 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/files/failing-tests0000664000175000017500000000147412332757070024524 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 testtools class FakeTestClass(testtools.TestCase): def test_pass(self): self.assertTrue(False) def test_pass_list(self): test_list = ['test', 'a', 'b'] self.assertIn('fail', test_list) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_rest_client.py0000664000175000017500000005221512332757070024651 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 httplib2 import json from tempest.common import rest_client from tempest.common import xml_utils as xml from tempest import config from tempest import exceptions from tempest.openstack.common.fixture import mockpatch from tempest.tests import base from tempest.tests import fake_auth_provider from tempest.tests import fake_config from tempest.tests import fake_http class BaseRestClientTestClass(base.TestCase): url = 'fake_endpoint' def _get_region(self): return 'fake region' def setUp(self): super(BaseRestClientTestClass, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.rest_client = rest_client.RestClient( fake_auth_provider.FakeAuthProvider()) self.stubs.Set(httplib2.Http, 'request', self.fake_http.request) self.useFixture(mockpatch.PatchObject(self.rest_client, '_get_region', side_effect=self._get_region())) self.useFixture(mockpatch.PatchObject(self.rest_client, '_log_request')) class TestRestClientHTTPMethods(BaseRestClientTestClass): def setUp(self): self.fake_http = fake_http.fake_httplib2() super(TestRestClientHTTPMethods, self).setUp() self.useFixture(mockpatch.PatchObject(self.rest_client, '_error_checker')) def test_post(self): __, return_dict = self.rest_client.post(self.url, {}, {}) self.assertEqual('POST', return_dict['method']) def test_get(self): __, return_dict = self.rest_client.get(self.url) self.assertEqual('GET', return_dict['method']) def test_delete(self): __, return_dict = self.rest_client.delete(self.url) self.assertEqual('DELETE', return_dict['method']) def test_patch(self): __, return_dict = self.rest_client.patch(self.url, {}, {}) self.assertEqual('PATCH', return_dict['method']) def test_put(self): __, return_dict = self.rest_client.put(self.url, {}, {}) self.assertEqual('PUT', return_dict['method']) def test_head(self): self.useFixture(mockpatch.PatchObject(self.rest_client, 'response_checker')) __, return_dict = self.rest_client.head(self.url) self.assertEqual('HEAD', return_dict['method']) def test_copy(self): __, return_dict = self.rest_client.copy(self.url) self.assertEqual('COPY', return_dict['method']) class TestRestClientNotFoundHandling(BaseRestClientTestClass): def setUp(self): self.fake_http = fake_http.fake_httplib2(404) super(TestRestClientNotFoundHandling, self).setUp() def test_post(self): self.assertRaises(exceptions.NotFound, self.rest_client.post, self.url, {}, {}) class TestRestClientHeadersJSON(TestRestClientHTTPMethods): TYPE = "json" def _verify_headers(self, resp): self.assertEqual(self.rest_client._get_type(), self.TYPE) resp = dict((k.lower(), v) for k, v in resp.iteritems()) self.assertEqual(self.header_value, resp['accept']) self.assertEqual(self.header_value, resp['content-type']) def setUp(self): super(TestRestClientHeadersJSON, self).setUp() self.rest_client.TYPE = self.TYPE self.header_value = 'application/%s' % self.rest_client._get_type() def test_post(self): resp, __ = self.rest_client.post(self.url, {}) self._verify_headers(resp) def test_get(self): resp, __ = self.rest_client.get(self.url) self._verify_headers(resp) def test_delete(self): resp, __ = self.rest_client.delete(self.url) self._verify_headers(resp) def test_patch(self): resp, __ = self.rest_client.patch(self.url, {}) self._verify_headers(resp) def test_put(self): resp, __ = self.rest_client.put(self.url, {}) self._verify_headers(resp) def test_head(self): self.useFixture(mockpatch.PatchObject(self.rest_client, 'response_checker')) resp, __ = self.rest_client.head(self.url) self._verify_headers(resp) def test_copy(self): resp, __ = self.rest_client.copy(self.url) self._verify_headers(resp) class TestRestClientUpdateHeaders(BaseRestClientTestClass): def setUp(self): self.fake_http = fake_http.fake_httplib2() super(TestRestClientUpdateHeaders, self).setUp() self.useFixture(mockpatch.PatchObject(self.rest_client, '_error_checker')) self.headers = {'X-Configuration-Session': 'session_id'} def test_post_update_headers(self): __, return_dict = self.rest_client.post(self.url, {}, extra_headers=True, headers=self.headers) self.assertDictContainsSubset( {'X-Configuration-Session': 'session_id', 'Content-Type': 'application/json', 'Accept': 'application/json'}, return_dict['headers'] ) def test_get_update_headers(self): __, return_dict = self.rest_client.get(self.url, extra_headers=True, headers=self.headers) self.assertDictContainsSubset( {'X-Configuration-Session': 'session_id', 'Content-Type': 'application/json', 'Accept': 'application/json'}, return_dict['headers'] ) def test_delete_update_headers(self): __, return_dict = self.rest_client.delete(self.url, extra_headers=True, headers=self.headers) self.assertDictContainsSubset( {'X-Configuration-Session': 'session_id', 'Content-Type': 'application/json', 'Accept': 'application/json'}, return_dict['headers'] ) def test_patch_update_headers(self): __, return_dict = self.rest_client.patch(self.url, {}, extra_headers=True, headers=self.headers) self.assertDictContainsSubset( {'X-Configuration-Session': 'session_id', 'Content-Type': 'application/json', 'Accept': 'application/json'}, return_dict['headers'] ) def test_put_update_headers(self): __, return_dict = self.rest_client.put(self.url, {}, extra_headers=True, headers=self.headers) self.assertDictContainsSubset( {'X-Configuration-Session': 'session_id', 'Content-Type': 'application/json', 'Accept': 'application/json'}, return_dict['headers'] ) def test_head_update_headers(self): self.useFixture(mockpatch.PatchObject(self.rest_client, 'response_checker')) __, return_dict = self.rest_client.head(self.url, extra_headers=True, headers=self.headers) self.assertDictContainsSubset( {'X-Configuration-Session': 'session_id', 'Content-Type': 'application/json', 'Accept': 'application/json'}, return_dict['headers'] ) def test_copy_update_headers(self): __, return_dict = self.rest_client.copy(self.url, extra_headers=True, headers=self.headers) self.assertDictContainsSubset( {'X-Configuration-Session': 'session_id', 'Content-Type': 'application/json', 'Accept': 'application/json'}, return_dict['headers'] ) class TestRestClientHeadersXML(TestRestClientHeadersJSON): TYPE = "xml" # These two tests are needed in one exemplar def test_send_json_accept_xml(self): resp, __ = self.rest_client.get(self.url, self.rest_client.get_headers("xml", "json")) resp = dict((k.lower(), v) for k, v in resp.iteritems()) self.assertEqual("application/json", resp["content-type"]) self.assertEqual("application/xml", resp["accept"]) def test_send_xml_accept_json(self): resp, __ = self.rest_client.get(self.url, self.rest_client.get_headers("json", "xml")) resp = dict((k.lower(), v) for k, v in resp.iteritems()) self.assertEqual("application/json", resp["accept"]) self.assertEqual("application/xml", resp["content-type"]) class TestRestClientParseRespXML(BaseRestClientTestClass): TYPE = "xml" keys = ["fake_key1", "fake_key2"] values = ["fake_value1", "fake_value2"] item_expected = dict((key, value) for (key, value) in zip(keys, values)) list_expected = {"body_list": [ {keys[0]: values[0]}, {keys[1]: values[1]}, ]} dict_expected = {"body_dict": { keys[0]: values[0], keys[1]: values[1], }} def setUp(self): self.fake_http = fake_http.fake_httplib2() super(TestRestClientParseRespXML, self).setUp() self.rest_client.TYPE = self.TYPE def test_parse_resp_body_item(self): body_item = xml.Element("item", **self.item_expected) body = self.rest_client._parse_resp(str(xml.Document(body_item))) self.assertEqual(self.item_expected, body) def test_parse_resp_body_list(self): self.rest_client.list_tags = ["fake_list", ] body_list = xml.Element(self.rest_client.list_tags[0]) for i in range(2): body_list.append(xml.Element("fake_item", **self.list_expected["body_list"][i])) body = self.rest_client._parse_resp(str(xml.Document(body_list))) self.assertEqual(self.list_expected["body_list"], body) def test_parse_resp_body_dict(self): self.rest_client.dict_tags = ["fake_dict", ] body_dict = xml.Element(self.rest_client.dict_tags[0]) for i in range(2): body_dict.append(xml.Element("fake_item", xml.Text(self.values[i]), key=self.keys[i])) body = self.rest_client._parse_resp(str(xml.Document(body_dict))) self.assertEqual(self.dict_expected["body_dict"], body) class TestRestClientParseRespJSON(TestRestClientParseRespXML): TYPE = "json" def test_parse_resp_body_item(self): body = self.rest_client._parse_resp(json.dumps(self.item_expected)) self.assertEqual(self.item_expected, body) def test_parse_resp_body_list(self): body = self.rest_client._parse_resp(json.dumps(self.list_expected)) self.assertEqual(self.list_expected["body_list"], body) def test_parse_resp_body_dict(self): body = self.rest_client._parse_resp(json.dumps(self.dict_expected)) self.assertEqual(self.dict_expected["body_dict"], body) def test_parse_resp_two_top_keys(self): dict_two_keys = self.dict_expected.copy() dict_two_keys.update({"second_key": ""}) body = self.rest_client._parse_resp(json.dumps(dict_two_keys)) self.assertEqual(dict_two_keys, body) def test_parse_resp_one_top_key_without_list_or_dict(self): data = {"one_top_key": "not_list_or_dict_value"} body = self.rest_client._parse_resp(json.dumps(data)) self.assertEqual(data, body) class TestRestClientErrorCheckerJSON(base.TestCase): c_type = "application/json" def set_data(self, r_code, enc=None, r_body=None): if enc is None: enc = self.c_type resp_dict = {'status': r_code, 'content-type': enc} resp = httplib2.Response(resp_dict) data = { "method": "fake_method", "url": "fake_url", "headers": "fake_headers", "body": "fake_body", "resp": resp, "resp_body": '{"resp_body": "fake_resp_body"}', } if r_body is not None: data.update({"resp_body": r_body}) return data def setUp(self): super(TestRestClientErrorCheckerJSON, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) self.rest_client = rest_client.RestClient( fake_auth_provider.FakeAuthProvider()) def test_response_less_than_400(self): self.rest_client._error_checker(**self.set_data("399")) def test_response_400(self): self.assertRaises(exceptions.BadRequest, self.rest_client._error_checker, **self.set_data("400")) def test_response_401(self): self.assertRaises(exceptions.Unauthorized, self.rest_client._error_checker, **self.set_data("401")) def test_response_403(self): self.assertRaises(exceptions.Unauthorized, self.rest_client._error_checker, **self.set_data("403")) def test_response_404(self): self.assertRaises(exceptions.NotFound, self.rest_client._error_checker, **self.set_data("404")) def test_response_409(self): self.assertRaises(exceptions.Conflict, self.rest_client._error_checker, **self.set_data("409")) def test_response_413(self): self.assertRaises(exceptions.OverLimit, self.rest_client._error_checker, **self.set_data("413")) def test_response_422(self): self.assertRaises(exceptions.UnprocessableEntity, self.rest_client._error_checker, **self.set_data("422")) def test_response_500_with_text(self): # _parse_resp is expected to return 'str' self.assertRaises(exceptions.ServerFault, self.rest_client._error_checker, **self.set_data("500")) def test_response_501_with_text(self): self.assertRaises(exceptions.ServerFault, self.rest_client._error_checker, **self.set_data("501")) def test_response_500_with_dict(self): r_body = '{"resp_body": {"err": "fake_resp_body"}}' self.assertRaises(exceptions.ServerFault, self.rest_client._error_checker, **self.set_data("500", r_body=r_body)) def test_response_501_with_dict(self): r_body = '{"resp_body": {"err": "fake_resp_body"}}' self.assertRaises(exceptions.ServerFault, self.rest_client._error_checker, **self.set_data("501", r_body=r_body)) def test_response_bigger_than_400(self): # Any response code, that bigger than 400, and not in # (401, 403, 404, 409, 413, 422, 500, 501) self.assertRaises(exceptions.UnexpectedResponseCode, self.rest_client._error_checker, **self.set_data("402")) class TestRestClientErrorCheckerXML(TestRestClientErrorCheckerJSON): c_type = "application/xml" class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON): c_type = "text/plain" def test_fake_content_type(self): # This test is required only in one exemplar # Any response code, that bigger than 400, and not in # (401, 403, 404, 409, 413, 422, 500, 501) self.assertRaises(exceptions.InvalidContentType, self.rest_client._error_checker, **self.set_data("405", enc="fake_enc")) class TestRestClientUtils(BaseRestClientTestClass): def _is_resource_deleted(self, resource_id): if not isinstance(self.retry_pass, int): return False if self.retry_count >= self.retry_pass: return True self.retry_count = self.retry_count + 1 return False def setUp(self): self.fake_http = fake_http.fake_httplib2() super(TestRestClientUtils, self).setUp() self.retry_count = 0 self.retry_pass = None self.original_deleted_method = self.rest_client.is_resource_deleted self.rest_client.is_resource_deleted = self._is_resource_deleted def test_wait_for_resource_deletion(self): self.retry_pass = 2 # Ensure timeout long enough for loop execution to hit retry count self.rest_client.build_timeout = 500 sleep_mock = self.patch('time.sleep') self.rest_client.wait_for_resource_deletion('1234') self.assertEqual(len(sleep_mock.mock_calls), 2) def test_wait_for_resource_deletion_not_deleted(self): self.patch('time.sleep') # Set timeout to be very quick to force exception faster self.rest_client.build_timeout = 1 self.assertRaises(exceptions.TimeoutException, self.rest_client.wait_for_resource_deletion, '1234') def test_wait_for_deletion_with_unimplemented_deleted_method(self): self.rest_client.is_resource_deleted = self.original_deleted_method self.assertRaises(NotImplementedError, self.rest_client.wait_for_resource_deletion, '1234') class TestNegativeRestClient(BaseRestClientTestClass): def setUp(self): self.fake_http = fake_http.fake_httplib2() super(TestNegativeRestClient, self).setUp() self.negative_rest_client = rest_client.NegativeRestClient( fake_auth_provider.FakeAuthProvider()) self.useFixture(mockpatch.PatchObject(self.negative_rest_client, '_log_request')) def test_post(self): __, return_dict = self.negative_rest_client.send_request('POST', self.url, [], {}) self.assertEqual('POST', return_dict['method']) def test_get(self): __, return_dict = self.negative_rest_client.send_request('GET', self.url, []) self.assertEqual('GET', return_dict['method']) def test_delete(self): __, return_dict = self.negative_rest_client.send_request('DELETE', self.url, []) self.assertEqual('DELETE', return_dict['method']) def test_patch(self): __, return_dict = self.negative_rest_client.send_request('PATCH', self.url, [], {}) self.assertEqual('PATCH', return_dict['method']) def test_put(self): __, return_dict = self.negative_rest_client.send_request('PUT', self.url, [], {}) self.assertEqual('PUT', return_dict['method']) def test_head(self): self.useFixture(mockpatch.PatchObject(self.negative_rest_client, 'response_checker')) __, return_dict = self.negative_rest_client.send_request('HEAD', self.url, []) self.assertEqual('HEAD', return_dict['method']) def test_copy(self): __, return_dict = self.negative_rest_client.send_request('COPY', self.url, []) self.assertEqual('COPY', return_dict['method']) def test_other(self): self.assertRaises(AssertionError, self.negative_rest_client.send_request, 'OTHER', self.url, []) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/cmd/0000775000175000017500000000000012332757136021466 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/cmd/__init__.py0000664000175000017500000000000012332757070023562 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/cmd/test_verify_tempest_config.py0000664000175000017500000004607412332757070027501 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # # 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 json import mock from tempest.cmd import verify_tempest_config from tempest import config from tempest.openstack.common.fixture import mockpatch from tempest.tests import base from tempest.tests import fake_config class TestGetAPIVersions(base.TestCase): def test_url_grab_versioned_nova_nossl(self): base_url = 'http://127.0.0.1:8774/v2/' endpoint = verify_tempest_config._get_unversioned_endpoint(base_url) self.assertEqual('http://127.0.0.1:8774', endpoint) def test_url_grab_versioned_nova_ssl(self): base_url = 'https://127.0.0.1:8774/v3/' endpoint = verify_tempest_config._get_unversioned_endpoint(base_url) self.assertEqual('https://127.0.0.1:8774', endpoint) class TestDiscovery(base.TestCase): def setUp(self): super(TestDiscovery, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) def test_get_keystone_api_versions(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': {'values': [{'id': 'v2.0'}, {'id': 'v3.0'}]}} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() versions = verify_tempest_config._get_api_versions(fake_os, 'keystone') self.assertIn('v2.0', versions) self.assertIn('v3.0', versions) def test_get_cinder_api_versions(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v2.0'}]} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() versions = verify_tempest_config._get_api_versions(fake_os, 'cinder') self.assertIn('v1.0', versions) self.assertIn('v2.0', versions) def test_get_nova_versions(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() versions = verify_tempest_config._get_api_versions(fake_os, 'nova') self.assertIn('v2.0', versions) self.assertIn('v3.0', versions) def test_verify_keystone_api_versions_no_v3(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': {'values': [{'id': 'v2.0'}]}} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_keystone_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v3', 'identity_feature_enabled', False, True) def test_verify_keystone_api_versions_no_v2(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': {'values': [{'id': 'v3.0'}]}} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_keystone_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v2', 'identity_feature_enabled', False, True) def test_verify_cinder_api_versions_no_v2(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v1.0'}]} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_cinder_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v2', 'volume_feature_enabled', False, True) def test_verify_cinder_api_versions_no_v1(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v2.0'}]} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_cinder_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v1', 'volume_feature_enabled', False, True) def test_verify_nova_versions(self): self.useFixture(mockpatch.PatchObject( verify_tempest_config, '_get_unversioned_endpoint', return_value='http://fake_endpoint:5000')) fake_resp = {'versions': [{'id': 'v2.0'}]} fake_resp = json.dumps(fake_resp) self.useFixture(mockpatch.PatchObject( verify_tempest_config.RAW_HTTP, 'request', return_value=(None, fake_resp))) fake_os = mock.MagicMock() with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_nova_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v3', 'compute_feature_enabled', False, True) def test_verify_glance_version_no_v2_with_v1_1(self): def fake_get_versions(): return (None, ['v1.1']) fake_os = mock.MagicMock() fake_os.image_client.get_versions = fake_get_versions with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_glance_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v2', 'image_feature_enabled', False, True) def test_verify_glance_version_no_v2_with_v1_0(self): def fake_get_versions(): return (None, ['v1.0']) fake_os = mock.MagicMock() fake_os.image_client.get_versions = fake_get_versions with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_glance_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v2', 'image_feature_enabled', False, True) def test_verify_glance_version_no_v1(self): def fake_get_versions(): return (None, ['v2.0']) fake_os = mock.MagicMock() fake_os.image_client.get_versions = fake_get_versions with mock.patch.object(verify_tempest_config, 'print_and_or_update') as print_mock: verify_tempest_config.verify_glance_api_versions(fake_os, True) print_mock.assert_called_once_with('api_v1', 'image_feature_enabled', False, True) def test_verify_extensions_neutron(self): def fake_list_extensions(): return (None, {'extensions': [{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.network_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'neutron', {}) self.assertIn('neutron', results) self.assertIn('fake1', results['neutron']) self.assertTrue(results['neutron']['fake1']) self.assertIn('fake2', results['neutron']) self.assertTrue(results['neutron']['fake2']) self.assertIn('fake3', results['neutron']) self.assertFalse(results['neutron']['fake3']) self.assertIn('not_fake', results['neutron']) self.assertFalse(results['neutron']['not_fake']) def test_verify_extensions_neutron_all(self): def fake_list_extensions(): return (None, {'extensions': [{'alias': 'fake1'}, {'alias': 'fake2'}, {'alias': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.network_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'neutron', {}) self.assertIn('neutron', results) self.assertIn('extensions', results['neutron']) self.assertEqual(['fake1', 'fake2', 'not_fake'], results['neutron']['extensions']) def test_verify_extensions_cinder(self): def fake_list_extensions(): return (None, {'extensions': [{'name': 'fake1'}, {'name': 'fake2'}, {'name': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.volumes_extension_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'cinder', {}) self.assertIn('cinder', results) self.assertIn('fake1', results['cinder']) self.assertTrue(results['cinder']['fake1']) self.assertIn('fake2', results['cinder']) self.assertTrue(results['cinder']['fake2']) self.assertIn('fake3', results['cinder']) self.assertFalse(results['cinder']['fake3']) self.assertIn('not_fake', results['cinder']) self.assertFalse(results['cinder']['not_fake']) def test_verify_extensions_cinder_all(self): def fake_list_extensions(): return (None, {'extensions': [{'name': 'fake1'}, {'name': 'fake2'}, {'name': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.volumes_extension_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'cinder', {}) self.assertIn('cinder', results) self.assertIn('extensions', results['cinder']) self.assertEqual(['fake1', 'fake2', 'not_fake'], results['cinder']['extensions']) def test_verify_extensions_nova(self): def fake_list_extensions(): return (None, {'extensions': [{'name': 'fake1'}, {'name': 'fake2'}, {'name': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.extensions_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'nova', {}) self.assertIn('nova', results) self.assertIn('fake1', results['nova']) self.assertTrue(results['nova']['fake1']) self.assertIn('fake2', results['nova']) self.assertTrue(results['nova']['fake2']) self.assertIn('fake3', results['nova']) self.assertFalse(results['nova']['fake3']) self.assertIn('not_fake', results['nova']) self.assertFalse(results['nova']['not_fake']) def test_verify_extensions_nova_all(self): def fake_list_extensions(): return (None, {'extensions': [{'name': 'fake1'}, {'name': 'fake2'}, {'name': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.extensions_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'nova', {}) self.assertIn('nova', results) self.assertIn('extensions', results['nova']) self.assertEqual(['fake1', 'fake2', 'not_fake'], results['nova']['extensions']) def test_verify_extensions_nova_v3(self): def fake_list_extensions(): return (None, {'extensions': [{'name': 'fake1'}, {'name': 'fake2'}, {'name': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.extensions_v3_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'nova_v3', {}) self.assertIn('nova_v3', results) self.assertIn('fake1', results['nova_v3']) self.assertTrue(results['nova_v3']['fake1']) self.assertIn('fake2', results['nova_v3']) self.assertTrue(results['nova_v3']['fake2']) self.assertIn('fake3', results['nova_v3']) self.assertFalse(results['nova_v3']['fake3']) self.assertIn('not_fake', results['nova_v3']) self.assertFalse(results['nova_v3']['not_fake']) def test_verify_extensions_nova_v3_all(self): def fake_list_extensions(): return (None, {'extensions': [{'name': 'fake1'}, {'name': 'fake2'}, {'name': 'not_fake'}]}) fake_os = mock.MagicMock() fake_os.extensions_v3_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'nova_v3', {}) self.assertIn('nova_v3', results) self.assertIn('extensions', results['nova_v3']) self.assertEqual(['fake1', 'fake2', 'not_fake'], results['nova_v3']['extensions']) def test_verify_extensions_swift(self): def fake_list_extensions(): return (None, {'fake1': 'metadata', 'fake2': 'metadata', 'not_fake': 'metadata', 'swift': 'metadata'}) fake_os = mock.MagicMock() fake_os.account_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['fake1', 'fake2', 'fake3']))) results = verify_tempest_config.verify_extensions(fake_os, 'swift', {}) self.assertIn('swift', results) self.assertIn('fake1', results['swift']) self.assertTrue(results['swift']['fake1']) self.assertIn('fake2', results['swift']) self.assertTrue(results['swift']['fake2']) self.assertIn('fake3', results['swift']) self.assertFalse(results['swift']['fake3']) self.assertIn('not_fake', results['swift']) self.assertFalse(results['swift']['not_fake']) def test_verify_extensions_swift_all(self): def fake_list_extensions(): return (None, {'fake1': 'metadata', 'fake2': 'metadata', 'not_fake': 'metadata', 'swift': 'metadata'}) fake_os = mock.MagicMock() fake_os.account_client.list_extensions = fake_list_extensions self.useFixture(mockpatch.PatchObject( verify_tempest_config, 'get_enabled_extensions', return_value=(['all']))) results = verify_tempest_config.verify_extensions(fake_os, 'swift', {}) self.assertIn('swift', results) self.assertIn('extensions', results['swift']) self.assertEqual(['not_fake', 'fake1', 'fake2'], results['swift']['extensions']) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/__init__.py0000664000175000017500000000000012332757070023017 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_credentials.py0000664000175000017500000002247112332757070024634 0ustar chuckchuck00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from oslo.config import cfg from tempest import auth from tempest.common import http from tempest.common import tempest_fixtures as fixtures from tempest import config from tempest import exceptions from tempest.tests import base from tempest.tests import fake_config from tempest.tests import fake_http from tempest.tests import fake_identity class CredentialsTests(base.TestCase): attributes = {} credentials_class = auth.Credentials def _get_credentials(self, attributes=None): if attributes is None: attributes = self.attributes return self.credentials_class(**attributes) def setUp(self): super(CredentialsTests, self).setUp() self.fake_http = fake_http.fake_httplib2(return_type=200) self.stubs.Set(http.ClosingHttp, 'request', self.fake_http.request) self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) def test_create(self): creds = self._get_credentials() self.assertEqual(self.attributes, creds._initial) def test_create_invalid_attr(self): self.assertRaises(exceptions.InvalidCredentials, self._get_credentials, attributes=dict(invalid='fake')) def test_default(self): self.useFixture(fixtures.LockFixture('auth_version')) for ctype in self.credentials_class.TYPES: self.assertRaises(NotImplementedError, self.credentials_class.get_default, credentials_type=ctype) def test_invalid_default(self): self.assertRaises(exceptions.InvalidCredentials, auth.Credentials.get_default, credentials_type='invalid_type') def test_is_valid(self): creds = self._get_credentials() self.assertRaises(NotImplementedError, creds.is_valid) class KeystoneV2CredentialsTests(CredentialsTests): attributes = { 'username': 'fake_username', 'password': 'fake_password', 'tenant_name': 'fake_tenant_name' } identity_response = fake_identity._fake_v2_response credentials_class = auth.KeystoneV2Credentials def setUp(self): super(KeystoneV2CredentialsTests, self).setUp() self.stubs.Set(http.ClosingHttp, 'request', self.identity_response) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) def _verify_credentials(self, credentials_class, filled=True, creds_dict=None): def _check(credentials): # Check the right version of credentials has been returned self.assertIsInstance(credentials, credentials_class) # Check the id attributes are filled in attributes = [x for x in credentials.ATTRIBUTES if ( '_id' in x and x != 'domain_id')] for attr in attributes: if filled: self.assertIsNotNone(getattr(credentials, attr)) else: self.assertIsNone(getattr(credentials, attr)) if creds_dict is None: for ctype in auth.Credentials.TYPES: creds = auth.get_default_credentials(credential_type=ctype, fill_in=filled) _check(creds) else: creds = auth.get_credentials(fill_in=filled, **creds_dict) _check(creds) def test_get_default_credentials(self): self.useFixture(fixtures.LockFixture('auth_version')) self._verify_credentials(credentials_class=self.credentials_class) def test_get_credentials(self): self.useFixture(fixtures.LockFixture('auth_version')) self._verify_credentials(credentials_class=self.credentials_class, creds_dict=self.attributes) def test_get_credentials_not_filled(self): self.useFixture(fixtures.LockFixture('auth_version')) self._verify_credentials(credentials_class=self.credentials_class, filled=False, creds_dict=self.attributes) def test_is_valid(self): creds = self._get_credentials() self.assertTrue(creds.is_valid()) def test_is_not_valid(self): creds = self._get_credentials() for attr in self.attributes.keys(): delattr(creds, attr) self.assertFalse(creds.is_valid(), "Credentials should be invalid without %s" % attr) def test_default(self): self.useFixture(fixtures.LockFixture('auth_version')) for ctype in self.credentials_class.TYPES: creds = self.credentials_class.get_default(credentials_type=ctype) for attr in self.attributes.keys(): # Default configuration values related to credentials # are defined as fake_* in fake_config.py self.assertEqual(getattr(creds, attr), 'fake_' + attr) def test_reset_all_attributes(self): creds = self._get_credentials() initial_creds = copy.deepcopy(creds) set_attr = creds.__dict__.keys() missing_attr = set(creds.ATTRIBUTES).difference(set_attr) # Set all unset attributes, then reset for attr in missing_attr: setattr(creds, attr, 'fake' + attr) creds.reset() # Check reset credentials are same as initial ones self.assertEqual(creds, initial_creds) def test_reset_single_attribute(self): creds = self._get_credentials() initial_creds = copy.deepcopy(creds) set_attr = creds.__dict__.keys() missing_attr = set(creds.ATTRIBUTES).difference(set_attr) # Set one unset attributes, then reset for attr in missing_attr: setattr(creds, attr, 'fake' + attr) creds.reset() # Check reset credentials are same as initial ones self.assertEqual(creds, initial_creds) class KeystoneV3CredentialsTests(KeystoneV2CredentialsTests): attributes = { 'username': 'fake_username', 'password': 'fake_password', 'project_name': 'fake_project_name', 'user_domain_name': 'fake_domain_name' } credentials_class = auth.KeystoneV3Credentials identity_response = fake_identity._fake_v3_response def setUp(self): super(KeystoneV3CredentialsTests, self).setUp() # Additional config items reset by cfg fixture after each test cfg.CONF.set_default('auth_version', 'v3', group='identity') # Identity group items for prefix in ['', 'alt_', 'admin_']: cfg.CONF.set_default(prefix + 'domain_name', 'fake_domain_name', group='identity') # Compute Admin group items cfg.CONF.set_default('domain_name', 'fake_domain_name', group='compute-admin') def test_default(self): self.useFixture(fixtures.LockFixture('auth_version')) for ctype in self.credentials_class.TYPES: creds = self.credentials_class.get_default(credentials_type=ctype) for attr in self.attributes.keys(): if attr == 'project_name': config_value = 'fake_tenant_name' elif attr == 'user_domain_name': config_value = 'fake_domain_name' else: config_value = 'fake_' + attr self.assertEqual(getattr(creds, attr), config_value) def test_synced_attributes(self): attributes = self.attributes # Create V3 credentials with tenant instead of project, and user_domain for attr in ['project_id', 'user_domain_id']: attributes[attr] = 'fake_' + attr creds = self._get_credentials(attributes) self.assertEqual(creds.project_name, creds.tenant_name) self.assertEqual(creds.project_id, creds.tenant_id) self.assertEqual(creds.user_domain_name, creds.project_domain_name) self.assertEqual(creds.user_domain_id, creds.project_domain_id) # Replace user_domain with project_domain del attributes['user_domain_name'] del attributes['user_domain_id'] del attributes['project_name'] del attributes['project_id'] for attr in ['project_domain_name', 'project_domain_id', 'tenant_name', 'tenant_id']: attributes[attr] = 'fake_' + attr self.assertEqual(creds.tenant_name, creds.project_name) self.assertEqual(creds.tenant_id, creds.project_id) self.assertEqual(creds.project_domain_name, creds.user_domain_name) self.assertEqual(creds.project_domain_id, creds.user_domain_id) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_compute_xml_common.py0000664000175000017500000000554612332757070026247 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 lxml import etree from tempest.common import xml_utils as common from tempest.tests import base class TestXMLParser(base.TestCase): def test_xml_to_json_parser_bool_value(self): node = etree.fromstring(''' False True ''') body = common.xml_to_json(node) self.assertEqual(body['admin_state_up'], False) self.assertEqual(body['fake_state_up'], True) def test_xml_to_json_parser_int_value(self): node = etree.fromstring(''' 4 3 ''') body = common.xml_to_json(node) self.assertEqual(body['delay'], 4L) self.assertEqual(body['max_retries'], 3) def test_xml_to_json_parser_text_value(self): node = etree.fromstring(''' ACTIVE ''') body = common.xml_to_json(node) self.assertEqual(body['status'], 'ACTIVE') def test_xml_to_json_parser_list_as_value(self): node = etree.fromstring(''' first_element second_element ''') body = common.xml_to_json(node, 'elements') self.assertEqual(body['elements'], ['first_element', 'second_element']) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_hacking.py0000664000175000017500000001142012332757070023733 0ustar chuckchuck00000000000000# Copyright 2014 Matthew Treinish # # 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 tempest.hacking import checks from tempest.tests import base class HackingTestCase(base.TestCase): """ This class tests the hacking checks in tempest.hacking.checks by passing strings to the check methods like the pep8/flake8 parser would. The parser loops over each line in the file and then passes the parameters to the check method. The parameter names in the check method dictate what type of object is passed to the check method. The parameter types are:: logical_line: A processed line with the following modifications: - Multi-line statements converted to a single line. - Stripped left and right. - Contents of strings replaced with "xxx" of same length. - Comments removed. physical_line: Raw line of text from the input file. lines: a list of the raw lines from the input file tokens: the tokens that contribute to this logical line line_number: line number in the input file total_lines: number of lines in the input file blank_lines: blank lines before this one indent_char: indentation character in this file (" " or "\t") indent_level: indentation (with tabs expanded to multiples of 8) previous_indent_level: indentation on previous line previous_logical: previous logical line filename: Path of the file being run through pep8 When running a test on a check method the return will be False/None if there is no violation in the sample input. If there is an error a tuple is returned with a position in the line, and a message. So to check the result just assertTrue if the check is expected to fail and assertFalse if it should pass. """ def test_no_setupclass_for_unit_tests(self): self.assertTrue(checks.no_setupclass_for_unit_tests( " def setUpClass(cls):", './tempest/tests/fake_test.py')) self.assertIsNone(checks.no_setupclass_for_unit_tests( " def setUpClass(cls): # noqa", './tempest/tests/fake_test.py')) self.assertFalse(checks.no_setupclass_for_unit_tests( " def setUpClass(cls):", './tempest/api/fake_test.py')) def test_import_no_clients_in_api(self): for client in checks.PYTHON_CLIENTS: string = "import " + client + "client" self.assertTrue(checks.import_no_clients_in_api( string, './tempest/api/fake_test.py')) self.assertFalse(checks.import_no_clients_in_api( string, './tempest/scenario/fake_test.py')) def test_scenario_tests_need_service_tags(self): self.assertFalse(checks.scenario_tests_need_service_tags( 'def test_fake:', './tempest/scenario/test_fake.py', "@test.services('compute')")) self.assertFalse(checks.scenario_tests_need_service_tags( 'def test_fake_test:', './tempest/api/compute/test_fake.py', "@test.services('image')")) self.assertTrue(checks.scenario_tests_need_service_tags( 'def test_fake_test:', './tempest/scenario/test_fake.py', '\n')) def test_no_vi_headers(self): # NOTE(mtreinish) The lines parameter is used only for finding the # line location in the file. So these tests just pass a list of an # arbitrary length to use for verifying the check function. self.assertTrue(checks.no_vi_headers( '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 1, range(250))) self.assertTrue(checks.no_vi_headers( '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 249, range(250))) self.assertFalse(checks.no_vi_headers( '# vim: tabstop=4 shiftwidth=4 softtabstop=4', 149, range(250))) def test_service_tags_not_in_module_path(self): self.assertTrue(checks.service_tags_not_in_module_path( "@test.services('compute')", './tempest/api/compute/fake_test.py')) self.assertFalse(checks.service_tags_not_in_module_path( "@test.services('compute')", './tempest/scenario/compute/fake_test.py')) self.assertFalse(checks.service_tags_not_in_module_path( "@test.services('compute')", './tempest/api/image/fake_test.py')) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/README.rst0000664000175000017500000000200512332757070022404 0ustar chuckchuck00000000000000Tempest Field Guide to Unit tests ================================= What are these tests? --------------------- Unit tests are the self checks for Tempest. They provide functional verification and regression checking for the internal components of tempest. They should be used to just verify that the individual pieces of tempest are working as expected. They should not require an external service to be running and should be able to run solely from the tempest tree. Why are these tests in tempest? ------------------------------- These tests exist to make sure that the mechanisms that we use inside of tempest to are valid and remain functional. They are only here for self validation of tempest. Scope of these tests -------------------- Unit tests should not require an external service to be running or any extra configuration to run. Any state that is required for a test should either be mocked out or created in a temporary test directory. (see test_wrappers.py for an example of using a temporary test directory) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/fake_credentials.py0000664000175000017500000000360212332757070024556 0ustar chuckchuck00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import auth class FakeCredentials(auth.Credentials): def is_valid(self): return True class FakeKeystoneV2Credentials(auth.KeystoneV2Credentials): def __init__(self): creds = dict( username='fake_username', password='fake_password', tenant_name='fake_tenant_name' ) super(FakeKeystoneV2Credentials, self).__init__(**creds) class FakeKeystoneV3Credentials(auth.KeystoneV3Credentials): """ Fake credentials suitable for the Keystone Identity V3 API """ def __init__(self): creds = dict( username='fake_username', password='fake_password', user_domain_name='fake_domain_name', project_name='fake_tenant_name' ) super(FakeKeystoneV3Credentials, self).__init__(**creds) class FakeKeystoneV3DomainCredentials(auth.KeystoneV3Credentials): """ Fake credentials suitable for the Keystone Identity V3 API, with no scope """ def __init__(self): creds = dict( username='fake_username', password='fake_password', user_domain_name='fake_domain_name' ) super(FakeKeystoneV3DomainCredentials, self).__init__(**creds) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_commands.py0000664000175000017500000000643312332757070024140 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock import subprocess from tempest.common import commands from tempest.tests import base class TestCommands(base.TestCase): def setUp(self): super(TestCommands, self).setUp() self.subprocess_args = {'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT} @mock.patch('subprocess.Popen') def test_ip_addr_raw(self, mock): expected = ['/usr/bin/sudo', '-n', 'ip', 'a'] commands.ip_addr_raw() mock.assert_called_once_with(expected, **self.subprocess_args) @mock.patch('subprocess.Popen') def test_ip_route_raw(self, mock): expected = ['/usr/bin/sudo', '-n', 'ip', 'r'] commands.ip_route_raw() mock.assert_called_once_with(expected, **self.subprocess_args) @mock.patch('subprocess.Popen') def test_ip_ns_raw(self, mock): expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'list'] commands.ip_ns_raw() mock.assert_called_once_with(expected, **self.subprocess_args) @mock.patch('subprocess.Popen') def test_iptables_raw(self, mock): table = 'filter' expected = ['/usr/bin/sudo', '-n', 'iptables', '-v', '-S', '-t', '%s' % table] commands.iptables_raw(table) mock.assert_called_once_with(expected, **self.subprocess_args) @mock.patch('subprocess.Popen') def test_ip_ns_list(self, mock): expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'list'] commands.ip_ns_list() mock.assert_called_once_with(expected, **self.subprocess_args) @mock.patch('subprocess.Popen') def test_ip_ns_addr(self, mock): ns_list = commands.ip_ns_list() for ns in ns_list: expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'exec', ns, 'ip', 'a'] commands.ip_ns_addr(ns) mock.assert_called_once_with(expected, **self.subprocess_args) @mock.patch('subprocess.Popen') def test_ip_ns_route(self, mock): ns_list = commands.ip_ns_list() for ns in ns_list: expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'exec', ns, 'ip', 'r'] commands.ip_ns_route(ns) mock.assert_called_once_with(expected, **self.subprocess_args) @mock.patch('subprocess.Popen') def test_iptables_ns(self, mock): table = 'filter' ns_list = commands.ip_ns_list() for ns in ns_list: expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'exec', ns, 'iptables', '-v', '-S', '-t', table] commands.iptables_ns(ns, table) mock.assert_called_once_with(expected, **self.subprocess_args) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/negative/0000775000175000017500000000000012332757136022525 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/negative/test_negative_auto_test.py0000664000175000017500000000541212332757070030026 0ustar chuckchuck00000000000000# Copyright 2014 Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from tempest import config import tempest.test as test from tempest.tests import base from tempest.tests import fake_config class TestNegativeAutoTest(base.TestCase): # Fake entries _interface = 'json' _service = 'compute' fake_input_desc = {"name": "list-flavors-with-detail", "http-method": "GET", "url": "flavors/detail", "json-schema": {"type": "object", "properties": {"minRam": {"type": "integer"}, "minDisk": {"type": "integer"}} }, "resources": ["flavor", "volume", "image"] } def setUp(self): super(TestNegativeAutoTest, self).setUp() self.useFixture(fake_config.ConfigFixture()) self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate) def _check_prop_entries(self, result, entry): entries = [a for a in result if entry in a[0]] self.assertIsNotNone(entries) self.assertIs(len(entries), 2) for entry in entries: self.assertIsNotNone(entry[1]['schema']) def _check_resource_entries(self, result, entry): entries = [a for a in result if entry in a[0]] self.assertIsNotNone(entries) self.assertIs(len(entries), 3) for entry in entries: self.assertIsNotNone(entry[1]['resource']) @mock.patch('tempest.test.NegativeAutoTest.load_schema') def test_generate_scenario(self, open_mock): open_mock.return_value = self.fake_input_desc scenarios = test.NegativeAutoTest.\ generate_scenario(None) self.assertIsInstance(scenarios, list) for scenario in scenarios: self.assertIsInstance(scenario, tuple) self.assertIsInstance(scenario[0], str) self.assertIsInstance(scenario[1], dict) self._check_prop_entries(scenarios, "prop_minRam") self._check_prop_entries(scenarios, "prop_minDisk") self._check_resource_entries(scenarios, "inv_res") tempest-2014.1.dev4108.gf22b6cc/tempest/tests/negative/__init__.py0000664000175000017500000000000012332757070024621 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/negative/test_negative_generators.py0000664000175000017500000001252512332757070030173 0ustar chuckchuck00000000000000# Copyright 2014 Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import jsonschema import mock from tempest.common.generator import base_generator from tempest.common.generator import negative_generator from tempest.common.generator import valid_generator from tempest.tests import base class TestNegativeBasicGenerator(base.TestCase): valid_desc = { "name": "list-flavors-with-detail", "http-method": "GET", "url": "flavors/detail", "json-schema": { "type": "object", "properties": { "minRam": {"type": "integer"}, "minDisk": {"type": "integer"} } }, "resources": ["flavor", "volume", "image"] } minimal_desc = { "name": "list-flavors-with-detail", "http-method": "GET", "url": "flavors/detail", } add_prop_desc = { "name": "list-flavors-with-detail", "http-method": "GET", "url": "flavors/detail", "unknown_field": [12] } invalid_json_schema_desc = { "name": "list-flavors-with-detail", "http-method": "GET", "url": "flavors/detail", "json-schema": {"type": "NotExistingType"} } def setUp(self): super(TestNegativeBasicGenerator, self).setUp() self.generator = base_generator.BasicGeneratorSet() def _assert_valid_jsonschema_call(self, jsonschema_mock, desc): self.assertEqual(jsonschema_mock.call_count, 1) jsonschema_mock.assert_called_with(desc, self.generator.schema) @mock.patch('jsonschema.validate', wraps=jsonschema.validate) def test_validate_schema_with_valid_input(self, jsonschema_mock): self.generator.validate_schema(self.valid_desc) self._assert_valid_jsonschema_call(jsonschema_mock, self.valid_desc) @mock.patch('jsonschema.validate', wraps=jsonschema.validate) def test_validate_schema_with_minimal_input(self, jsonschema_mock): self.generator.validate_schema(self.minimal_desc) self._assert_valid_jsonschema_call(jsonschema_mock, self.minimal_desc) def test_validate_schema_with_invalid_input(self): self.assertRaises(jsonschema.ValidationError, self.generator.validate_schema, self.add_prop_desc) self.assertRaises(jsonschema.SchemaError, self.generator.validate_schema, self.invalid_json_schema_desc) class BaseNegativeGenerator(object): types = ['string', 'integer', 'object'] fake_input_str = {"type": "string", "minLength": 2, "maxLength": 8, 'results': {'gen_int': 404}} fake_input_int = {"type": "integer", "maximum": 255, "minimum": 1} fake_input_obj = {"type": "object", "properties": {"minRam": {"type": "integer"}, "diskName": {"type": "string"}, "maxRam": {"type": "integer", } } } unkown_type_schema = { "type": "not_defined" } def _validate_result(self, data): self.assertTrue(isinstance(data, list)) for t in data: self.assertIsInstance(t, tuple) self.assertEqual(3, len(t)) self.assertIsInstance(t[0], str) def test_generate_string(self): result = self.generator.generate(self.fake_input_str) self._validate_result(result) def test_generate_integer(self): result = self.generator.generate(self.fake_input_int) self._validate_result(result) def test_generate_obj(self): result = self.generator.generate(self.fake_input_obj) self._validate_result(result) def test_generator_mandatory_functions(self): for data_type in self.types: self.assertIn(data_type, self.generator.types_dict) def test_generate_with_unknown_type(self): self.assertRaises(TypeError, self.generator.generate, self.unkown_type_schema) class TestNegativeValidGenerator(base.TestCase, BaseNegativeGenerator): def setUp(self): super(TestNegativeValidGenerator, self).setUp() self.generator = valid_generator.ValidTestGenerator() def test_generate_valid(self): result = self.generator.generate_valid(self.fake_input_obj) self.assertIn("minRam", result) self.assertIsInstance(result["minRam"], int) self.assertIn("diskName", result) self.assertIsInstance(result["diskName"], str) class TestNegativeNegativeGenerator(base.TestCase, BaseNegativeGenerator): def setUp(self): super(TestNegativeNegativeGenerator, self).setUp() self.generator = negative_generator.NegativeTestGenerator() tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_waiters.py0000664000175000017500000000363112332757070024012 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # # 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 time import mock from tempest.common import waiters from tempest import exceptions from tempest.tests import base class TestImageWaiters(base.TestCase): def setUp(self): super(TestImageWaiters, self).setUp() self.client = mock.MagicMock() self.client.build_timeout = 1 self.client.build_interval = 1 def test_wait_for_image_status(self): self.client.get_image.return_value = (None, {'status': 'active'}) start_time = int(time.time()) waiters.wait_for_image_status(self.client, 'fake_image_id', 'active') end_time = int(time.time()) # Ensure waiter returns before build_timeout self.assertTrue((end_time - start_time) < 10) def test_wait_for_image_status_timeout(self): self.client.get_image.return_value = (None, {'status': 'saving'}) self.assertRaises(exceptions.TimeoutException, waiters.wait_for_image_status, self.client, 'fake_image_id', 'active') def test_wait_for_image_status_error_on_image_create(self): self.client.get_image.return_value = (None, {'status': 'ERROR'}) self.assertRaises(exceptions.AddImageException, waiters.wait_for_image_status, self.client, 'fake_image_id', 'active') tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_list_tests.py0000664000175000017500000000263612332757070024535 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 subprocess from tempest.tests import base class TestTestList(base.TestCase): def test_no_import_errors(self): import_failures = [] p = subprocess.Popen(['testr', 'list-tests'], stdout=subprocess.PIPE) ids = p.stdout.read() ids = ids.split('\n') for test_id in ids: if re.match('(\w+\.){3}\w+', test_id): if not test_id.startswith('tempest.'): fail_id = test_id.split('unittest.loader.ModuleImport' 'Failure.')[1] import_failures.append(fail_id) error_message = ("The following tests have import failures and aren't" " being run with test filters %s" % import_failures) self.assertFalse(import_failures, error_message) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/cli/0000775000175000017500000000000012332757136021472 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/cli/test_output_parser.py0000664000175000017500000001507612332757070026025 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.cli import output_parser from tempest import exceptions from tempest.tests import base class TestOutputParser(base.TestCase): OUTPUT_LINES = """ +----+------+---------+ | ID | Name | Status | +----+------+---------+ | 11 | foo | BUILD | | 21 | bar | ERROR | | 31 | bee | None | +----+------+---------+ """ OUTPUT_LINES2 = """ +----+-------+---------+ | ID | Name2 | Status2 | +----+-------+---------+ | 41 | aaa | SSSSS | | 51 | bbb | TTTTT | | 61 | ccc | AAAAA | +----+-------+---------+ """ EXPECTED_TABLE = {'headers': ['ID', 'Name', 'Status'], 'values': [['11', 'foo', 'BUILD'], ['21', 'bar', 'ERROR'], ['31', 'bee', 'None']]} EXPECTED_TABLE2 = {'headers': ['ID', 'Name2', 'Status2'], 'values': [['41', 'aaa', 'SSSSS'], ['51', 'bbb', 'TTTTT'], ['61', 'ccc', 'AAAAA']]} def test_table_with_normal_values(self): actual = output_parser.table(self.OUTPUT_LINES) self.assertIsInstance(actual, dict) self.assertEqual(self.EXPECTED_TABLE, actual) def test_table_with_list(self): output_lines = self.OUTPUT_LINES.split('\n') actual = output_parser.table(output_lines) self.assertIsInstance(actual, dict) self.assertEqual(self.EXPECTED_TABLE, actual) def test_table_with_invalid_line(self): output_lines = self.OUTPUT_LINES + "aaaa" actual = output_parser.table(output_lines) self.assertIsInstance(actual, dict) self.assertEqual(self.EXPECTED_TABLE, actual) def test_tables_with_normal_values(self): output_lines = 'test' + self.OUTPUT_LINES +\ 'test2' + self.OUTPUT_LINES2 expected = [{'headers': self.EXPECTED_TABLE['headers'], 'label': 'test', 'values': self.EXPECTED_TABLE['values']}, {'headers': self.EXPECTED_TABLE2['headers'], 'label': 'test2', 'values': self.EXPECTED_TABLE2['values']}] actual = output_parser.tables(output_lines) self.assertIsInstance(actual, list) self.assertEqual(expected, actual) def test_tables_with_invalid_values(self): output_lines = 'test' + self.OUTPUT_LINES +\ 'test2' + self.OUTPUT_LINES2 + '\n' expected = [{'headers': self.EXPECTED_TABLE['headers'], 'label': 'test', 'values': self.EXPECTED_TABLE['values']}, {'headers': self.EXPECTED_TABLE2['headers'], 'label': 'test2', 'values': self.EXPECTED_TABLE2['values']}] actual = output_parser.tables(output_lines) self.assertIsInstance(actual, list) self.assertEqual(expected, actual) def test_tables_with_invalid_line(self): output_lines = 'test' + self.OUTPUT_LINES +\ 'test2' + self.OUTPUT_LINES2 +\ '+----+-------+---------+' expected = [{'headers': self.EXPECTED_TABLE['headers'], 'label': 'test', 'values': self.EXPECTED_TABLE['values']}, {'headers': self.EXPECTED_TABLE2['headers'], 'label': 'test2', 'values': self.EXPECTED_TABLE2['values']}] actual = output_parser.tables(output_lines) self.assertIsInstance(actual, list) self.assertEqual(expected, actual) LISTING_OUTPUT = """ +----+ | ID | +----+ | 11 | | 21 | | 31 | +----+ """ def test_listing(self): expected = [{'ID': '11'}, {'ID': '21'}, {'ID': '31'}] actual = output_parser.listing(self.LISTING_OUTPUT) self.assertIsInstance(actual, list) self.assertEqual(expected, actual) def test_details_multiple_with_invalid_line(self): self.assertRaises(exceptions.InvalidStructure, output_parser.details_multiple, self.OUTPUT_LINES) DETAILS_LINES1 = """First Table +----------+--------+ | Property | Value | +----------+--------+ | foo | BUILD | | bar | ERROR | | bee | None | +----------+--------+ """ DETAILS_LINES2 = """Second Table +----------+--------+ | Property | Value | +----------+--------+ | aaa | VVVVV | | bbb | WWWWW | | ccc | XXXXX | +----------+--------+ """ def test_details_with_normal_line_label_false(self): expected = {'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'} actual = output_parser.details(self.DETAILS_LINES1) self.assertEqual(expected, actual) def test_details_with_normal_line_label_true(self): expected = {'__label': 'First Table', 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'} actual = output_parser.details(self.DETAILS_LINES1, with_label=True) self.assertEqual(expected, actual) def test_details_multiple_with_normal_line_label_false(self): expected = [{'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}, {'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}] actual = output_parser.details_multiple(self.DETAILS_LINES1 + self.DETAILS_LINES2) self.assertIsInstance(actual, list) self.assertEqual(expected, actual) def test_details_multiple_with_normal_line_label_true(self): expected = [{'__label': 'First Table', 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}, {'__label': 'Second Table', 'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}] actual = output_parser.details_multiple(self.DETAILS_LINES1 + self.DETAILS_LINES2, with_label=True) self.assertIsInstance(actual, list) self.assertEqual(expected, actual) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/cli/__init__.py0000664000175000017500000000000012332757070023566 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/tests/test_glance_http.py0000664000175000017500000002217612332757070024631 0ustar chuckchuck00000000000000# Copyright 2014 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import httplib import json import mock import six import socket from tempest.common import glance_http from tempest import exceptions from tempest.openstack.common.fixture import mockpatch from tempest.tests import base from tempest.tests import fake_auth_provider from tempest.tests import fake_http class TestGlanceHTTPClient(base.TestCase): def setUp(self): super(TestGlanceHTTPClient, self).setUp() self.fake_http = fake_http.fake_httplib2(return_type=200) # NOTE(maurosr): using http here implies that we will be using httplib # directly. With https glance_client would use an httpS version, but # the real backend would still be httplib anyway and since we mock it # that there is no reason to care. self.endpoint = 'http://fake_url.com' self.fake_auth = fake_auth_provider.FakeAuthProvider() self.fake_auth.base_url = mock.MagicMock(return_value=self.endpoint) self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection, 'request', side_effect=self.fake_http.request(self.endpoint)[1])) self.client = glance_http.HTTPClient(self.fake_auth, {}) def _set_response_fixture(self, header, status, resp_body): resp = fake_http.fake_httplib(header, status=status, body=six.StringIO(resp_body)) self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection, 'getresponse', return_value=resp)) return resp def test_json_request_without_content_type_header(self): self._set_response_fixture({}, 200, 'fake_response_body') resp, body = self.client.json_request('GET', '/images') self.assertEqual(200, resp.status) self.assertIsNone(body) def test_json_request_with_xml_content_type_header(self): self._set_response_fixture({'content-type': 'application/xml'}, 200, 'fake_response_body') resp, body = self.client.json_request('GET', '/images') self.assertEqual(200, resp.status) self.assertIsNone(body) def test_json_request_with_content_type_header(self): self._set_response_fixture({'content-type': 'application/json'}, 200, 'fake_response_body') resp, body = self.client.json_request('GET', '/images') self.assertEqual(200, resp.status) self.assertEqual('fake_response_body', body) def test_json_request_fails_to_json_loads(self): self._set_response_fixture({'content-type': 'application/json'}, 200, 'fake_response_body') self.useFixture(mockpatch.PatchObject(json, 'loads', side_effect=ValueError())) resp, body = self.client.json_request('GET', '/images') self.assertEqual(200, resp.status) self.assertEqual(body, 'fake_response_body') def test_json_request_socket_timeout(self): self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection, 'request', side_effect=socket.timeout())) self.assertRaises(exceptions.TimeoutException, self.client.json_request, 'GET', '/images') def test_json_request_endpoint_not_found(self): self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection, 'request', side_effect=socket.gaierror())) self.assertRaises(exceptions.EndpointNotFound, self.client.json_request, 'GET', '/images') def test_raw_request(self): self._set_response_fixture({}, 200, 'fake_response_body') resp, body = self.client.raw_request('GET', '/images') self.assertEqual(200, resp.status) self.assertEqual('fake_response_body', body.read()) def test_raw_request_with_response_chunked(self): self._set_response_fixture({}, 200, 'fake_response_body') self.useFixture(mockpatch.PatchObject(glance_http, 'CHUNKSIZE', 1)) resp, body = self.client.raw_request('GET', '/images') self.assertEqual(200, resp.status) self.assertEqual('fake_response_body', body.read()) def test_raw_request_chunked(self): self.useFixture(mockpatch.PatchObject(glance_http, 'CHUNKSIZE', 1)) self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection, 'endheaders')) self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection, 'send')) self._set_response_fixture({}, 200, 'fake_response_body') req_body = six.StringIO('fake_request_body') resp, body = self.client.raw_request('PUT', '/images', body=req_body) self.assertEqual(200, resp.status) self.assertEqual('fake_response_body', body.read()) httplib.HTTPConnection.send.assert_call_count(req_body.len) def test_get_connection_class_for_https(self): conn_class = self.client.get_connection_class('https') self.assertEqual(glance_http.VerifiedHTTPSConnection, conn_class) def test_get_connection_class_for_http(self): conn_class = (self.client.get_connection_class('http')) self.assertEqual(httplib.HTTPConnection, conn_class) def test_get_connection_http(self): self.assertTrue(isinstance(self.client.get_connection(), httplib.HTTPConnection)) def test_get_connection_https(self): endpoint = 'https://fake_url.com' self.fake_auth.base_url = mock.MagicMock(return_value=endpoint) self.client = glance_http.HTTPClient(self.fake_auth, {}) self.assertTrue(isinstance(self.client.get_connection(), glance_http.VerifiedHTTPSConnection)) def test_get_connection_url_not_fount(self): self.useFixture(mockpatch.PatchObject(self.client, 'connection_class', side_effect=httplib.InvalidURL() )) self.assertRaises(exceptions.EndpointNotFound, self.client.get_connection) def test_get_connection_kwargs_default_for_http(self): kwargs = self.client.get_connection_kwargs('http') self.assertEqual(600, kwargs['timeout']) self.assertEqual(1, len(kwargs.keys())) def test_get_connection_kwargs_set_timeout_for_http(self): kwargs = self.client.get_connection_kwargs('http', timeout=10, cacert='foo') self.assertEqual(10, kwargs['timeout']) # nothing more than timeout is evaluated for http connections self.assertEqual(1, len(kwargs.keys())) def test_get_connection_kwargs_default_for_https(self): kwargs = self.client.get_connection_kwargs('https') self.assertEqual(600, kwargs['timeout']) self.assertEqual(None, kwargs['cacert']) self.assertEqual(None, kwargs['cert_file']) self.assertEqual(None, kwargs['key_file']) self.assertEqual(False, kwargs['insecure']) self.assertEqual(True, kwargs['ssl_compression']) self.assertEqual(6, len(kwargs.keys())) def test_get_connection_kwargs_set_params_for_https(self): kwargs = self.client.get_connection_kwargs('https', timeout=10, cacert='foo', cert_file='/foo/bar.cert', key_file='/foo/key.pem', insecure=True, ssl_compression=False) self.assertEqual(10, kwargs['timeout']) self.assertEqual('foo', kwargs['cacert']) self.assertEqual('/foo/bar.cert', kwargs['cert_file']) self.assertEqual('/foo/key.pem', kwargs['key_file']) self.assertEqual(True, kwargs['insecure']) self.assertEqual(False, kwargs['ssl_compression']) self.assertEqual(6, len(kwargs.keys())) class TestResponseBodyIterator(base.TestCase): def test_iter_default_chunk_size_64k(self): resp = fake_http.fake_httplib({}, six.StringIO( 'X' * (glance_http.CHUNKSIZE + 1))) iterator = glance_http.ResponseBodyIterator(resp) chunks = list(iterator) self.assertEqual(chunks, ['X' * glance_http.CHUNKSIZE, 'X']) tempest-2014.1.dev4108.gf22b6cc/tempest/tests/base.py0000664000175000017500000000422512332757070022207 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 fixtures import mock import testtools from tempest.openstack.common.fixture import moxstubout class TestCase(testtools.TestCase): def setUp(self): super(TestCase, self).setUp() if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or os.environ.get('OS_STDOUT_CAPTURE') == '1'): stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or os.environ.get('OS_STDERR_CAPTURE') == '1'): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) mox_fixture = self.useFixture(moxstubout.MoxStubout()) self.mox = mox_fixture.mox self.stubs = mox_fixture.stubs def patch(self, target, **kwargs): """ Returns a started `mock.patch` object for the supplied target. The caller may then call the returned patcher to create a mock object. The caller does not need to call stop() on the returned patcher object, as this method automatically adds a cleanup to the test class to stop the patcher. :param target: String module.class or module.object expression to patch :param **kwargs: Passed as-is to `mock.patch`. See mock documentation for details. """ p = mock.patch(target, **kwargs) m = p.start() self.addCleanup(p.stop) return m tempest-2014.1.dev4108.gf22b6cc/tempest/tests/fake_http.py0000664000175000017500000000462412332757070023245 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 copy import httplib2 class fake_httplib2(object): def __init__(self, return_type=None, *args, **kwargs): self.return_type = return_type def request(self, uri, method="GET", body=None, headers=None, redirections=5, connection_type=None): if not self.return_type: fake_headers = httplib2.Response(headers) return_obj = { 'uri': uri, 'method': method, 'body': body, 'headers': headers } return (fake_headers, return_obj) # return (headers, return_obj) elif isinstance(self.return_type, int): body = "fake_body" header_info = { 'content-type': 'text/plain', 'status': str(self.return_type), 'content-length': len(body) } resp_header = httplib2.Response(header_info) return (resp_header, body) else: msg = "unsupported return type %s" % self.return_type raise TypeError(msg) class fake_httplib(object): def __init__(self, headers, body=None, version=1.0, status=200, reason="Ok"): """ :param headers: dict representing HTTP response headers :param body: file-like object :param version: HTTP Version :param status: Response status code :param reason: Status code related message. """ self.body = body self.status = status self.reason = reason self.version = version self.headers = headers def getheaders(self): return copy.deepcopy(self.headers).items() def getheader(self, key, default): return self.headers.get(key, default) def read(self, amt): return self.body.read(amt) tempest-2014.1.dev4108.gf22b6cc/tempest/common/0000775000175000017500000000000012332757136021051 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/common/generator/0000775000175000017500000000000012332757136023037 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/common/generator/base_generator.py0000664000175000017500000001124312332757070026367 0ustar chuckchuck00000000000000# Copyright 2014 Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import jsonschema from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) def _check_for_expected_result(name, schema): expected_result = None if "results" in schema: if name in schema["results"]: expected_result = schema["results"][name] return expected_result def generator_type(*args): def wrapper(func): func.types = args return func return wrapper def simple_generator(fn): """ Decorator for simple generators that return one value """ def wrapped(self, schema): result = fn(self, schema) if result is not None: expected_result = _check_for_expected_result(fn.__name__, schema) return (fn.__name__, result, expected_result) return return wrapped class BasicGeneratorSet(object): _instance = None schema = { "type": "object", "properties": { "name": {"type": "string"}, "http-method": { "enum": ["GET", "PUT", "HEAD", "POST", "PATCH", "DELETE", 'COPY'] }, "admin_client": {"type": "boolean"}, "url": {"type": "string"}, "default_result_code": {"type": "integer"}, "json-schema": {}, "resources": { "type": "array", "items": { "oneOf": [ {"type": "string"}, { "type": "object", "properties": { "name": {"type": "string"}, "expected_result": {"type": "integer"} } } ] } }, "results": { "type": "object", "properties": {} } }, "required": ["name", "http-method", "url"], "additionalProperties": False, } def __init__(self): self.types_dict = {} for m in dir(self): if callable(getattr(self, m)) and not'__' in m: method = getattr(self, m) if hasattr(method, "types"): for type in method.types: if type not in self.types_dict: self.types_dict[type] = [] self.types_dict[type].append(method) def validate_schema(self, schema): if "json-schema" in schema: jsonschema.Draft4Validator.check_schema(schema['json-schema']) jsonschema.validate(schema, self.schema) def generate(self, schema): """ Generate an json dictionary based on a schema. Only one value is mis-generated for each dictionary created. Any generator must return a list of tuples or a single tuple. The values of this tuple are: result[0]: Name of the test result[1]: json schema for the test result[2]: expected result of the test (can be None) """ LOG.debug("generate_invalid: %s" % schema) schema_type = schema["type"] if isinstance(schema_type, list): if "integer" in schema_type: schema_type = "integer" else: raise Exception("non-integer list types not supported") result = [] if schema_type not in self.types_dict: raise TypeError("generator (%s) doesn't support type: %s" % (self.__class__.__name__, schema_type)) for generator in self.types_dict[schema_type]: ret = generator(schema) if ret is not None: if isinstance(ret, list): result.extend(ret) elif isinstance(ret, tuple): result.append(ret) else: raise Exception("generator (%s) returns invalid result: %s" % (generator, ret)) LOG.debug("result: %s" % result) return result tempest-2014.1.dev4108.gf22b6cc/tempest/common/generator/valid_generator.py0000664000175000017500000000365012332757070026557 0ustar chuckchuck00000000000000# Copyright 2014 Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import tempest.common.generator.base_generator as base from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) class ValidTestGenerator(base.BasicGeneratorSet): @base.generator_type("string") @base.simple_generator def generate_valid_string(self, schema): size = schema.get("minLength", 0) # TODO(dkr mko): handle format and pattern return "x" * size @base.generator_type("integer") @base.simple_generator def generate_valid_integer(self, schema): # TODO(dkr mko): handle multipleOf if "minimum" in schema: minimum = schema["minimum"] if "exclusiveMinimum" not in schema: return minimum else: return minimum + 1 if "maximum" in schema: maximum = schema["maximum"] if "exclusiveMaximum" not in schema: return maximum else: return maximum - 1 return 0 @base.generator_type("object") @base.simple_generator def generate_valid_object(self, schema): obj = {} for k, v in schema["properties"].iteritems(): obj[k] = self.generate_valid(v) return obj def generate_valid(self, schema): return self.generate(schema)[0][1] tempest-2014.1.dev4108.gf22b6cc/tempest/common/generator/__init__.py0000664000175000017500000000000012332757070025133 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/common/generator/negative_generator.py0000664000175000017500000000747312332757070027271 0ustar chuckchuck00000000000000# Copyright 2014 Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import tempest.common.generator.base_generator as base import tempest.common.generator.valid_generator as valid from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) class NegativeTestGenerator(base.BasicGeneratorSet): @base.generator_type("string") @base.simple_generator def gen_int(self, _): return 4 @base.generator_type("integer") @base.simple_generator def gen_string(self, _): return "XXXXXX" @base.generator_type("integer", "string") def gen_none(self, schema): # Note(mkoderer): it's not using the decorator otherwise it'd be # filtered expected_result = base._check_for_expected_result('gen_none', schema) return ('gen_none', None, expected_result) @base.generator_type("string") @base.simple_generator def gen_str_min_length(self, schema): min_length = schema.get("minLength", 0) if min_length > 0: return "x" * (min_length - 1) @base.generator_type("string") @base.simple_generator def gen_str_max_length(self, schema): max_length = schema.get("maxLength", -1) if max_length > -1: return "x" * (max_length + 1) @base.generator_type("integer") @base.simple_generator def gen_int_min(self, schema): if "minimum" in schema: minimum = schema["minimum"] if "exclusiveMinimum" not in schema: minimum -= 1 return minimum @base.generator_type("integer") @base.simple_generator def gen_int_max(self, schema): if "maximum" in schema: maximum = schema["maximum"] if "exclusiveMaximum" not in schema: maximum += 1 return maximum @base.generator_type("object") def gen_obj_remove_attr(self, schema): invalids = [] valid_schema = valid.ValidTestGenerator().generate_valid(schema) required = schema.get("required", []) for r in required: new_valid = copy.deepcopy(valid_schema) del new_valid[r] invalids.append(("gen_obj_remove_attr", new_valid, None)) return invalids @base.generator_type("object") @base.simple_generator def gen_obj_add_attr(self, schema): valid_schema = valid.ValidTestGenerator().generate_valid(schema) if not schema.get("additionalProperties", True): new_valid = copy.deepcopy(valid_schema) new_valid["$$$$$$$$$$"] = "xxx" return new_valid @base.generator_type("object") def gen_inv_prop_obj(self, schema): LOG.debug("generate_invalid_object: %s" % schema) valid_schema = valid.ValidTestGenerator().generate_valid(schema) invalids = [] properties = schema["properties"] for k, v in properties.iteritems(): for invalid in self.generate(v): LOG.debug(v) new_valid = copy.deepcopy(valid_schema) new_valid[k] = invalid[1] name = "prop_%s_%s" % (k, invalid[0]) invalids.append((name, new_valid, invalid[2])) LOG.debug("generate_invalid_object return: %s" % invalids) return invalids tempest-2014.1.dev4108.gf22b6cc/tempest/common/http.py0000664000175000017500000000176012332757070022403 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 Citrix Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import httplib2 class ClosingHttp(httplib2.Http): def request(self, *args, **kwargs): original_headers = kwargs.get('headers', {}) new_headers = dict(original_headers, connection='close') new_kwargs = dict(kwargs, headers=new_headers) return super(ClosingHttp, self).request(*args, **new_kwargs) tempest-2014.1.dev4108.gf22b6cc/tempest/common/xml_utils.py0000664000175000017500000001252112332757070023441 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1" XMLNS_V3 = "http://docs.openstack.org/compute/api/v1.1" NEUTRON_NAMESPACES = { 'binding': "http://docs.openstack.org/ext/binding/api/v1.0", 'router': "http://docs.openstack.org/ext/neutron/router/api/v1.0", 'provider': 'http://docs.openstack.org/ext/provider/api/v1.0', } # NOTE(danms): This is just a silly implementation to help make generating # XML faster for prototyping. Could be replaced with proper etree gorp # if desired class Element(object): def __init__(self, element_name, *args, **kwargs): self.element_name = element_name self._attrs = kwargs self._elements = list(args) def add_attr(self, name, value): self._attrs[name] = value def append(self, element): self._elements.append(element) def __str__(self): args = " ".join(['%s="%s"' % (k, v if v is not None else "") for k, v in self._attrs.items()]) string = '<%s %s' % (self.element_name, args) if not self._elements: string += '/>' return string string += '>' for element in self._elements: string += str(element) string += '' % self.element_name return string def __getitem__(self, name): for element in self._elements: if element.element_name == name: return element raise KeyError("No such element `%s'" % name) def __getattr__(self, name): if name in self._attrs: return self._attrs[name] return object.__getattr__(self, name) def attributes(self): return self._attrs.items() def children(self): return self._elements class Document(Element): def __init__(self, *args, **kwargs): if 'version' not in kwargs: kwargs['version'] = '1.0' if 'encoding' not in kwargs: kwargs['encoding'] = 'UTF-8' Element.__init__(self, '?xml', *args, **kwargs) def __str__(self): args = " ".join(['%s="%s"' % (k, v if v is not None else "") for k, v in self._attrs.items()]) string = '\n' % args for element in self._elements: string += str(element) return string class Text(Element): def __init__(self, content=""): Element.__init__(self, None) self.__content = content def __str__(self): return self.__content def parse_array(node, plurals=None): array = [] for child in node.getchildren(): array.append(xml_to_json(child, plurals)) return array def xml_to_json(node, plurals=None): """This does a really braindead conversion of an XML tree to something that looks like a json dump. In cases where the XML and json structures are the same, then this "just works". In others, it requires a little hand-editing of the result. """ json = {} bool_flag = False int_flag = False long_flag = False for attr in node.keys(): if not attr.startswith("xmlns"): json[attr] = node.get(attr) if json[attr] == 'bool': bool_flag = True elif json[attr] == 'int': int_flag = True elif json[attr] == 'long': long_flag = True if not node.getchildren(): if bool_flag: return node.text == 'True' elif int_flag: return int(node.text) elif long_flag: return long(node.text) else: return node.text or json for child in node.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) for key, uri in NEUTRON_NAMESPACES.iteritems(): if uri == ns[1:]: tag = key + ":" + tag if plurals is not None and tag in plurals: json[tag] = parse_array(child, plurals) else: json[tag] = xml_to_json(child, plurals) return json def deep_dict_to_xml(dest, source): """Populates the ``dest`` xml element with the ``source`` ``Mapping`` elements, if the source Mapping's value is also a ``Mapping`` they will be recursively added as a child elements. :param source: A python ``Mapping`` (dict) :param dest: XML child element will be added to the ``dest`` """ for element, content in source.iteritems(): if isinstance(content, collections.Mapping): xml_element = Element(element) deep_dict_to_xml(xml_element, content) dest.append(xml_element) else: dest.append(Element(element, content)) tempest-2014.1.dev4108.gf22b6cc/tempest/common/commands.py0000664000175000017500000000401212332757070023216 0ustar chuckchuck00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import shlex import subprocess from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) # NOTE(afazekas): # These commands assumes the tempest node is the same as # the only one service node. all-in-one installation. def sudo_cmd_call(cmd): args = shlex.split(cmd) subprocess_args = {'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT} try: proc = subprocess.Popen(['/usr/bin/sudo', '-n'] + args, **subprocess_args) return proc.communicate()[0] if proc.returncode != 0: LOG.error(cmd + "returned with: " + proc.returncode + "exit status") except subprocess.CalledProcessError as e: LOG.error("command output:\n%s" % e.output) def ip_addr_raw(): return sudo_cmd_call("ip a") def ip_route_raw(): return sudo_cmd_call("ip r") def ip_ns_raw(): return sudo_cmd_call("ip netns list") def iptables_raw(table): return sudo_cmd_call("iptables -v -S -t " + table) def ip_ns_list(): return ip_ns_raw().split() def ip_ns_exec(ns, cmd): return sudo_cmd_call(" ".join(("ip netns exec", ns, cmd))) def ip_ns_addr(ns): return ip_ns_exec(ns, "ip a") def ip_ns_route(ns): return ip_ns_exec(ns, "ip r") def iptables_ns(ns, table): return ip_ns_exec(ns, "iptables -v -S -t " + table) def ovs_db_dump(): return sudo_cmd_call("ovsdb-client dump") tempest-2014.1.dev4108.gf22b6cc/tempest/common/isolated_creds.py0000664000175000017500000004715312332757070024416 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 netaddr from tempest import auth from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class IsolatedCreds(object): def __init__(self, name, tempest_client=True, interface='json', password='pass', network_resources=None): self.network_resources = network_resources self.isolated_creds = {} self.isolated_creds_old_style = {} self.isolated_net_resources = {} self.ports = [] self.name = name self.tempest_client = tempest_client self.interface = interface self.password = password self.identity_admin_client, self.network_admin_client = ( self._get_admin_clients()) def _get_admin_clients(self): """ Returns a tuple with instances of the following admin clients (in this order): identity network """ if self.tempest_client: os = clients.AdminManager(interface=self.interface) else: os = clients.OfficialClientManager( auth.get_default_credentials('identity_admin') ) return os.identity_client, os.network_client def _create_tenant(self, name, description): if self.tempest_client: resp, tenant = self.identity_admin_client.create_tenant( name=name, description=description) else: tenant = self.identity_admin_client.tenants.create( name, description=description) return tenant def _get_tenant_by_name(self, name): if self.tempest_client: resp, tenant = self.identity_admin_client.get_tenant_by_name(name) else: tenants = self.identity_admin_client.tenants.list() for ten in tenants: if ten['name'] == name: tenant = ten break else: raise exceptions.NotFound('No such tenant') return tenant def _create_user(self, username, password, tenant, email): if self.tempest_client: resp, user = self.identity_admin_client.create_user(username, password, tenant['id'], email) else: user = self.identity_admin_client.users.create(username, password, email, tenant_id=tenant.id) return user def _get_user(self, tenant, username): if self.tempest_client: resp, user = self.identity_admin_client.get_user_by_username( tenant['id'], username) else: user = self.identity_admin_client.users.get(username) return user def _list_roles(self): if self.tempest_client: resp, roles = self.identity_admin_client.list_roles() else: roles = self.identity_admin_client.roles.list() return roles def _assign_user_role(self, tenant, user, role): if self.tempest_client: self.identity_admin_client.assign_user_role(tenant, user, role) else: self.identity_admin_client.roles.add_user_role(user, role, tenant=tenant) def _delete_user(self, user): if self.tempest_client: self.identity_admin_client.delete_user(user) else: self.identity_admin_client.users.delete(user) def _delete_tenant(self, tenant): if self.tempest_client: self.identity_admin_client.delete_tenant(tenant) else: self.identity_admin_client.tenants.delete(tenant) def _create_creds(self, suffix="", admin=False): """Create random credentials under the following schema. If the name contains a '.' is the full class path of something, and we don't really care. If it isn't, it's probably a meaningful name, so use it. For logging purposes, -user and -tenant are long and redundant, don't use them. The user# will be sufficient to figure it out. """ if '.' in self.name: root = "" else: root = self.name tenant_name = data_utils.rand_name(root) + suffix tenant_desc = tenant_name + "-desc" tenant = self._create_tenant(name=tenant_name, description=tenant_desc) username = data_utils.rand_name(root) + suffix email = data_utils.rand_name(root) + suffix + "@example.com" user = self._create_user(username, self.password, tenant, email) if admin: role = None try: roles = self._list_roles() admin_role = CONF.identity.admin_role if self.tempest_client: role = next(r for r in roles if r['name'] == admin_role) else: role = next(r for r in roles if r.name == admin_role) except StopIteration: msg = "No admin role found" raise exceptions.NotFound(msg) if self.tempest_client: self._assign_user_role(tenant['id'], user['id'], role['id']) else: self._assign_user_role(tenant.id, user.id, role.id) return self._get_credentials(user, tenant), user, tenant def _get_credentials(self, user, tenant): if self.tempest_client: user_get = user.get tenant_get = tenant.get else: user_get = user.__dict__.get tenant_get = tenant.__dict__.get return auth.get_credentials( username=user_get('name'), user_id=user_get('id'), tenant_name=tenant_get('name'), tenant_id=tenant_get('id'), password=self.password) def _create_network_resources(self, tenant_id): network = None subnet = None router = None # Make sure settings if self.network_resources: if self.network_resources['router']: if (not self.network_resources['subnet'] or not self.network_resources['network']): raise exceptions.InvalidConfiguration( 'A router requires a subnet and network') elif self.network_resources['subnet']: if not self.network_resources['network']: raise exceptions.InvalidConfiguration( 'A subnet requires a network') elif self.network_resources['dhcp']: raise exceptions.InvalidConfiguration('DHCP requires a subnet') data_utils.rand_name_root = data_utils.rand_name(self.name) if not self.network_resources or self.network_resources['network']: network_name = data_utils.rand_name_root + "-network" network = self._create_network(network_name, tenant_id) try: if not self.network_resources or self.network_resources['subnet']: subnet_name = data_utils.rand_name_root + "-subnet" subnet = self._create_subnet(subnet_name, tenant_id, network['id']) if not self.network_resources or self.network_resources['router']: router_name = data_utils.rand_name_root + "-router" router = self._create_router(router_name, tenant_id) self._add_router_interface(router['id'], subnet['id']) except Exception: if router: self._clear_isolated_router(router['id'], router['name']) if subnet: self._clear_isolated_subnet(subnet['id'], subnet['name']) if network: self._clear_isolated_network(network['id'], network['name']) raise return network, subnet, router def _create_network(self, name, tenant_id): if self.tempest_client: resp, resp_body = self.network_admin_client.create_network( name=name, tenant_id=tenant_id) else: body = {'network': {'tenant_id': tenant_id, 'name': name}} resp_body = self.network_admin_client.create_network(body) return resp_body['network'] def _create_subnet(self, subnet_name, tenant_id, network_id): if not self.tempest_client: body = {'subnet': {'name': subnet_name, 'tenant_id': tenant_id, 'network_id': network_id, 'ip_version': 4}} if self.network_resources: body['enable_dhcp'] = self.network_resources['dhcp'] base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr) mask_bits = CONF.network.tenant_network_mask_bits for subnet_cidr in base_cidr.subnet(mask_bits): try: if self.tempest_client: if self.network_resources: resp, resp_body = self.network_admin_client.\ create_subnet( network_id=network_id, cidr=str(subnet_cidr), name=subnet_name, tenant_id=tenant_id, enable_dhcp=self.network_resources['dhcp'], ip_version=4) else: resp, resp_body = self.network_admin_client.\ create_subnet(network_id=network_id, cidr=str(subnet_cidr), name=subnet_name, tenant_id=tenant_id, ip_version=4) else: body['subnet']['cidr'] = str(subnet_cidr) resp_body = self.network_admin_client.create_subnet(body) break except exceptions.BadRequest as e: if 'overlaps with another subnet' not in str(e): raise else: e = exceptions.BuildErrorException() e.message = 'Available CIDR for subnet creation could not be found' raise e return resp_body['subnet'] def _create_router(self, router_name, tenant_id): external_net_id = dict( network_id=CONF.network.public_network_id) if self.tempest_client: resp, resp_body = self.network_admin_client.create_router( router_name, external_gateway_info=external_net_id, tenant_id=tenant_id) else: body = {'router': {'name': router_name, 'tenant_id': tenant_id, 'external_gateway_info': external_net_id, 'admin_state_up': True}} resp_body = self.network_admin_client.create_router(body) return resp_body['router'] def _add_router_interface(self, router_id, subnet_id): if self.tempest_client: self.network_admin_client.add_router_interface_with_subnet_id( router_id, subnet_id) else: body = {'subnet_id': subnet_id} self.network_admin_client.add_interface_router(router_id, body) def get_primary_tenant(self): # Deprecated. Maintained until all tests are ported return self.isolated_creds_old_style.get('primary')[1] def get_primary_user(self): # Deprecated. Maintained until all tests are ported return self.isolated_creds_old_style.get('primary')[0] def get_alt_tenant(self): # Deprecated. Maintained until all tests are ported return self.isolated_creds_old_style.get('alt')[1] def get_alt_user(self): # Deprecated. Maintained until all tests are ported return self.isolated_creds_old_style.get('alt')[0] def get_admin_tenant(self): # Deprecated. Maintained until all tests are ported return self.isolated_creds_old_style.get('admin')[1] def get_admin_user(self): # Deprecated. Maintained until all tests are ported return self.isolated_creds_old_style.get('admin')[0] def get_primary_network(self): return self.isolated_net_resources.get('primary')[0] def get_primary_subnet(self): return self.isolated_net_resources.get('primary')[1] def get_primary_router(self): return self.isolated_net_resources.get('primary')[2] def get_admin_network(self): return self.isolated_net_resources.get('admin')[0] def get_admin_subnet(self): return self.isolated_net_resources.get('admin')[1] def get_admin_router(self): return self.isolated_net_resources.get('admin')[2] def get_alt_network(self): return self.isolated_net_resources.get('alt')[0] def get_alt_subnet(self): return self.isolated_net_resources.get('alt')[1] def get_alt_router(self): return self.isolated_net_resources.get('alt')[2] def get_credentials(self, credential_type, old_style): if self.isolated_creds.get(credential_type): credentials = self.isolated_creds[credential_type] else: is_admin = (credential_type == 'admin') credentials, user, tenant = self._create_creds(admin=is_admin) self.isolated_creds[credential_type] = credentials # Maintained until tests are ported self.isolated_creds_old_style[credential_type] = (user, tenant) LOG.info("Acquired isolated creds:\n credentials: %s" % credentials) if CONF.service_available.neutron: network, subnet, router = self._create_network_resources( credentials.tenant_id) self.isolated_net_resources[credential_type] = ( network, subnet, router,) LOG.info("Created isolated network resources for : \n" + " credentials: %s" % credentials) if old_style: return (credentials.username, credentials.tenant_name, credentials.password) else: return credentials def get_primary_creds(self, old_style=False): return self.get_credentials('primary', old_style) def get_admin_creds(self, old_style=False): return self.get_credentials('admin', old_style) def get_alt_creds(self, old_style=False): return self.get_credentials('alt', old_style) def _clear_isolated_router(self, router_id, router_name): net_client = self.network_admin_client try: net_client.delete_router(router_id) except exceptions.NotFound: LOG.warn('router with name: %s not found for delete' % router_name) def _clear_isolated_subnet(self, subnet_id, subnet_name): net_client = self.network_admin_client try: net_client.delete_subnet(subnet_id) except exceptions.NotFound: LOG.warn('subnet with name: %s not found for delete' % subnet_name) def _clear_isolated_network(self, network_id, network_name): net_client = self.network_admin_client try: net_client.delete_network(network_id) except exceptions.NotFound: LOG.warn('network with name: %s not found for delete' % network_name) def _cleanup_ports(self, network_id): # TODO(mlavalle) This method will be removed once patch # https://review.openstack.org/#/c/46563/ merges in Neutron if not self.ports: if self.tempest_client: resp, resp_body = self.network_admin_client.list_ports() else: resp_body = self.network_admin_client.list_ports() self.ports = resp_body['ports'] ports_to_delete = [ port for port in self.ports if (port['network_id'] == network_id and port['device_owner'] != 'network:router_interface' and port['device_owner'] != 'network:dhcp') ] for port in ports_to_delete: try: LOG.info('Cleaning up port id %s, name %s' % (port['id'], port['name'])) self.network_admin_client.delete_port(port['id']) except exceptions.NotFound: LOG.warn('Port id: %s, name %s not found for clean-up' % (port['id'], port['name'])) def _clear_isolated_net_resources(self): net_client = self.network_admin_client for cred in self.isolated_net_resources: network, subnet, router = self.isolated_net_resources.get(cred) LOG.debug("Clearing network: %(network)s, " "subnet: %(subnet)s, router: %(router)s", {'network': network, 'subnet': subnet, 'router': router}) if (not self.network_resources or self.network_resources.get('router')): try: if self.tempest_client: net_client.remove_router_interface_with_subnet_id( router['id'], subnet['id']) else: body = {'subnet_id': subnet['id']} net_client.remove_interface_router(router['id'], body) except exceptions.NotFound: LOG.warn('router with name: %s not found for delete' % router['name']) self._clear_isolated_router(router['id'], router['name']) if (not self.network_resources or self.network_resources.get('network')): # TODO(mlavalle) This method call will be removed once patch # https://review.openstack.org/#/c/46563/ merges in Neutron self._cleanup_ports(network['id']) if (not self.network_resources or self.network_resources.get('subnet')): self._clear_isolated_subnet(subnet['id'], subnet['name']) if (not self.network_resources or self.network_resources.get('network')): self._clear_isolated_network(network['id'], network['name']) def clear_isolated_creds(self): if not self.isolated_creds: return self._clear_isolated_net_resources() for creds in self.isolated_creds.itervalues(): try: self._delete_user(creds.user_id) except exceptions.NotFound: LOG.warn("user with name: %s not found for delete" % creds.username) try: self._delete_tenant(creds.tenant_id) except exceptions.NotFound: LOG.warn("tenant with name: %s not found for delete" % creds.tenant_name) tempest-2014.1.dev4108.gf22b6cc/tempest/common/waiters.py0000664000175000017500000001260612332757070023103 0ustar chuckchuck00000000000000# 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 time from tempest.common.utils import misc as misc_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) # NOTE(afazekas): This function needs to know a token and a subject. def wait_for_server_status(client, server_id, status, ready_wait=True, extra_timeout=0, raise_on_error=True): """Waits for a server to reach a given status.""" def _get_task_state(body): if client.service == CONF.compute.catalog_v3_type: task_state = body.get("os-extended-status:task_state", None) else: task_state = body.get('OS-EXT-STS:task_state', None) return task_state # NOTE(afazekas): UNKNOWN status possible on ERROR # or in a very early stage. resp, body = client.get_server(server_id) old_status = server_status = body['status'] old_task_state = task_state = _get_task_state(body) start_time = int(time.time()) timeout = client.build_timeout + extra_timeout while True: # NOTE(afazekas): Now the BUILD status only reached # between the UNKNOWN->ACTIVE transition. # TODO(afazekas): enumerate and validate the stable status set if status == 'BUILD' and server_status != 'UNKNOWN': return if server_status == status: if ready_wait: if status == 'BUILD': return # NOTE(afazekas): The instance is in "ready for action state" # when no task in progress # NOTE(afazekas): Converted to string bacuse of the XML # responses if str(task_state) == "None": # without state api extension 3 sec usually enough time.sleep(CONF.compute.ready_wait) return else: return time.sleep(client.build_interval) resp, body = client.get_server(server_id) server_status = body['status'] task_state = _get_task_state(body) if (server_status != old_status) or (task_state != old_task_state): LOG.info('State transition "%s" ==> "%s" after %d second wait', '/'.join((old_status, str(old_task_state))), '/'.join((server_status, str(task_state))), time.time() - start_time) if (server_status == 'ERROR') and raise_on_error: raise exceptions.BuildErrorException(server_id=server_id) timed_out = int(time.time()) - start_time >= timeout if timed_out: expected_task_state = 'None' if ready_wait else 'n/a' message = ('Server %(server_id)s failed to reach %(status)s ' 'status and task state "%(expected_task_state)s" ' 'within the required time (%(timeout)s s).' % {'server_id': server_id, 'status': status, 'expected_task_state': expected_task_state, 'timeout': timeout}) message += ' Current status: %s.' % server_status message += ' Current task state: %s.' % task_state caller = misc_utils.find_test_caller() if caller: message = '(%s) %s' % (caller, message) raise exceptions.TimeoutException(message) old_status = server_status old_task_state = task_state def wait_for_image_status(client, image_id, status): """Waits for an image to reach a given status. The client should have a get_image(image_id) method to get the image. The client should also have build_interval and build_timeout attributes. """ resp, image = client.get_image(image_id) start = int(time.time()) while image['status'] != status: time.sleep(client.build_interval) resp, image = client.get_image(image_id) if image['status'] == 'ERROR': raise exceptions.AddImageException(image_id=image_id) # check the status again to avoid a false negative where we hit # the timeout at the same time that the image reached the expected # status if image['status'] == status: return if int(time.time()) - start >= client.build_timeout: message = ('Image %(image_id)s failed to reach %(status)s ' 'status within the required time (%(timeout)s s).' % {'image_id': image_id, 'status': status, 'timeout': client.build_timeout}) message += ' Current status: %s.' % image['status'] caller = misc_utils.find_test_caller() if caller: message = '(%s) %s' % (caller, message) raise exceptions.TimeoutException(message) tempest-2014.1.dev4108.gf22b6cc/tempest/common/ssh.py0000664000175000017500000001346212332757070022223 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import cStringIO import select import six import socket import time import warnings from tempest import exceptions from tempest.openstack.common import log as logging with warnings.catch_warnings(): warnings.simplefilter("ignore") import paramiko LOG = logging.getLogger(__name__) class Client(object): def __init__(self, host, username, password=None, timeout=300, pkey=None, channel_timeout=10, look_for_keys=False, key_filename=None): self.host = host self.username = username self.password = password if isinstance(pkey, six.string_types): pkey = paramiko.RSAKey.from_private_key( cStringIO.StringIO(str(pkey))) self.pkey = pkey self.look_for_keys = look_for_keys self.key_filename = key_filename self.timeout = int(timeout) self.channel_timeout = float(channel_timeout) self.buf_size = 1024 def _get_ssh_connection(self, sleep=1.5, backoff=1): """Returns an ssh connection to the specified host.""" bsleep = sleep ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy( paramiko.AutoAddPolicy()) _start_time = time.time() if self.pkey is not None: LOG.info("Creating ssh connection to '%s' as '%s'" " with public key authentication", self.host, self.username) else: LOG.info("Creating ssh connection to '%s' as '%s'" " with password %s", self.host, self.username, str(self.password)) attempts = 0 while True: try: ssh.connect(self.host, username=self.username, password=self.password, look_for_keys=self.look_for_keys, key_filename=self.key_filename, timeout=self.channel_timeout, pkey=self.pkey) LOG.info("ssh connection to %s@%s successfuly created", self.username, self.host) return ssh except (socket.error, paramiko.SSHException) as e: if self._is_timed_out(_start_time): LOG.exception("Failed to establish authenticated ssh" " connection to %s@%s after %d attempts", self.username, self.host, attempts) raise exceptions.SSHTimeout(host=self.host, user=self.username, password=self.password) bsleep += backoff attempts += 1 LOG.warning("Failed to establish authenticated ssh" " connection to %s@%s (%s). Number attempts: %s." " Retry after %d seconds.", self.username, self.host, e, attempts, bsleep) time.sleep(bsleep) def _is_timed_out(self, start_time): return (time.time() - self.timeout) > start_time def exec_command(self, cmd): """ Execute the specified command on the server. Note that this method is reading whole command outputs to memory, thus shouldn't be used for large outputs. :returns: data read from standard output of the command. :raises: SSHExecCommandFailed if command returns nonzero status. The exception contains command status stderr content. """ ssh = self._get_ssh_connection() transport = ssh.get_transport() channel = transport.open_session() channel.fileno() # Register event pipe channel.exec_command(cmd) channel.shutdown_write() out_data = [] err_data = [] poll = select.poll() poll.register(channel, select.POLLIN) start_time = time.time() while True: ready = poll.poll(self.channel_timeout) if not any(ready): if not self._is_timed_out(start_time): continue raise exceptions.TimeoutException( "Command: '{0}' executed on host '{1}'.".format( cmd, self.host)) if not ready[0]: # If there is nothing to read. continue out_chunk = err_chunk = None if channel.recv_ready(): out_chunk = channel.recv(self.buf_size) out_data += out_chunk, if channel.recv_stderr_ready(): err_chunk = channel.recv_stderr(self.buf_size) err_data += err_chunk, if channel.closed and not err_chunk and not out_chunk: break exit_status = channel.recv_exit_status() if 0 != exit_status: raise exceptions.SSHExecCommandFailed( command=cmd, exit_status=exit_status, strerror=''.join(err_data)) return ''.join(out_data) def test_connection_auth(self): """Raises an exception when we can not connect to server via ssh.""" connection = self._get_ssh_connection() connection.close() tempest-2014.1.dev4108.gf22b6cc/tempest/common/__init__.py0000664000175000017500000000000012332757070023145 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/common/custom_matchers.py0000664000175000017500000001351212332757070024622 0ustar chuckchuck00000000000000# Copyright 2013 NTT Corporation # # 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 class ExistsAllResponseHeaders(object): """ Specific matcher to check the existence of Swift's response headers This matcher checks the existence of common headers for each HTTP method or the target, which means account, container or object. When checking the existence of 'specific' headers such as X-Account-Meta-* or X-Object-Manifest for example, those headers must be checked in each test code. """ def __init__(self, target, method): """ param: target Account/Container/Object param: method PUT/GET/HEAD/DELETE/COPY/POST """ self.target = target self.method = method def match(self, actual): """ param: actual HTTP response headers """ # Check common headers for all HTTP methods if 'content-length' not in actual: return NonExistentHeader('content-length') if 'content-type' not in actual: return NonExistentHeader('content-type') if 'x-trans-id' not in actual: return NonExistentHeader('x-trans-id') if 'date' not in actual: return NonExistentHeader('date') # Check headers for a specific method or target if self.method == 'GET' or self.method == 'HEAD': if 'x-timestamp' not in actual: return NonExistentHeader('x-timestamp') if 'accept-ranges' not in actual: return NonExistentHeader('accept-ranges') if self.target == 'Account': if 'x-account-bytes-used' not in actual: return NonExistentHeader('x-account-bytes-used') if 'x-account-container-count' not in actual: return NonExistentHeader('x-account-container-count') if 'x-account-object-count' not in actual: return NonExistentHeader('x-account-object-count') elif self.target == 'Container': if 'x-container-bytes-used' not in actual: return NonExistentHeader('x-container-bytes-used') if 'x-container-object-count' not in actual: return NonExistentHeader('x-container-object-count') elif self.target == 'Object': if 'etag' not in actual: return NonExistentHeader('etag') elif self.method == 'PUT' or self.method == 'COPY': if self.target == 'Object': if 'etag' not in actual: return NonExistentHeader('etag') return None class NonExistentHeader(object): """ Informs an error message for end users in the case of missing a certain header in Swift's responses """ def __init__(self, header): self.header = header def describe(self): return "%s header does not exist" % self.header def get_details(self): return {} class AreAllWellFormatted(object): """ Specific matcher to check the correctness of formats of values of Swift's response headers This matcher checks the format of values of response headers. When checking the format of values of 'specific' headers such as X-Account-Meta-* or X-Object-Manifest for example, those values must be checked in each test code. """ def match(self, actual): for key, value in actual.iteritems(): if key == 'content-length' and not value.isdigit(): return InvalidFormat(key, value) elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value): return InvalidFormat(key, value) elif key == 'x-account-bytes-used' and not value.isdigit(): return InvalidFormat(key, value) elif key == 'x-account-container-count' and not value.isdigit(): return InvalidFormat(key, value) elif key == 'x-account-object-count' and not value.isdigit(): return InvalidFormat(key, value) elif key == 'x-container-bytes-used' and not value.isdigit(): return InvalidFormat(key, value) elif key == 'x-container-object-count' and not value.isdigit(): return InvalidFormat(key, value) elif key == 'content-type' and not value: return InvalidFormat(key, value) elif key == 'x-trans-id' and \ not re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", value): return InvalidFormat(key, value) elif key == 'date' and not value: return InvalidFormat(key, value) elif key == 'accept-ranges' and not value == 'bytes': return InvalidFormat(key, value) elif key == 'etag' and not value.isalnum(): return InvalidFormat(key, value) elif key == 'transfer-encoding' and not value == 'chunked': return InvalidFormat(key, value) return None class InvalidFormat(object): """ Informs an error message for end users if a format of a certain header is invalid """ def __init__(self, key, value): self.key = key self.value = value def describe(self): return "InvalidFormat (%s, %s)" % (self.key, self.value) def get_details(self): return {} tempest-2014.1.dev4108.gf22b6cc/tempest/common/debug.py0000664000175000017500000000323212332757070022506 0ustar chuckchuck00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common import commands from tempest import config from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) TABLES = ['filter', 'nat', 'mangle'] def log_ip_ns(): if not CONF.debug.enable: return LOG.info("Host Addr:\n" + commands.ip_addr_raw()) LOG.info("Host Route:\n" + commands.ip_route_raw()) for table in TABLES: LOG.info('Host %s table:\n%s', table, commands.iptables_raw(table)) ns_list = commands.ip_ns_list() LOG.info("Host ns list" + str(ns_list)) for ns in ns_list: LOG.info("ns(%s) Addr:\n%s", ns, commands.ip_ns_addr(ns)) LOG.info("ns(%s) Route:\n%s", ns, commands.ip_ns_route(ns)) for table in TABLES: LOG.info('ns(%s) table(%s):\n%s', ns, table, commands.iptables_ns(ns, table)) def log_ovs_db(): if not CONF.debug.enable or not CONF.service_available.neutron: return db_dump = commands.ovs_db_dump() LOG.info("OVS DB:\n" + db_dump) def log_net_debug(): log_ip_ns() log_ovs_db() tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/0000775000175000017500000000000012332757136022211 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/linux/0000775000175000017500000000000012332757136023350 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/linux/__init__.py0000664000175000017500000000000012332757070025444 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/linux/remote_client.py0000664000175000017500000001025212332757070026550 0ustar chuckchuck00000000000000# 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 six import time from tempest.common import ssh from tempest import config from tempest import exceptions CONF = config.CONF class RemoteClient(): # NOTE(afazekas): It should always get an address instead of server def __init__(self, server, username, password=None, pkey=None): ssh_timeout = CONF.compute.ssh_timeout network = CONF.compute.network_for_ssh ip_version = CONF.compute.ip_version_for_ssh ssh_channel_timeout = CONF.compute.ssh_channel_timeout if isinstance(server, six.string_types): ip_address = server else: addresses = server['addresses'][network] for address in addresses: if address['version'] == ip_version: ip_address = address['addr'] break else: raise exceptions.ServerUnreachable() self.ssh_client = ssh.Client(ip_address, username, password, ssh_timeout, pkey=pkey, channel_timeout=ssh_channel_timeout) def exec_command(self, cmd): return self.ssh_client.exec_command(cmd) def validate_authentication(self): """Validate ssh connection and authentication This method raises an Exception when the validation fails. """ self.ssh_client.test_connection_auth() def hostname_equals_servername(self, expected_hostname): # Get host name using command "hostname" actual_hostname = self.exec_command("hostname").rstrip() return expected_hostname == actual_hostname def get_files(self, path): # Return a list of comma separated files command = "ls -m " + path return self.exec_command(command).rstrip('\n').split(', ') def get_ram_size_in_mb(self): output = self.exec_command('free -m | grep Mem') if output: return output.split()[1] def get_number_of_vcpus(self): command = 'cat /proc/cpuinfo | grep processor | wc -l' output = self.exec_command(command) return int(output) def get_partitions(self): # Return the contents of /proc/partitions command = 'cat /proc/partitions' output = self.exec_command(command) return output def get_boot_time(self): cmd = 'cut -f1 -d. /proc/uptime' boot_secs = self.exec_command(cmd) boot_time = time.time() - int(boot_secs) return time.localtime(boot_time) def write_to_console(self, message): message = re.sub("([$\\`])", "\\\\\\\\\\1", message) # usually to /dev/ttyS0 cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message return self.exec_command(cmd) def ping_host(self, host): cmd = 'ping -c1 -w1 %s' % host return self.exec_command(cmd) def get_mac_address(self): cmd = "/sbin/ifconfig | awk '/HWaddr/ {print $5}'" return self.exec_command(cmd) def get_ip_list(self): cmd = "/bin/ip address" return self.exec_command(cmd) def assign_static_ip(self, nic, addr): cmd = "sudo /bin/ip addr add {ip}/{mask} dev {nic}".format( ip=addr, mask=CONF.network.tenant_network_mask_bits, nic=nic ) return self.exec_command(cmd) def turn_nic_on(self, nic): cmd = "sudo /bin/ip link set {nic} up".format(nic=nic) return self.exec_command(cmd) def get_pids(self, pr_name): # Get pid(s) of a process/program cmd = "ps -ef | grep %s | grep -v 'grep' | awk {'print $1'}" % pr_name return self.exec_command(cmd).split('\n') tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/data_utils.py0000664000175000017500000000404212332757070024711 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import itertools import random import uuid def rand_uuid(): return str(uuid.uuid4()) def rand_uuid_hex(): return uuid.uuid4().hex def rand_name(name=''): randbits = str(random.randint(1, 0x7fffffff)) if name: return name + '-' + randbits else: return randbits def rand_int_id(start=0, end=0x7fffffff): return random.randint(start, end) def rand_mac_address(): """Generate an Ethernet MAC address.""" # NOTE(vish): We would prefer to use 0xfe here to ensure that linux # bridge mac addresses don't change, but it appears to # conflict with libvirt, so we use the next highest octet # that has the unicast and locally administered bits set # properly: 0xfa. # Discussion: https://bugs.launchpad.net/nova/+bug/921838 mac = [0xfa, 0x16, 0x3e, random.randint(0x00, 0xff), random.randint(0x00, 0xff), random.randint(0x00, 0xff)] return ':'.join(["%02x" % x for x in mac]) def parse_image_id(image_ref): """Return the image id from a given image ref.""" return image_ref.rsplit('/')[-1] def arbitrary_string(size=4, base_text=None): """ Return size characters from base_text, repeating the base_text infinitely if needed. """ if not base_text: base_text = 'test' return ''.join(itertools.islice(itertools.cycle(base_text), size)) tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/__init__.py0000664000175000017500000000017412332757070024321 0ustar chuckchuck00000000000000PING_IPV4_COMMAND = 'ping -c 3 ' PING_IPV6_COMMAND = 'ping6 -c 3 ' PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\% packet loss' tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/misc.py0000664000175000017500000000565412332757070023525 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import inspect import re from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) def singleton(cls): """Simple wrapper for classes that should only have a single instance.""" instances = {} def getinstance(): if cls not in instances: instances[cls] = cls() return instances[cls] return getinstance def find_test_caller(): """Find the caller class and test name. Because we know that the interesting things that call us are test_* methods, and various kinds of setUp / tearDown, we can look through the call stack to find appropriate methods, and the class we were in when those were called. """ caller_name = None names = [] frame = inspect.currentframe() is_cleanup = False # Start climbing the ladder until we hit a good method while True: try: frame = frame.f_back name = frame.f_code.co_name names.append(name) if re.search("^(test_|setUp|tearDown)", name): cname = "" if 'self' in frame.f_locals: cname = frame.f_locals['self'].__class__.__name__ if 'cls' in frame.f_locals: cname = frame.f_locals['cls'].__name__ caller_name = cname + ":" + name break elif re.search("^_run_cleanup", name): is_cleanup = True else: cname = "" if 'self' in frame.f_locals: cname = frame.f_locals['self'].__class__.__name__ if 'cls' in frame.f_locals: cname = frame.f_locals['cls'].__name__ # the fact that we are running cleanups is indicated pretty # deep in the stack, so if we see that we want to just # start looking for a real class name, and declare victory # once we do. if is_cleanup and cname: if not re.search("^RunTest", cname): caller_name = cname + ":_run_cleanups" break except Exception: break # prevents frame leaks del frame if caller_name is None: LOG.debug("Sane call name not found in %s" % names) return caller_name tempest-2014.1.dev4108.gf22b6cc/tempest/common/utils/file_utils.py0000664000175000017500000000142212332757070024716 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def have_effective_read_access(path): try: fh = open(path, "rb") except IOError: return False fh.close() return True tempest-2014.1.dev4108.gf22b6cc/tempest/common/rest_client.py0000664000175000017500000005631612332757070023746 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import json from lxml import etree import re import time import jsonschema from tempest.common import http from tempest.common.utils import misc as misc_utils from tempest.common import xml_utils as common from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging CONF = config.CONF # redrive rate limited calls at most twice MAX_RECURSION_DEPTH = 2 TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$') # All the successful HTTP status codes from RFC 2616 HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206) class RestClient(object): TYPE = "json" # This is used by _parse_resp method # Redefine it for purposes of your xml service client # List should contain top-xml_tag-names of data, which is like list/array # For example, in keystone it is users, roles, tenants and services # All of it has children with same tag-names list_tags = [] # This is used by _parse_resp method too # Used for selection of dict-like xmls, # like metadata for Vms in nova, and volumes in cinder dict_tags = ["metadata", ] LOG = logging.getLogger(__name__) def __init__(self, auth_provider): self.auth_provider = auth_provider self.endpoint_url = None self.service = None # The version of the API this client implements self.api_version = None self._skip_path = False self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout self.general_header_lc = set(('cache-control', 'connection', 'date', 'pragma', 'trailer', 'transfer-encoding', 'via', 'warning')) self.response_header_lc = set(('accept-ranges', 'age', 'etag', 'location', 'proxy-authenticate', 'retry-after', 'server', 'vary', 'www-authenticate')) dscv = CONF.identity.disable_ssl_certificate_validation self.http_obj = http.ClosingHttp( disable_ssl_certificate_validation=dscv) def _get_type(self): return self.TYPE def get_headers(self, accept_type=None, send_type=None): if accept_type is None: accept_type = self._get_type() if send_type is None: send_type = self._get_type() return {'Content-Type': 'application/%s' % send_type, 'Accept': 'application/%s' % accept_type} def __str__(self): STRING_LIMIT = 80 str_format = ("config:%s, service:%s, base_url:%s, " "filters: %s, build_interval:%s, build_timeout:%s" "\ntoken:%s..., \nheaders:%s...") return str_format % (CONF, self.service, self.base_url, self.filters, self.build_interval, self.build_timeout, str(self.token)[0:STRING_LIMIT], str(self.get_headers())[0:STRING_LIMIT]) def _get_region(self, service): """ Returns the region for a specific service """ service_region = None for cfgname in dir(CONF._config): # Find all config.FOO.catalog_type and assume FOO is a service. cfg = getattr(CONF, cfgname) catalog_type = getattr(cfg, 'catalog_type', None) if catalog_type == service: service_region = getattr(cfg, 'region', None) if not service_region: service_region = CONF.identity.region return service_region def _get_endpoint_type(self, service): """ Returns the endpoint type for a specific service """ # If the client requests a specific endpoint type, then be it if self.endpoint_url: return self.endpoint_url endpoint_type = None for cfgname in dir(CONF._config): # Find all config.FOO.catalog_type and assume FOO is a service. cfg = getattr(CONF, cfgname) catalog_type = getattr(cfg, 'catalog_type', None) if catalog_type == service: endpoint_type = getattr(cfg, 'endpoint_type', 'publicURL') break # Special case for compute v3 service which hasn't its own # configuration group else: if service == CONF.compute.catalog_v3_type: endpoint_type = CONF.compute.endpoint_type return endpoint_type @property def user(self): return self.auth_provider.credentials.username @property def tenant_name(self): return self.auth_provider.credentials.tenant_name @property def tenant_id(self): return self.auth_provider.credentials.tenant_id @property def password(self): return self.auth_provider.credentials.password @property def base_url(self): return self.auth_provider.base_url(filters=self.filters) @property def token(self): return self.auth_provider.get_token() @property def filters(self): _filters = dict( service=self.service, endpoint_type=self._get_endpoint_type(self.service), region=self._get_region(self.service) ) if self.api_version is not None: _filters['api_version'] = self.api_version if self._skip_path: _filters['skip_path'] = self._skip_path return _filters def skip_path(self): """ When set, ignore the path part of the base URL from the catalog """ self._skip_path = True def reset_path(self): """ When reset, use the base URL from the catalog as-is """ self._skip_path = False def expected_success(self, expected_code, read_code): assert_msg = ("This function only allowed to use for HTTP status" "codes which explicitly defined in the RFC 2616. {0}" " is not a defined Success Code!").format(expected_code) assert expected_code in HTTP_SUCCESS, assert_msg # NOTE(afazekas): the http status code above 400 is processed by # the _error_checker method if read_code < 400 and read_code != expected_code: pattern = """Unexpected http success status code {0}, The expected status code is {1}""" details = pattern.format(read_code, expected_code) raise exceptions.InvalidHttpSuccessCode(details) def post(self, url, body, headers=None, extra_headers=False): return self.request('POST', url, extra_headers, headers, body) def get(self, url, headers=None, extra_headers=False): return self.request('GET', url, extra_headers, headers) def delete(self, url, headers=None, body=None, extra_headers=False): return self.request('DELETE', url, extra_headers, headers, body) def patch(self, url, body, headers=None, extra_headers=False): return self.request('PATCH', url, extra_headers, headers, body) def put(self, url, body, headers=None, extra_headers=False): return self.request('PUT', url, extra_headers, headers, body) def head(self, url, headers=None, extra_headers=False): return self.request('HEAD', url, extra_headers, headers) def copy(self, url, headers=None, extra_headers=False): return self.request('COPY', url, extra_headers, headers) def get_versions(self): resp, body = self.get('') body = self._parse_resp(body) versions = map(lambda x: x['id'], body) return resp, versions def _get_request_id(self, resp): for i in ('x-openstack-request-id', 'x-compute-request-id'): if i in resp: return resp[i] return "" def _log_request(self, method, req_url, resp, secs="", req_headers={}, req_body=None, resp_body=None): # if we have the request id, put it in the right part of the log extra = dict(request_id=self._get_request_id(resp)) # NOTE(sdague): while we still have 6 callers to this function # we're going to just provide work around on who is actually # providing timings by gracefully adding no content if they don't. # Once we're down to 1 caller, clean this up. caller_name = misc_utils.find_test_caller() if secs: secs = " %.3fs" % secs self.LOG.info( 'Request (%s): %s %s %s%s' % ( caller_name, resp['status'], method, req_url, secs), extra=extra) # We intentionally duplicate the info content because in a parallel # world this is important to match trace_regex = CONF.debug.trace_requests if trace_regex and re.search(trace_regex, caller_name): if 'X-Auth-Token' in req_headers: req_headers['X-Auth-Token'] = '' log_fmt = """Request (%s): %s %s %s%s Request - Headers: %s Body: %s Response - Headers: %s Body: %s""" self.LOG.debug( log_fmt % ( caller_name, resp['status'], method, req_url, secs, str(req_headers), str(req_body)[:2048], str(resp), str(resp_body)[:2048]), extra=extra) def _parse_resp(self, body): if self._get_type() is "json": body = json.loads(body) # We assume, that if the first value of the deserialized body's # item set is a dict or a list, that we just return the first value # of deserialized body. # Essentially "cutting out" the first placeholder element in a body # that looks like this: # # { # "users": [ # ... # ] # } try: # Ensure there are not more than one top-level keys if len(body.keys()) > 1: return body # Just return the "wrapped" element first_key, first_item = body.items()[0] if isinstance(first_item, (dict, list)): return first_item except (ValueError, IndexError): pass return body elif self._get_type() is "xml": element = etree.fromstring(body) if any(s in element.tag for s in self.dict_tags): # Parse dictionary-like xmls (metadata, etc) dictionary = {} for el in element.getchildren(): dictionary[u"%s" % el.get("key")] = u"%s" % el.text return dictionary if any(s in element.tag for s in self.list_tags): # Parse list-like xmls (users, roles, etc) array = [] for child in element.getchildren(): array.append(common.xml_to_json(child)) return array # Parse one-item-like xmls (user, role, etc) return common.xml_to_json(element) def response_checker(self, method, resp, resp_body): if (resp.status in set((204, 205, 304)) or resp.status < 200 or method.upper() == 'HEAD') and resp_body: raise exceptions.ResponseWithNonEmptyBody(status=resp.status) # NOTE(afazekas): # If the HTTP Status Code is 205 # 'The response MUST NOT include an entity.' # A HTTP entity has an entity-body and an 'entity-header'. # In the HTTP response specification (Section 6) the 'entity-header' # 'generic-header' and 'response-header' are in OR relation. # All headers not in the above two group are considered as entity # header in every interpretation. if (resp.status == 205 and 0 != len(set(resp.keys()) - set(('status',)) - self.response_header_lc - self.general_header_lc)): raise exceptions.ResponseWithEntity() # NOTE(afazekas) # Now the swift sometimes (delete not empty container) # returns with non json error response, we can create new rest class # for swift. # Usually RFC2616 says error responses SHOULD contain an explanation. # The warning is normal for SHOULD/SHOULD NOT case # Likely it will cause an error if not resp_body and resp.status >= 400: self.LOG.warning("status >= 400 response with empty body") def _request(self, method, url, headers=None, body=None): """A simple HTTP request interface.""" # Authenticate the request with the auth provider req_url, req_headers, req_body = self.auth_provider.auth_request( method, url, headers, body, self.filters) # Do the actual request, and time it start = time.time() resp, resp_body = self.http_obj.request( req_url, method, headers=req_headers, body=req_body) end = time.time() self._log_request(method, req_url, resp, secs=(end - start), req_headers=req_headers, req_body=req_body, resp_body=resp_body) # Verify HTTP response codes self.response_checker(method, resp, resp_body) return resp, resp_body def request(self, method, url, extra_headers=False, headers=None, body=None): # if extra_headers is True # default headers would be added to headers retry = 0 if headers is None: # NOTE(vponomaryov): if some client do not need headers, # it should explicitly pass empty dict headers = self.get_headers() elif extra_headers: try: headers = headers.copy() headers.update(self.get_headers()) except (ValueError, TypeError): headers = self.get_headers() resp, resp_body = self._request(method, url, headers=headers, body=body) while (resp.status == 413 and 'retry-after' in resp and not self.is_absolute_limit( resp, self._parse_resp(resp_body)) and retry < MAX_RECURSION_DEPTH): retry += 1 delay = int(resp['retry-after']) time.sleep(delay) resp, resp_body = self._request(method, url, headers=headers, body=body) self._error_checker(method, url, headers, body, resp, resp_body) return resp, resp_body def _error_checker(self, method, url, headers, body, resp, resp_body): # NOTE(mtreinish): Check for httplib response from glance_http. The # object can't be used here because importing httplib breaks httplib2. # If another object from a class not imported were passed here as # resp this could possibly fail if str(type(resp)) == "": ctype = resp.getheader('content-type') else: try: ctype = resp['content-type'] # NOTE(mtreinish): Keystone delete user responses doesn't have a # content-type header. (They don't have a body) So just pretend it # is set. except KeyError: ctype = 'application/json' # It is not an error response if resp.status < 400: return JSON_ENC = ['application/json', 'application/json; charset=utf-8'] # NOTE(mtreinish): This is for compatibility with Glance and swift # APIs. These are the return content types that Glance api v1 # (and occasionally swift) are using. TXT_ENC = ['text/plain', 'text/html', 'text/html; charset=utf-8', 'text/plain; charset=utf-8'] XML_ENC = ['application/xml', 'application/xml; charset=utf-8'] if ctype.lower() in JSON_ENC or ctype.lower() in XML_ENC: parse_resp = True elif ctype.lower() in TXT_ENC: parse_resp = False else: raise exceptions.InvalidContentType(str(resp.status)) if resp.status == 401 or resp.status == 403: raise exceptions.Unauthorized(resp_body) if resp.status == 404: raise exceptions.NotFound(resp_body) if resp.status == 400: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.BadRequest(resp_body) if resp.status == 409: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.Conflict(resp_body) if resp.status == 413: if parse_resp: resp_body = self._parse_resp(resp_body) if self.is_absolute_limit(resp, resp_body): raise exceptions.OverLimit(resp_body) else: raise exceptions.RateLimitExceeded(resp_body) if resp.status == 422: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.UnprocessableEntity(resp_body) if resp.status in (500, 501): message = resp_body if parse_resp: try: resp_body = self._parse_resp(resp_body) except ValueError: # If response body is a non-json string message. # Use resp_body as is and raise InvalidResponseBody # exception. raise exceptions.InvalidHTTPResponseBody(message) else: if isinstance(resp_body, dict): # I'm seeing both computeFault # and cloudServersFault come back. # Will file a bug to fix, but leave as is for now. if 'cloudServersFault' in resp_body: message = resp_body['cloudServersFault']['message'] elif 'computeFault' in resp_body: message = resp_body['computeFault']['message'] elif 'error' in resp_body: # Keystone errors message = resp_body['error']['message'] raise exceptions.IdentityError(message) elif 'message' in resp_body: message = resp_body['message'] else: message = resp_body raise exceptions.ServerFault(message) if resp.status >= 400: raise exceptions.UnexpectedResponseCode(str(resp.status)) def is_absolute_limit(self, resp, resp_body): if (not isinstance(resp_body, collections.Mapping) or 'retry-after' not in resp): return True if self._get_type() is "json": over_limit = resp_body.get('overLimit', None) if not over_limit: return True return 'exceed' in over_limit.get('message', 'blabla') elif self._get_type() is "xml": return 'exceed' in resp_body.get('message', 'blabla') def wait_for_resource_deletion(self, id): """Waits for a resource to be deleted.""" start_time = int(time.time()) while True: if self.is_resource_deleted(id): return if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def is_resource_deleted(self, id): """ Subclasses override with specific deletion detection. """ message = ('"%s" does not implement is_resource_deleted' % self.__class__.__name__) raise NotImplementedError(message) @classmethod def validate_response(cls, schema, resp, body): # Only check the response if the status code is a success code # TODO(cyeoh): Eventually we should be able to verify that a failure # code if it exists is something that we expect. This is explicitly # declared in the V3 API and so we should be able to export this in # the response schema. For now we'll ignore it. if resp.status in HTTP_SUCCESS: response_code = schema['status_code'] if resp.status not in response_code: msg = ("The status code(%s) is different than the expected " "one(%s)") % (resp.status, response_code) raise exceptions.InvalidHttpSuccessCode(msg) # Check the body of a response body_schema = schema.get('response_body') if body_schema: try: jsonschema.validate(body, body_schema) except jsonschema.ValidationError as ex: msg = ("HTTP response body is invalid (%s)") % ex raise exceptions.InvalidHTTPResponseBody(msg) else: if body: msg = ("HTTP response body should not exist (%s)") % body raise exceptions.InvalidHTTPResponseBody(msg) # Check the header of a response header_schema = schema.get('response_header') if header_schema: try: jsonschema.validate(resp, header_schema) except jsonschema.ValidationError as ex: msg = ("HTTP response header is invalid (%s)") % ex raise exceptions.InvalidHTTPResponseHeader(msg) class NegativeRestClient(RestClient): """ Version of RestClient that does not raise exceptions. """ def _error_checker(self, method, url, headers, body, resp, resp_body): pass def send_request(self, method, url_template, resources, body=None): url = url_template % tuple(resources) if method == "GET": resp, body = self.get(url) elif method == "POST": resp, body = self.post(url, body) elif method == "PUT": resp, body = self.put(url, body) elif method == "PATCH": resp, body = self.patch(url, body) elif method == "HEAD": resp, body = self.head(url) elif method == "DELETE": resp, body = self.delete(url) elif method == "COPY": resp, body = self.copy(url) else: assert False return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/common/tempest_fixtures.py0000664000175000017500000000145612332757070025040 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.openstack.common.fixture import lockutils class LockFixture(lockutils.LockFixture): def __init__(self, name): super(LockFixture, self).__init__(name, 'tempest-') tempest-2014.1.dev4108.gf22b6cc/tempest/common/glance_http.py0000664000175000017500000003350312332757070023714 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Originally copied from python-glanceclient import copy import hashlib import httplib import json import OpenSSL import posixpath import re from six import moves import socket import StringIO import struct import urlparse from tempest import exceptions as exc from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) USER_AGENT = 'tempest' CHUNKSIZE = 1024 * 64 # 64kB TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$') class HTTPClient(object): def __init__(self, auth_provider, filters, **kwargs): self.auth_provider = auth_provider self.filters = filters self.endpoint = auth_provider.base_url(filters) endpoint_parts = urlparse.urlparse(self.endpoint) self.endpoint_scheme = endpoint_parts.scheme self.endpoint_hostname = endpoint_parts.hostname self.endpoint_port = endpoint_parts.port self.endpoint_path = endpoint_parts.path self.connection_class = self.get_connection_class(self.endpoint_scheme) self.connection_kwargs = self.get_connection_kwargs( self.endpoint_scheme, **kwargs) @staticmethod def get_connection_class(scheme): if scheme == 'https': return VerifiedHTTPSConnection else: return httplib.HTTPConnection @staticmethod def get_connection_kwargs(scheme, **kwargs): _kwargs = {'timeout': float(kwargs.get('timeout', 600))} if scheme == 'https': _kwargs['cacert'] = kwargs.get('cacert', None) _kwargs['cert_file'] = kwargs.get('cert_file', None) _kwargs['key_file'] = kwargs.get('key_file', None) _kwargs['insecure'] = kwargs.get('insecure', False) _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True) return _kwargs def get_connection(self): _class = self.connection_class try: return _class(self.endpoint_hostname, self.endpoint_port, **self.connection_kwargs) except httplib.InvalidURL: raise exc.EndpointNotFound def _http_request(self, url, method, **kwargs): """Send an http request with the specified characteristics. Wrapper around httplib.HTTP(S)Connection.request to handle tasks such as setting headers and error handling. """ # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', USER_AGENT) self._log_request(method, url, kwargs['headers']) conn = self.get_connection() try: url_parts = urlparse.urlparse(url) conn_url = posixpath.normpath(url_parts.path) LOG.debug('Actual Path: {path}'.format(path=conn_url)) if kwargs['headers'].get('Transfer-Encoding') == 'chunked': conn.putrequest(method, conn_url) for header, value in kwargs['headers'].items(): conn.putheader(header, value) conn.endheaders() chunk = kwargs['body'].read(CHUNKSIZE) # Chunk it, baby... while chunk: conn.send('%x\r\n%s\r\n' % (len(chunk), chunk)) chunk = kwargs['body'].read(CHUNKSIZE) conn.send('0\r\n\r\n') else: conn.request(method, conn_url, **kwargs) resp = conn.getresponse() except socket.gaierror as e: message = ("Error finding address for %(url)s: %(e)s" % {'url': url, 'e': e}) raise exc.EndpointNotFound(message) except (socket.error, socket.timeout) as e: message = ("Error communicating with %(endpoint)s %(e)s" % {'endpoint': self.endpoint, 'e': e}) raise exc.TimeoutException(message) body_iter = ResponseBodyIterator(resp) # Read body into string if it isn't obviously image data if resp.getheader('content-type', None) != 'application/octet-stream': body_str = ''.join([body_chunk for body_chunk in body_iter]) body_iter = StringIO.StringIO(body_str) self._log_response(resp, None) else: self._log_response(resp, body_iter) return resp, body_iter def _log_request(self, method, url, headers): LOG.info('Request: ' + method + ' ' + url) if headers: headers_out = headers if 'X-Auth-Token' in headers and headers['X-Auth-Token']: token = headers['X-Auth-Token'] if len(token) > 64 and TOKEN_CHARS_RE.match(token): headers_out = headers.copy() headers_out['X-Auth-Token'] = "" LOG.info('Request Headers: ' + str(headers_out)) def _log_response(self, resp, body): status = str(resp.status) LOG.info("Response Status: " + status) if resp.getheaders(): LOG.info('Response Headers: ' + str(resp.getheaders())) if body: str_body = str(body) length = len(body) LOG.info('Response Body: ' + str_body[:2048]) if length >= 2048: self.LOG.debug("Large body (%d) md5 summary: %s", length, hashlib.md5(str_body).hexdigest()) def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') if 'body' in kwargs: kwargs['body'] = json.dumps(kwargs['body']) resp, body_iter = self._http_request(url, method, **kwargs) if 'application/json' in resp.getheader('content-type', ''): body = ''.join([chunk for chunk in body_iter]) try: body = json.loads(body) except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') if 'body' in kwargs: if (hasattr(kwargs['body'], 'read') and method.lower() in ('post', 'put')): # We use 'Transfer-Encoding: chunked' because # body size may not always be known in advance. kwargs['headers']['Transfer-Encoding'] = 'chunked' # Decorate the request with auth req_url, kwargs['headers'], kwargs['body'] = \ self.auth_provider.auth_request( method=method, url=url, headers=kwargs['headers'], body=kwargs.get('body', None), filters=self.filters) return self._http_request(req_url, method, **kwargs) class OpenSSLConnectionDelegator(object): """ An OpenSSL.SSL.Connection delegator. Supplies an additional 'makefile' method which httplib requires and is not present in OpenSSL.SSL.Connection. Note: Since it is not possible to inherit from OpenSSL.SSL.Connection a delegator must be used. """ def __init__(self, *args, **kwargs): self.connection = OpenSSL.SSL.Connection(*args, **kwargs) def __getattr__(self, name): return getattr(self.connection, name) def makefile(self, *args, **kwargs): # Ensure the socket is closed when this file is closed kwargs['close'] = True return socket._fileobject(self.connection, *args, **kwargs) class VerifiedHTTPSConnection(httplib.HTTPSConnection): """ Extended HTTPSConnection which uses the OpenSSL library for enhanced SSL support. Note: Much of this functionality can eventually be replaced with native Python 3.3 code. """ def __init__(self, host, port=None, key_file=None, cert_file=None, cacert=None, timeout=None, insecure=False, ssl_compression=True): httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file, cert_file=cert_file) self.key_file = key_file self.cert_file = cert_file self.timeout = timeout self.insecure = insecure self.ssl_compression = ssl_compression self.cacert = cacert self.setcontext() @staticmethod def host_matches_cert(host, x509): """ Verify that the the x509 certificate we have received from 'host' correctly identifies the server we are connecting to, ie that the certificate's Common Name or a Subject Alternative Name matches 'host'. """ # First see if we can match the CN if x509.get_subject().commonName == host: return True # Also try Subject Alternative Names for a match san_list = None for i in moves.xrange(x509.get_extension_count()): ext = x509.get_extension(i) if ext.get_short_name() == 'subjectAltName': san_list = str(ext) for san in ''.join(san_list.split()).split(','): if san == "DNS:%s" % host: return True # Server certificate does not match host msg = ('Host "%s" does not match x509 certificate contents: ' 'CommonName "%s"' % (host, x509.get_subject().commonName)) if san_list is not None: msg = msg + ', subjectAltName "%s"' % san_list raise exc.SSLCertificateError(msg) def verify_callback(self, connection, x509, errnum, depth, preverify_ok): if x509.has_expired(): msg = "SSL Certificate expired on '%s'" % x509.get_notAfter() raise exc.SSLCertificateError(msg) if depth == 0 and preverify_ok is True: # We verify that the host matches against the last # certificate in the chain return self.host_matches_cert(self.host, x509) else: # Pass through OpenSSL's default result return preverify_ok def setcontext(self): """ Set up the OpenSSL context. """ self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) if self.ssl_compression is False: self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION if self.insecure is not True: self.context.set_verify(OpenSSL.SSL.VERIFY_PEER, self.verify_callback) else: self.context.set_verify(OpenSSL.SSL.VERIFY_NONE, self.verify_callback) if self.cert_file: try: self.context.use_certificate_file(self.cert_file) except Exception as e: msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e) raise exc.SSLConfigurationError(msg) if self.key_file is None: # We support having key and cert in same file try: self.context.use_privatekey_file(self.cert_file) except Exception as e: msg = ('No key file specified and unable to load key ' 'from "%s" %s' % (self.cert_file, e)) raise exc.SSLConfigurationError(msg) if self.key_file: try: self.context.use_privatekey_file(self.key_file) except Exception as e: msg = 'Unable to load key from "%s" %s' % (self.key_file, e) raise exc.SSLConfigurationError(msg) if self.cacert: try: self.context.load_verify_locations(self.cacert) except Exception as e: msg = 'Unable to load CA from "%s"' % (self.cacert, e) raise exc.SSLConfigurationError(msg) else: self.context.set_default_verify_paths() def connect(self): """ Connect to an SSL port using the OpenSSL library and apply per-connection parameters. """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.timeout is not None: # '0' microseconds sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, struct.pack('LL', self.timeout, 0)) self.sock = OpenSSLConnectionDelegator(self.context, sock) self.sock.connect((self.host, self.port)) def close(self): if self.sock: # Remove the reference to the socket but don't close it yet. # Response close will close both socket and associated # file. Closing socket too soon will cause response # reads to fail with socket IO error 'Bad file descriptor'. self.sock = None httplib.HTTPSConnection.close(self) class ResponseBodyIterator(object): """A class that acts as an iterator over an HTTP response.""" def __init__(self, resp): self.resp = resp def __iter__(self): while True: yield self.next() def next(self): chunk = self.resp.read(CHUNKSIZE) if chunk: return chunk else: raise StopIteration() tempest-2014.1.dev4108.gf22b6cc/tempest/common/generate_sample_tempest.py0000664000175000017500000000274212332757070026321 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import sys import tempest.config from tempest.openstack.common.config import generator # NOTE(mtreinish): This hack is needed because of how oslo config is used in # tempest. Tempest is run from inside a test runner and so we can't rely on the # global CONF object being fully populated when we run a test. (test runners # don't init every file for running a test) So to get around that we manually # load the config file in tempest for each test class to ensure that every # config option is set. However, the tool expects the CONF object to be fully # populated when it inits all the files in the project. This just works around # the issue by manually loading the config file (which may or may not exist) # which will populate all the options before running the generator. if __name__ == "__main__": tempest.config.register_opts() generator.generate(sys.argv[1:]) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/0000775000175000017500000000000012332757136021550 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/0000775000175000017500000000000012332757136023040 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/lockutils.py0000664000175000017500000002332512332757070025425 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import errno import functools import os import shutil import subprocess import sys import tempfile import threading import time import weakref from oslo.config import cfg from tempest.openstack.common import fileutils from tempest.openstack.common.gettextutils import _ from tempest.openstack.common import local from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) util_opts = [ cfg.BoolOpt('disable_process_locking', default=False, help='Whether to disable inter-process locks'), cfg.StrOpt('lock_path', default=os.environ.get("TEMPEST_LOCK_PATH"), help=('Directory to use for lock files.')) ] CONF = cfg.CONF CONF.register_opts(util_opts) def set_defaults(lock_path): cfg.set_defaults(util_opts, lock_path=lock_path) class _InterProcessLock(object): """Lock implementation which allows multiple locks, working around issues like bugs.debian.org/cgi-bin/bugreport.cgi?bug=632857 and does not require any cleanup. Since the lock is always held on a file descriptor rather than outside of the process, the lock gets dropped automatically if the process crashes, even if __exit__ is not executed. There are no guarantees regarding usage by multiple green threads in a single process here. This lock works only between processes. Exclusive access between local threads should be achieved using the semaphores in the @synchronized decorator. Note these locks are released when the descriptor is closed, so it's not safe to close the file descriptor while another green thread holds the lock. Just opening and closing the lock file can break synchronisation, so lock files must be accessed only using this abstraction. """ def __init__(self, name): self.lockfile = None self.fname = name def __enter__(self): self.lockfile = open(self.fname, 'w') while True: try: # Using non-blocking locks since green threads are not # patched to deal with blocking locking calls. # Also upon reading the MSDN docs for locking(), it seems # to have a laughable 10 attempts "blocking" mechanism. self.trylock() return self except IOError as e: if e.errno in (errno.EACCES, errno.EAGAIN): # external locks synchronise things like iptables # updates - give it some time to prevent busy spinning time.sleep(0.01) else: raise def __exit__(self, exc_type, exc_val, exc_tb): try: self.unlock() self.lockfile.close() except IOError: LOG.exception(_("Could not release the acquired lock `%s`"), self.fname) def trylock(self): raise NotImplementedError() def unlock(self): raise NotImplementedError() class _WindowsLock(_InterProcessLock): def trylock(self): msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1) def unlock(self): msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1) class _PosixLock(_InterProcessLock): def trylock(self): fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) def unlock(self): fcntl.lockf(self.lockfile, fcntl.LOCK_UN) if os.name == 'nt': import msvcrt InterProcessLock = _WindowsLock else: import fcntl InterProcessLock = _PosixLock _semaphores = weakref.WeakValueDictionary() _semaphores_lock = threading.Lock() @contextlib.contextmanager def lock(name, lock_file_prefix=None, external=False, lock_path=None): """Context based lock This function yields a `threading.Semaphore` instance (if we don't use eventlet.monkey_patch(), else `semaphore.Semaphore`) unless external is True, in which case, it'll yield an InterProcessLock instance. :param lock_file_prefix: The lock_file_prefix argument is used to provide lock files on disk with a meaningful prefix. :param external: The external keyword argument denotes whether this lock should work across multiple processes. This means that if two different workers both run a a method decorated with @synchronized('mylock', external=True), only one of them will execute at a time. :param lock_path: The lock_path keyword argument is used to specify a special location for external lock files to live. If nothing is set, then CONF.lock_path is used as a default. """ with _semaphores_lock: try: sem = _semaphores[name] except KeyError: sem = threading.Semaphore() _semaphores[name] = sem with sem: LOG.debug(_('Got semaphore "%(lock)s"'), {'lock': name}) # NOTE(mikal): I know this looks odd if not hasattr(local.strong_store, 'locks_held'): local.strong_store.locks_held = [] local.strong_store.locks_held.append(name) try: if external and not CONF.disable_process_locking: LOG.debug(_('Attempting to grab file lock "%(lock)s"'), {'lock': name}) # We need a copy of lock_path because it is non-local local_lock_path = lock_path or CONF.lock_path if not local_lock_path: raise cfg.RequiredOptError('lock_path') if not os.path.exists(local_lock_path): fileutils.ensure_tree(local_lock_path) LOG.info(_('Created lock path: %s'), local_lock_path) def add_prefix(name, prefix): if not prefix: return name sep = '' if prefix.endswith('-') else '-' return '%s%s%s' % (prefix, sep, name) # NOTE(mikal): the lock name cannot contain directory # separators lock_file_name = add_prefix(name.replace(os.sep, '_'), lock_file_prefix) lock_file_path = os.path.join(local_lock_path, lock_file_name) try: lock = InterProcessLock(lock_file_path) with lock as lock: LOG.debug(_('Got file lock "%(lock)s" at %(path)s'), {'lock': name, 'path': lock_file_path}) yield lock finally: LOG.debug(_('Released file lock "%(lock)s" at %(path)s'), {'lock': name, 'path': lock_file_path}) else: yield sem finally: local.strong_store.locks_held.remove(name) def synchronized(name, lock_file_prefix=None, external=False, lock_path=None): """Synchronization decorator. Decorating a method like so:: @synchronized('mylock') def foo(self, *args): ... ensures that only one thread will execute the foo method at a time. Different methods can share the same lock:: @synchronized('mylock') def foo(self, *args): ... @synchronized('mylock') def bar(self, *args): ... This way only one of either foo or bar can be executing at a time. """ def wrap(f): @functools.wraps(f) def inner(*args, **kwargs): try: with lock(name, lock_file_prefix, external, lock_path): LOG.debug(_('Got semaphore / lock "%(function)s"'), {'function': f.__name__}) return f(*args, **kwargs) finally: LOG.debug(_('Semaphore / lock released "%(function)s"'), {'function': f.__name__}) return inner return wrap def synchronized_with_prefix(lock_file_prefix): """Partial object generator for the synchronization decorator. Redefine @synchronized in each project like so:: (in nova/utils.py) from nova.openstack.common import lockutils synchronized = lockutils.synchronized_with_prefix('nova-') (in nova/foo.py) from nova import utils @utils.synchronized('mylock') def bar(self, *args): ... The lock_file_prefix argument is used to provide lock files on disk with a meaningful prefix. """ return functools.partial(synchronized, lock_file_prefix=lock_file_prefix) def main(argv): """Create a dir for locks and pass it to command from arguments If you run this: python -m openstack.common.lockutils python setup.py testr a temporary directory will be created for all your locks and passed to all your tests in an environment variable. The temporary dir will be deleted afterwards and the return value will be preserved. """ lock_dir = tempfile.mkdtemp() os.environ["TEMPEST_LOCK_PATH"] = lock_dir try: ret_val = subprocess.call(argv[1:]) finally: shutil.rmtree(lock_dir, ignore_errors=True) return ret_val if __name__ == '__main__': sys.exit(main(sys.argv)) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/excutils.py0000664000175000017500000000717212332757070025256 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # Copyright 2012, 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. """ Exception related utilities. """ import logging import sys import time import traceback import six from tempest.openstack.common.gettextutils import _ class save_and_reraise_exception(object): """Save current exception, run some code and then re-raise. In some cases the exception context can be cleared, resulting in None being attempted to be re-raised after an exception handler is run. This can happen when eventlet switches greenthreads or when running an exception handler, code raises and catches an exception. In both cases the exception context will be cleared. To work around this, we save the exception state, run handler code, and then re-raise the original exception. If another exception occurs, the saved exception is logged and the new exception is re-raised. In some cases the caller may not want to re-raise the exception, and for those circumstances this context provides a reraise flag that can be used to suppress the exception. For example:: except Exception: with save_and_reraise_exception() as ctxt: decide_if_need_reraise() if not should_be_reraised: ctxt.reraise = False """ def __init__(self): self.reraise = True def __enter__(self): self.type_, self.value, self.tb, = sys.exc_info() return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: logging.error(_('Original exception being dropped: %s'), traceback.format_exception(self.type_, self.value, self.tb)) return False if self.reraise: six.reraise(self.type_, self.value, self.tb) def forever_retry_uncaught_exceptions(infunc): def inner_func(*args, **kwargs): last_log_time = 0 last_exc_message = None exc_count = 0 while True: try: return infunc(*args, **kwargs) except Exception as exc: this_exc_message = six.u(str(exc)) if this_exc_message == last_exc_message: exc_count += 1 else: exc_count = 1 # Do not log any more frequently than once a minute unless # the exception message changes cur_time = int(time.time()) if (cur_time - last_log_time > 60 or this_exc_message != last_exc_message): logging.exception( _('Unexpected exception occurred %d time(s)... ' 'retrying.') % exc_count) last_log_time = cur_time last_exc_message = this_exc_message exc_count = 0 # This should be a very rare event. In case it isn't, do # a sleep. time.sleep(1) return inner_func tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/importutils.py0000664000175000017500000000451412332757070026006 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Import related utilities and helper functions. """ import sys import traceback def import_class(import_str): """Returns a class from a string including module and class.""" mod_str, _sep, class_str = import_str.rpartition('.') try: __import__(mod_str) return getattr(sys.modules[mod_str], class_str) except (ValueError, AttributeError): raise ImportError('Class %s cannot be found (%s)' % (class_str, traceback.format_exception(*sys.exc_info()))) def import_object(import_str, *args, **kwargs): """Import a class and return an instance of it.""" return import_class(import_str)(*args, **kwargs) def import_object_ns(name_space, import_str, *args, **kwargs): """Tries to import object from default namespace. Imports a class and return an instance of it, first by trying to find the class in a default namespace, then failing back to a full path if not found in the default namespace. """ import_value = "%s.%s" % (name_space, import_str) try: return import_class(import_value)(*args, **kwargs) except ImportError: return import_class(import_str)(*args, **kwargs) def import_module(import_str): """Import a module.""" __import__(import_str) return sys.modules[import_str] def import_versioned_module(version, submodule=None): module = 'tempest.v%s' % version if submodule: module = '.'.join((module, submodule)) return import_module(module) def try_import(import_str, default=None): """Try to import a module and if it fails return default.""" try: return import_module(import_str) except ImportError: return default tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/timeutils.py0000664000175000017500000001424012332757070025427 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Time related utilities and helper functions. """ import calendar import datetime import time import iso8601 import six # ISO 8601 extended time format with microseconds _ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f' _ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND def isotime(at=None, subsecond=False): """Stringify time in ISO 8601 format.""" if not at: at = utcnow() st = at.strftime(_ISO8601_TIME_FORMAT if not subsecond else _ISO8601_TIME_FORMAT_SUBSECOND) tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' st += ('Z' if tz == 'UTC' else tz) return st def parse_isotime(timestr): """Parse time from ISO 8601 format.""" try: return iso8601.parse_date(timestr) except iso8601.ParseError as e: raise ValueError(six.text_type(e)) except TypeError as e: raise ValueError(six.text_type(e)) def strtime(at=None, fmt=PERFECT_TIME_FORMAT): """Returns formatted utcnow.""" if not at: at = utcnow() return at.strftime(fmt) def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): """Turn a formatted time back into a datetime.""" return datetime.datetime.strptime(timestr, fmt) def normalize_time(timestamp): """Normalize time in arbitrary timezone to UTC naive object.""" offset = timestamp.utcoffset() if offset is None: return timestamp return timestamp.replace(tzinfo=None) - offset def is_older_than(before, seconds): """Return True if before is older than seconds.""" if isinstance(before, six.string_types): before = parse_strtime(before).replace(tzinfo=None) else: before = before.replace(tzinfo=None) return utcnow() - before > datetime.timedelta(seconds=seconds) def is_newer_than(after, seconds): """Return True if after is newer than seconds.""" if isinstance(after, six.string_types): after = parse_strtime(after).replace(tzinfo=None) else: after = after.replace(tzinfo=None) return after - utcnow() > datetime.timedelta(seconds=seconds) def utcnow_ts(): """Timestamp version of our utcnow function.""" if utcnow.override_time is None: # NOTE(kgriffs): This is several times faster # than going through calendar.timegm(...) return int(time.time()) return calendar.timegm(utcnow().timetuple()) def utcnow(): """Overridable version of utils.utcnow.""" if utcnow.override_time: try: return utcnow.override_time.pop(0) except AttributeError: return utcnow.override_time return datetime.datetime.utcnow() def iso8601_from_timestamp(timestamp): """Returns a iso8601 formated date from timestamp.""" return isotime(datetime.datetime.utcfromtimestamp(timestamp)) utcnow.override_time = None def set_time_override(override_time=None): """Overrides utils.utcnow. Make it return a constant time or a list thereof, one at a time. :param override_time: datetime instance or list thereof. If not given, defaults to the current UTC time. """ utcnow.override_time = override_time or datetime.datetime.utcnow() def advance_time_delta(timedelta): """Advance overridden time using a datetime.timedelta.""" assert(not utcnow.override_time is None) try: for dt in utcnow.override_time: dt += timedelta except TypeError: utcnow.override_time += timedelta def advance_time_seconds(seconds): """Advance overridden time by seconds.""" advance_time_delta(datetime.timedelta(0, seconds)) def clear_time_override(): """Remove the overridden time.""" utcnow.override_time = None def marshall_now(now=None): """Make an rpc-safe datetime with microseconds. Note: tzinfo is stripped, but not required for relative times. """ if not now: now = utcnow() return dict(day=now.day, month=now.month, year=now.year, hour=now.hour, minute=now.minute, second=now.second, microsecond=now.microsecond) def unmarshall_time(tyme): """Unmarshall a datetime dict.""" return datetime.datetime(day=tyme['day'], month=tyme['month'], year=tyme['year'], hour=tyme['hour'], minute=tyme['minute'], second=tyme['second'], microsecond=tyme['microsecond']) def delta_seconds(before, after): """Return the difference between two timing objects. Compute the difference in seconds between two date, time, or datetime objects (as a float, to microsecond resolution). """ delta = after - before return total_seconds(delta) def total_seconds(delta): """Return the total seconds of datetime.timedelta object. Compute total seconds of datetime.timedelta, datetime.timedelta doesn't have method total_seconds in Python2.6, calculate it manually. """ try: return delta.total_seconds() except AttributeError: return ((delta.days * 24 * 3600) + delta.seconds + float(delta.microseconds) / (10 ** 6)) def is_soon(dt, window): """Determines if time is going to happen in the next window seconds. :param dt: the time :param window: minimum seconds to remain to consider the time not soon :return: True if expiration is within the given duration """ soon = (utcnow() + datetime.timedelta(seconds=window)) return normalize_time(dt) <= soon tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/fixture/0000775000175000017500000000000012332757136024526 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/fixture/lockutils.py0000664000175000017500000000354212332757070027112 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures from tempest.openstack.common import lockutils class LockFixture(fixtures.Fixture): """External locking fixture. This fixture is basically an alternative to the synchronized decorator with the external flag so that tearDowns and addCleanups will be included in the lock context for locking between tests. The fixture is recommended to be the first line in a test method, like so:: def test_method(self): self.useFixture(LockFixture) ... or the first line in setUp if all the test methods in the class are required to be serialized. Something like:: class TestCase(testtools.testcase): def setUp(self): self.useFixture(LockFixture) super(TestCase, self).setUp() ... This is because addCleanups are put on a LIFO queue that gets run after the test method exits. (either by completing or raising an exception) """ def __init__(self, name, lock_file_prefix=None): self.mgr = lockutils.lock(name, lock_file_prefix, True) def setUp(self): super(LockFixture, self).setUp() self.addCleanup(self.mgr.__exit__, None, None, None) self.mgr.__enter__() tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/fixture/config.py0000664000175000017500000000271012332757070026342 0ustar chuckchuck00000000000000# # Copyright 2013 Mirantis, Inc. # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures from oslo.config import cfg import six class Config(fixtures.Fixture): """Override some configuration values. The keyword arguments are the names of configuration options to override and their values. If a group argument is supplied, the overrides are applied to the specified configuration option group. All overrides are automatically cleared at the end of the current test by the reset() method, which is registered by addCleanup(). """ def __init__(self, conf=cfg.CONF): self.conf = conf def setUp(self): super(Config, self).setUp() self.addCleanup(self.conf.reset) def config(self, **kw): group = kw.pop('group', None) for k, v in six.iteritems(kw): self.conf.set_override(k, v, group) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/fixture/moxstubout.py0000664000175000017500000000230712332757070027330 0ustar chuckchuck00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures import mox class MoxStubout(fixtures.Fixture): """Deal with code around mox and stubout as a fixture.""" def setUp(self): super(MoxStubout, self).setUp() # emulate some of the mox stuff, we can't use the metaclass # because it screws with our generators self.mox = mox.Mox() self.stubs = self.mox.stubs self.addCleanup(self.mox.UnsetStubs) self.addCleanup(self.mox.VerifyAll) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/fixture/__init__.py0000664000175000017500000000000012332757070026622 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/fixture/mockpatch.py0000664000175000017500000000305112332757070027045 0ustar chuckchuck00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import fixtures import mock class PatchObject(fixtures.Fixture): """Deal with code around mock.""" def __init__(self, obj, attr, new=mock.DEFAULT, **kwargs): self.obj = obj self.attr = attr self.kwargs = kwargs self.new = new def setUp(self): super(PatchObject, self).setUp() _p = mock.patch.object(self.obj, self.attr, self.new, **self.kwargs) self.mock = _p.start() self.addCleanup(_p.stop) class Patch(fixtures.Fixture): """Deal with code around mock.patch.""" def __init__(self, obj, **kwargs): self.obj = obj self.kwargs = kwargs def setUp(self): super(Patch, self).setUp() _p = mock.patch(self.obj, **self.kwargs) self.mock = _p.start() self.addCleanup(_p.stop) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/config/0000775000175000017500000000000012332757136024305 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/config/generator.py0000664000175000017500000002445312332757070026652 0ustar chuckchuck00000000000000# Copyright 2012 SINA Corporation # Copyright 2014 Cisco Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Extracts OpenStack config option info from module(s).""" from __future__ import print_function import argparse import imp import os import re import socket import sys import textwrap from oslo.config import cfg import six import stevedore.named from tempest.openstack.common import gettextutils from tempest.openstack.common import importutils gettextutils.install('tempest') STROPT = "StrOpt" BOOLOPT = "BoolOpt" INTOPT = "IntOpt" FLOATOPT = "FloatOpt" LISTOPT = "ListOpt" DICTOPT = "DictOpt" MULTISTROPT = "MultiStrOpt" OPT_TYPES = { STROPT: 'string value', BOOLOPT: 'boolean value', INTOPT: 'integer value', FLOATOPT: 'floating point value', LISTOPT: 'list value', DICTOPT: 'dict value', MULTISTROPT: 'multi valued', } OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT, FLOATOPT, LISTOPT, DICTOPT, MULTISTROPT])) PY_EXT = ".py" BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) WORDWRAP_WIDTH = 60 def raise_extension_exception(extmanager, ep, err): raise def generate(argv): parser = argparse.ArgumentParser( description='generate sample configuration file', ) parser.add_argument('-m', dest='modules', action='append') parser.add_argument('-l', dest='libraries', action='append') parser.add_argument('srcfiles', nargs='*') parsed_args = parser.parse_args(argv) mods_by_pkg = dict() for filepath in parsed_args.srcfiles: pkg_name = filepath.split(os.sep)[1] mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]), os.path.basename(filepath).split('.')[0]]) mods_by_pkg.setdefault(pkg_name, list()).append(mod_str) # NOTE(lzyeval): place top level modules before packages pkg_names = sorted(pkg for pkg in mods_by_pkg if pkg.endswith(PY_EXT)) ext_names = sorted(pkg for pkg in mods_by_pkg if pkg not in pkg_names) pkg_names.extend(ext_names) # opts_by_group is a mapping of group name to an options list # The options list is a list of (module, options) tuples opts_by_group = {'DEFAULT': []} if parsed_args.modules: for module_name in parsed_args.modules: module = _import_module(module_name) if module: for group, opts in _list_opts(module): opts_by_group.setdefault(group, []).append((module_name, opts)) # Look for entry points defined in libraries (or applications) for # option discovery, and include their return values in the output. # # Each entry point should be a function returning an iterable # of pairs with the group name (or None for the default group) # and the list of Opt instances for that group. if parsed_args.libraries: loader = stevedore.named.NamedExtensionManager( 'oslo.config.opts', names=list(set(parsed_args.libraries)), invoke_on_load=False, on_load_failure_callback=raise_extension_exception ) for ext in loader: for group, opts in ext.plugin(): opt_list = opts_by_group.setdefault(group or 'DEFAULT', []) opt_list.append((ext.name, opts)) for pkg_name in pkg_names: mods = mods_by_pkg.get(pkg_name) mods.sort() for mod_str in mods: if mod_str.endswith('.__init__'): mod_str = mod_str[:mod_str.rfind(".")] mod_obj = _import_module(mod_str) if not mod_obj: raise RuntimeError("Unable to import module %s" % mod_str) for group, opts in _list_opts(mod_obj): opts_by_group.setdefault(group, []).append((mod_str, opts)) print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', [])) for group in sorted(opts_by_group.keys()): print_group_opts(group, opts_by_group[group]) def _import_module(mod_str): try: if mod_str.startswith('bin.'): imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:])) return sys.modules[mod_str[4:]] else: return importutils.import_module(mod_str) except Exception as e: sys.stderr.write("Error importing module %s: %s\n" % (mod_str, str(e))) return None def _is_in_group(opt, group): "Check if opt is in group." for value in group._opts.values(): # NOTE(llu): Temporary workaround for bug #1262148, wait until # newly released oslo.config support '==' operator. if not(value['opt'] != opt): return True return False def _guess_groups(opt, mod_obj): # is it in the DEFAULT group? if _is_in_group(opt, cfg.CONF): return 'DEFAULT' # what other groups is it in? for value in cfg.CONF.values(): if isinstance(value, cfg.CONF.GroupAttr): if _is_in_group(opt, value._group): return value._group.name raise RuntimeError( "Unable to find group for option %s, " "maybe it's defined twice in the same group?" % opt.name ) def _list_opts(obj): def is_opt(o): return (isinstance(o, cfg.Opt) and not isinstance(o, cfg.SubCommandOpt)) opts = list() for attr_str in dir(obj): attr_obj = getattr(obj, attr_str) if is_opt(attr_obj): opts.append(attr_obj) elif (isinstance(attr_obj, list) and all(map(lambda x: is_opt(x), attr_obj))): opts.extend(attr_obj) ret = {} for opt in opts: ret.setdefault(_guess_groups(opt, obj), []).append(opt) return ret.items() def print_group_opts(group, opts_by_module): print("[%s]" % group) print('') for mod, opts in opts_by_module: print('#') print('# Options defined in %s' % mod) print('#') print('') for opt in opts: _print_opt(opt) print('') def _get_my_ip(): try: csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) csock.connect(('8.8.8.8', 80)) (addr, port) = csock.getsockname() csock.close() return addr except socket.error: return None def _sanitize_default(name, value): """Set up a reasonably sensible default for pybasedir, my_ip and host.""" if value.startswith(sys.prefix): # NOTE(jd) Don't use os.path.join, because it is likely to think the # second part is an absolute pathname and therefore drop the first # part. value = os.path.normpath("/usr/" + value[len(sys.prefix):]) elif value.startswith(BASEDIR): return value.replace(BASEDIR, '/usr/lib/python/site-packages') elif BASEDIR in value: return value.replace(BASEDIR, '') elif value == _get_my_ip(): return '10.0.0.1' elif value in (socket.gethostname(), socket.getfqdn()) and 'host' in name: return 'tempest' elif value.strip() != value: return '"%s"' % value return value def _print_opt(opt): opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help if not opt_help: sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name) opt_help = "" opt_type = None try: opt_type = OPTION_REGEX.search(str(type(opt))).group(0) except (ValueError, AttributeError) as err: sys.stderr.write("%s\n" % str(err)) sys.exit(1) opt_help = u'%s (%s)' % (opt_help, OPT_TYPES[opt_type]) print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH))) if opt.deprecated_opts: for deprecated_opt in opt.deprecated_opts: if deprecated_opt.name: deprecated_group = (deprecated_opt.group if deprecated_opt.group else "DEFAULT") print('# Deprecated group/name - [%s]/%s' % (deprecated_group, deprecated_opt.name)) try: if opt_default is None: print('#%s=' % opt_name) elif opt_type == STROPT: assert(isinstance(opt_default, six.string_types)) print('#%s=%s' % (opt_name, _sanitize_default(opt_name, opt_default))) elif opt_type == BOOLOPT: assert(isinstance(opt_default, bool)) print('#%s=%s' % (opt_name, str(opt_default).lower())) elif opt_type == INTOPT: assert(isinstance(opt_default, int) and not isinstance(opt_default, bool)) print('#%s=%s' % (opt_name, opt_default)) elif opt_type == FLOATOPT: assert(isinstance(opt_default, float)) print('#%s=%s' % (opt_name, opt_default)) elif opt_type == LISTOPT: assert(isinstance(opt_default, list)) print('#%s=%s' % (opt_name, ','.join(opt_default))) elif opt_type == DICTOPT: assert(isinstance(opt_default, dict)) opt_default_strlist = [str(key) + ':' + str(value) for (key, value) in opt_default.items()] print('#%s=%s' % (opt_name, ','.join(opt_default_strlist))) elif opt_type == MULTISTROPT: assert(isinstance(opt_default, list)) if not opt_default: opt_default = [''] for default in opt_default: print('#%s=%s' % (opt_name, default)) print('') except Exception: sys.stderr.write('Error in option "%s"\n' % opt_name) sys.exit(1) def main(): generate(sys.argv[1:]) if __name__ == '__main__': main() tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/config/__init__.py0000664000175000017500000000000012332757070026401 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/log.py0000664000175000017500000005354512332757070024204 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Openstack logging handler. This module adds to logging functionality by adding the option to specify a context object when calling the various log methods. If the context object is not specified, default formatting is used. Additionally, an instance uuid may be passed as part of the log message, which is intended to make it easier for admins to find messages related to a specific instance. It also allows setting of formatting information through conf. """ import inspect import itertools import logging import logging.config import logging.handlers import os import re import sys import traceback from oslo.config import cfg import six from six import moves from tempest.openstack.common.gettextutils import _ from tempest.openstack.common import importutils from tempest.openstack.common import jsonutils from tempest.openstack.common import local _DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" _SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] # NOTE(ldbragst): Let's build a list of regex objects using the list of # _SANITIZE_KEYS we already have. This way, we only have to add the new key # to the list of _SANITIZE_KEYS and we can generate regular expressions # for XML and JSON automatically. _SANITIZE_PATTERNS = [] _FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', r'(<%(key)s>).*?()', r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])'] for key in _SANITIZE_KEYS: for pattern in _FORMAT_PATTERNS: reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) _SANITIZE_PATTERNS.append(reg_ex) common_cli_opts = [ cfg.BoolOpt('debug', short='d', default=False, help='Print debugging output (set logging level to ' 'DEBUG instead of default WARNING level).'), cfg.BoolOpt('verbose', short='v', default=False, help='Print more verbose output (set logging level to ' 'INFO instead of default WARNING level).'), ] logging_cli_opts = [ cfg.StrOpt('log-config-append', metavar='PATH', deprecated_name='log-config', help='The name of logging configuration file. It does not ' 'disable existing loggers, but just appends specified ' 'logging configuration to any other existing logging ' 'options. Please see the Python logging module ' 'documentation for details on logging configuration ' 'files.'), cfg.StrOpt('log-format', default=None, metavar='FORMAT', help='DEPRECATED. ' 'A logging.Formatter log message format string which may ' 'use any of the available logging.LogRecord attributes. ' 'This option is deprecated. Please use ' 'logging_context_format_string and ' 'logging_default_format_string instead.'), cfg.StrOpt('log-date-format', default=_DEFAULT_LOG_DATE_FORMAT, metavar='DATE_FORMAT', help='Format string for %%(asctime)s in log records. ' 'Default: %(default)s'), cfg.StrOpt('log-file', metavar='PATH', deprecated_name='logfile', help='(Optional) Name of log file to output to. ' 'If no default is set, logging will go to stdout.'), cfg.StrOpt('log-dir', deprecated_name='logdir', help='(Optional) The base directory used for relative ' '--log-file paths'), cfg.BoolOpt('use-syslog', default=False, help='Use syslog for logging.'), cfg.StrOpt('syslog-log-facility', default='LOG_USER', help='syslog facility to receive log lines') ] generic_log_opts = [ cfg.BoolOpt('use_stderr', default=True, help='Log output to standard error') ] log_opts = [ cfg.StrOpt('logging_context_format_string', default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [%(request_id)s %(user_identity)s] ' '%(instance)s%(message)s', help='format string to use for log messages with context'), cfg.StrOpt('logging_default_format_string', default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [-] %(instance)s%(message)s', help='format string to use for log messages without context'), cfg.StrOpt('logging_debug_format_suffix', default='%(funcName)s %(pathname)s:%(lineno)d', help='data to append to log format when level is DEBUG'), cfg.StrOpt('logging_exception_prefix', default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s ' '%(instance)s', help='prefix each line of exception output with this format'), cfg.ListOpt('default_log_levels', default=[ 'amqp=WARN', 'amqplib=WARN', 'boto=WARN', 'qpid=WARN', 'sqlalchemy=WARN', 'suds=INFO', 'iso8601=WARN', ], help='list of logger=LEVEL pairs'), cfg.BoolOpt('publish_errors', default=False, help='publish error events'), cfg.BoolOpt('fatal_deprecations', default=False, help='make deprecations fatal'), # NOTE(mikal): there are two options here because sometimes we are handed # a full instance (and could include more information), and other times we # are just handed a UUID for the instance. cfg.StrOpt('instance_format', default='[instance: %(uuid)s] ', help='If an instance is passed with the log message, format ' 'it like this'), cfg.StrOpt('instance_uuid_format', default='[instance: %(uuid)s] ', help='If an instance UUID is passed with the log message, ' 'format it like this'), ] CONF = cfg.CONF CONF.register_cli_opts(common_cli_opts) CONF.register_cli_opts(logging_cli_opts) CONF.register_opts(generic_log_opts) CONF.register_opts(log_opts) # our new audit level # NOTE(jkoelker) Since we synthesized an audit level, make the logging # module aware of it so it acts like other levels. logging.AUDIT = logging.INFO + 1 logging.addLevelName(logging.AUDIT, 'AUDIT') try: NullHandler = logging.NullHandler except AttributeError: # NOTE(jkoelker) NullHandler added in Python 2.7 class NullHandler(logging.Handler): def handle(self, record): pass def emit(self, record): pass def createLock(self): self.lock = None def _dictify_context(context): if context is None: return None if not isinstance(context, dict) and getattr(context, 'to_dict', None): context = context.to_dict() return context def _get_binary_name(): return os.path.basename(inspect.stack()[-1][1]) def _get_log_file_path(binary=None): logfile = CONF.log_file logdir = CONF.log_dir if logfile and not logdir: return logfile if logfile and logdir: return os.path.join(logdir, logfile) if logdir: binary = binary or _get_binary_name() return '%s.log' % (os.path.join(logdir, binary),) return None def mask_password(message, secret="***"): """Replace password with 'secret' in message. :param message: The string which includes security information. :param secret: value with which to replace passwords. :returns: The unicode value of message with the password fields masked. For example: >>> mask_password("'adminPass' : 'aaaaa'") "'adminPass' : '***'" >>> mask_password("'admin_pass' : 'aaaaa'") "'admin_pass' : '***'" >>> mask_password('"password" : "aaaaa"') '"password" : "***"' >>> mask_password("'original_password' : 'aaaaa'") "'original_password' : '***'" >>> mask_password("u'original_password' : u'aaaaa'") "u'original_password' : u'***'" """ message = six.text_type(message) # NOTE(ldbragst): Check to see if anything in message contains any key # specified in _SANITIZE_KEYS, if not then just return the message since # we don't have to mask any passwords. if not any(key in message for key in _SANITIZE_KEYS): return message secret = r'\g<1>' + secret + r'\g<2>' for pattern in _SANITIZE_PATTERNS: message = re.sub(pattern, secret, message) return message class BaseLoggerAdapter(logging.LoggerAdapter): def audit(self, msg, *args, **kwargs): self.log(logging.AUDIT, msg, *args, **kwargs) class LazyAdapter(BaseLoggerAdapter): def __init__(self, name='unknown', version='unknown'): self._logger = None self.extra = {} self.name = name self.version = version @property def logger(self): if not self._logger: self._logger = getLogger(self.name, self.version) return self._logger class ContextAdapter(BaseLoggerAdapter): warn = logging.LoggerAdapter.warning def __init__(self, logger, project_name, version_string): self.logger = logger self.project = project_name self.version = version_string @property def handlers(self): return self.logger.handlers def deprecated(self, msg, *args, **kwargs): stdmsg = _("Deprecated: %s") % msg if CONF.fatal_deprecations: self.critical(stdmsg, *args, **kwargs) raise DeprecatedConfig(msg=stdmsg) else: self.warn(stdmsg, *args, **kwargs) def process(self, msg, kwargs): # NOTE(mrodden): catch any Message/other object and # coerce to unicode before they can get # to the python logging and possibly # cause string encoding trouble if not isinstance(msg, six.string_types): msg = six.text_type(msg) if 'extra' not in kwargs: kwargs['extra'] = {} extra = kwargs['extra'] context = kwargs.pop('context', None) if not context: context = getattr(local.store, 'context', None) if context: extra.update(_dictify_context(context)) instance = kwargs.pop('instance', None) instance_uuid = (extra.get('instance_uuid', None) or kwargs.pop('instance_uuid', None)) instance_extra = '' if instance: instance_extra = CONF.instance_format % instance elif instance_uuid: instance_extra = (CONF.instance_uuid_format % {'uuid': instance_uuid}) extra['instance'] = instance_extra extra.setdefault('user_identity', kwargs.pop('user_identity', None)) extra['project'] = self.project extra['version'] = self.version extra['extra'] = extra.copy() return msg, kwargs class JSONFormatter(logging.Formatter): def __init__(self, fmt=None, datefmt=None): # NOTE(jkoelker) we ignore the fmt argument, but its still there # since logging.config.fileConfig passes it. self.datefmt = datefmt def formatException(self, ei, strip_newlines=True): lines = traceback.format_exception(*ei) if strip_newlines: lines = [moves.filter( lambda x: x, line.rstrip().splitlines()) for line in lines] lines = list(itertools.chain(*lines)) return lines def format(self, record): message = {'message': record.getMessage(), 'asctime': self.formatTime(record, self.datefmt), 'name': record.name, 'msg': record.msg, 'args': record.args, 'levelname': record.levelname, 'levelno': record.levelno, 'pathname': record.pathname, 'filename': record.filename, 'module': record.module, 'lineno': record.lineno, 'funcname': record.funcName, 'created': record.created, 'msecs': record.msecs, 'relative_created': record.relativeCreated, 'thread': record.thread, 'thread_name': record.threadName, 'process_name': record.processName, 'process': record.process, 'traceback': None} if hasattr(record, 'extra'): message['extra'] = record.extra if record.exc_info: message['traceback'] = self.formatException(record.exc_info) return jsonutils.dumps(message) def _create_logging_excepthook(product_name): def logging_excepthook(exc_type, value, tb): extra = {} if CONF.verbose: extra['exc_info'] = (exc_type, value, tb) getLogger(product_name).critical(str(value), **extra) return logging_excepthook class LogConfigError(Exception): message = _('Error loading logging config %(log_config)s: %(err_msg)s') def __init__(self, log_config, err_msg): self.log_config = log_config self.err_msg = err_msg def __str__(self): return self.message % dict(log_config=self.log_config, err_msg=self.err_msg) def _load_log_config(log_config_append): try: logging.config.fileConfig(log_config_append, disable_existing_loggers=False) except moves.configparser.Error as exc: raise LogConfigError(log_config_append, str(exc)) def setup(product_name): """Setup logging.""" if CONF.log_config_append: _load_log_config(CONF.log_config_append) else: _setup_logging_from_conf() sys.excepthook = _create_logging_excepthook(product_name) def set_defaults(logging_context_format_string): cfg.set_defaults(log_opts, logging_context_format_string= logging_context_format_string) def _find_facility_from_conf(): facility_names = logging.handlers.SysLogHandler.facility_names facility = getattr(logging.handlers.SysLogHandler, CONF.syslog_log_facility, None) if facility is None and CONF.syslog_log_facility in facility_names: facility = facility_names.get(CONF.syslog_log_facility) if facility is None: valid_facilities = facility_names.keys() consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON', 'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS', 'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP', 'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3', 'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7'] valid_facilities.extend(consts) raise TypeError(_('syslog facility must be one of: %s') % ', '.join("'%s'" % fac for fac in valid_facilities)) return facility def _setup_logging_from_conf(): log_root = getLogger(None).logger for handler in log_root.handlers: log_root.removeHandler(handler) if CONF.use_syslog: facility = _find_facility_from_conf() syslog = logging.handlers.SysLogHandler(address='/dev/log', facility=facility) log_root.addHandler(syslog) logpath = _get_log_file_path() if logpath: filelog = logging.handlers.WatchedFileHandler(logpath) log_root.addHandler(filelog) if CONF.use_stderr: streamlog = ColorHandler() log_root.addHandler(streamlog) elif not logpath: # pass sys.stdout as a positional argument # python2.6 calls the argument strm, in 2.7 it's stream streamlog = logging.StreamHandler(sys.stdout) log_root.addHandler(streamlog) if CONF.publish_errors: handler = importutils.import_object( "tempest.openstack.common.log_handler.PublishErrorsHandler", logging.ERROR) log_root.addHandler(handler) datefmt = CONF.log_date_format for handler in log_root.handlers: # NOTE(alaski): CONF.log_format overrides everything currently. This # should be deprecated in favor of context aware formatting. if CONF.log_format: handler.setFormatter(logging.Formatter(fmt=CONF.log_format, datefmt=datefmt)) log_root.info('Deprecated: log_format is now deprecated and will ' 'be removed in the next release') else: handler.setFormatter(ContextFormatter(datefmt=datefmt)) if CONF.debug: log_root.setLevel(logging.DEBUG) elif CONF.verbose: log_root.setLevel(logging.INFO) else: log_root.setLevel(logging.WARNING) for pair in CONF.default_log_levels: mod, _sep, level_name = pair.partition('=') level = logging.getLevelName(level_name) logger = logging.getLogger(mod) logger.setLevel(level) _loggers = {} def getLogger(name='unknown', version='unknown'): if name not in _loggers: _loggers[name] = ContextAdapter(logging.getLogger(name), name, version) return _loggers[name] def getLazyLogger(name='unknown', version='unknown'): """Returns lazy logger. Creates a pass-through logger that does not create the real logger until it is really needed and delegates all calls to the real logger once it is created. """ return LazyAdapter(name, version) class WritableLogger(object): """A thin wrapper that responds to `write` and logs.""" def __init__(self, logger, level=logging.INFO): self.logger = logger self.level = level def write(self, msg): self.logger.log(self.level, msg) class ContextFormatter(logging.Formatter): """A context.RequestContext aware formatter configured through flags. The flags used to set format strings are: logging_context_format_string and logging_default_format_string. You can also specify logging_debug_format_suffix to append extra formatting if the log level is debug. For information about what variables are available for the formatter see: http://docs.python.org/library/logging.html#formatter """ def format(self, record): """Uses contextstring if request_id is set, otherwise default.""" # NOTE(sdague): default the fancier formating params # to an empty string so we don't throw an exception if # they get used for key in ('instance', 'color'): if key not in record.__dict__: record.__dict__[key] = '' if record.__dict__.get('request_id', None): self._fmt = CONF.logging_context_format_string else: self._fmt = CONF.logging_default_format_string if (record.levelno == logging.DEBUG and CONF.logging_debug_format_suffix): self._fmt += " " + CONF.logging_debug_format_suffix # Cache this on the record, Logger will respect our formated copy if record.exc_info: record.exc_text = self.formatException(record.exc_info, record) return logging.Formatter.format(self, record) def formatException(self, exc_info, record=None): """Format exception output with CONF.logging_exception_prefix.""" if not record: return logging.Formatter.formatException(self, exc_info) stringbuffer = moves.StringIO() traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], None, stringbuffer) lines = stringbuffer.getvalue().split('\n') stringbuffer.close() if CONF.logging_exception_prefix.find('%(asctime)') != -1: record.asctime = self.formatTime(record, self.datefmt) formatted_lines = [] for line in lines: pl = CONF.logging_exception_prefix % record.__dict__ fl = '%s%s' % (pl, line) formatted_lines.append(fl) return '\n'.join(formatted_lines) class ColorHandler(logging.StreamHandler): LEVEL_COLORS = { logging.DEBUG: '\033[00;32m', # GREEN logging.INFO: '\033[00;36m', # CYAN logging.AUDIT: '\033[01;36m', # BOLD CYAN logging.WARN: '\033[01;33m', # BOLD YELLOW logging.ERROR: '\033[01;31m', # BOLD RED logging.CRITICAL: '\033[01;31m', # BOLD RED } def format(self, record): record.color = self.LEVEL_COLORS[record.levelno] return logging.StreamHandler.format(self, record) class DeprecatedConfig(Exception): message = _("Fatal call to deprecated config: %(msg)s") def __init__(self, msg): super(Exception, self).__init__(self.message % dict(msg=msg)) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/__init__.py0000664000175000017500000000120412332757070025143 0ustar chuckchuck00000000000000# # 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 six six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/local.py0000664000175000017500000000321512332757070024502 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Local storage of variables using weak references""" import threading import weakref class WeakLocal(threading.local): def __getattribute__(self, attr): rval = super(WeakLocal, self).__getattribute__(attr) if rval: # NOTE(mikal): this bit is confusing. What is stored is a weak # reference, not the value itself. We therefore need to lookup # the weak reference and return the inner value here. rval = rval() return rval def __setattr__(self, attr, value): value = weakref.ref(value) return super(WeakLocal, self).__setattr__(attr, value) # NOTE(mikal): the name "store" should be deprecated in the future store = WeakLocal() # A "weak" store uses weak references and allows an object to fall out of scope # when it falls out of scope in the code that uses the thread local storage. A # "strong" store will hold a reference to the object so that it never falls out # of scope. weak_store = WeakLocal() strong_store = threading.local() tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/gettextutils.py0000664000175000017500000004224312332757070026161 0ustar chuckchuck00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ gettext for openstack-common modules. Usual usage in an openstack.common module: from tempest.openstack.common.gettextutils import _ """ import copy import functools import gettext import locale from logging import handlers import os from babel import localedata import six _localedir = os.environ.get('tempest'.upper() + '_LOCALEDIR') _t = gettext.translation('tempest', localedir=_localedir, fallback=True) # We use separate translation catalogs for each log level, so set up a # mapping between the log level name and the translator. The domain # for the log level is project_name + "-log-" + log_level so messages # for each level end up in their own catalog. _t_log_levels = dict( (level, gettext.translation('tempest' + '-log-' + level, localedir=_localedir, fallback=True)) for level in ['info', 'warning', 'error', 'critical'] ) _AVAILABLE_LANGUAGES = {} USE_LAZY = False def enable_lazy(): """Convenience function for configuring _() to use lazy gettext Call this at the start of execution to enable the gettextutils._ function to use lazy gettext functionality. This is useful if your project is importing _ directly instead of using the gettextutils.install() way of importing the _ function. """ global USE_LAZY USE_LAZY = True def _(msg): if USE_LAZY: return Message(msg, domain='tempest') else: if six.PY3: return _t.gettext(msg) return _t.ugettext(msg) def _log_translation(msg, level): """Build a single translation of a log message """ if USE_LAZY: return Message(msg, domain='tempest' + '-log-' + level) else: translator = _t_log_levels[level] if six.PY3: return translator.gettext(msg) return translator.ugettext(msg) # Translators for log levels. # # The abbreviated names are meant to reflect the usual use of a short # name like '_'. The "L" is for "log" and the other letter comes from # the level. _LI = functools.partial(_log_translation, level='info') _LW = functools.partial(_log_translation, level='warning') _LE = functools.partial(_log_translation, level='error') _LC = functools.partial(_log_translation, level='critical') def install(domain, lazy=False): """Install a _() function using the given translation domain. Given a translation domain, install a _() function using gettext's install() function. The main difference from gettext.install() is that we allow overriding the default localedir (e.g. /usr/share/locale) using a translation-domain-specific environment variable (e.g. NOVA_LOCALEDIR). :param domain: the translation domain :param lazy: indicates whether or not to install the lazy _() function. The lazy _() introduces a way to do deferred translation of messages by installing a _ that builds Message objects, instead of strings, which can then be lazily translated into any available locale. """ if lazy: # NOTE(mrodden): Lazy gettext functionality. # # The following introduces a deferred way to do translations on # messages in OpenStack. We override the standard _() function # and % (format string) operation to build Message objects that can # later be translated when we have more information. def _lazy_gettext(msg): """Create and return a Message object. Lazy gettext function for a given domain, it is a factory method for a project/module to get a lazy gettext function for its own translation domain (i.e. nova, glance, cinder, etc.) Message encapsulates a string so that we can translate it later when needed. """ return Message(msg, domain=domain) from six import moves moves.builtins.__dict__['_'] = _lazy_gettext else: localedir = '%s_LOCALEDIR' % domain.upper() if six.PY3: gettext.install(domain, localedir=os.environ.get(localedir)) else: gettext.install(domain, localedir=os.environ.get(localedir), unicode=True) class Message(six.text_type): """A Message object is a unicode object that can be translated. Translation of Message is done explicitly using the translate() method. For all non-translation intents and purposes, a Message is simply unicode, and can be treated as such. """ def __new__(cls, msgid, msgtext=None, params=None, domain='tempest', *args): """Create a new Message object. In order for translation to work gettext requires a message ID, this msgid will be used as the base unicode text. It is also possible for the msgid and the base unicode text to be different by passing the msgtext parameter. """ # If the base msgtext is not given, we use the default translation # of the msgid (which is in English) just in case the system locale is # not English, so that the base text will be in that locale by default. if not msgtext: msgtext = Message._translate_msgid(msgid, domain) # We want to initialize the parent unicode with the actual object that # would have been plain unicode if 'Message' was not enabled. msg = super(Message, cls).__new__(cls, msgtext) msg.msgid = msgid msg.domain = domain msg.params = params return msg def translate(self, desired_locale=None): """Translate this message to the desired locale. :param desired_locale: The desired locale to translate the message to, if no locale is provided the message will be translated to the system's default locale. :returns: the translated message in unicode """ translated_message = Message._translate_msgid(self.msgid, self.domain, desired_locale) if self.params is None: # No need for more translation return translated_message # This Message object may have been formatted with one or more # Message objects as substitution arguments, given either as a single # argument, part of a tuple, or as one or more values in a dictionary. # When translating this Message we need to translate those Messages too translated_params = _translate_args(self.params, desired_locale) translated_message = translated_message % translated_params return translated_message @staticmethod def _translate_msgid(msgid, domain, desired_locale=None): if not desired_locale: system_locale = locale.getdefaultlocale() # If the system locale is not available to the runtime use English if not system_locale[0]: desired_locale = 'en_US' else: desired_locale = system_locale[0] locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') lang = gettext.translation(domain, localedir=locale_dir, languages=[desired_locale], fallback=True) if six.PY3: translator = lang.gettext else: translator = lang.ugettext translated_message = translator(msgid) return translated_message def __mod__(self, other): # When we mod a Message we want the actual operation to be performed # by the parent class (i.e. unicode()), the only thing we do here is # save the original msgid and the parameters in case of a translation params = self._sanitize_mod_params(other) unicode_mod = super(Message, self).__mod__(params) modded = Message(self.msgid, msgtext=unicode_mod, params=params, domain=self.domain) return modded def _sanitize_mod_params(self, other): """Sanitize the object being modded with this Message. - Add support for modding 'None' so translation supports it - Trim the modded object, which can be a large dictionary, to only those keys that would actually be used in a translation - Snapshot the object being modded, in case the message is translated, it will be used as it was when the Message was created """ if other is None: params = (other,) elif isinstance(other, dict): # Merge the dictionaries # Copy each item in case one does not support deep copy. params = {} if isinstance(self.params, dict): for key, val in self.params.items(): params[key] = self._copy_param(val) for key, val in other.items(): params[key] = self._copy_param(val) else: params = self._copy_param(other) return params def _copy_param(self, param): try: return copy.deepcopy(param) except Exception: # Fallback to casting to unicode this will handle the # python code-like objects that can't be deep-copied return six.text_type(param) def __add__(self, other): msg = _('Message objects do not support addition.') raise TypeError(msg) def __radd__(self, other): return self.__add__(other) def __str__(self): # NOTE(luisg): Logging in python 2.6 tries to str() log records, # and it expects specifically a UnicodeError in order to proceed. msg = _('Message objects do not support str() because they may ' 'contain non-ascii characters. ' 'Please use unicode() or translate() instead.') raise UnicodeError(msg) def get_available_languages(domain): """Lists the available languages for the given translation domain. :param domain: the domain to get languages for """ if domain in _AVAILABLE_LANGUAGES: return copy.copy(_AVAILABLE_LANGUAGES[domain]) localedir = '%s_LOCALEDIR' % domain.upper() find = lambda x: gettext.find(domain, localedir=os.environ.get(localedir), languages=[x]) # NOTE(mrodden): en_US should always be available (and first in case # order matters) since our in-line message strings are en_US language_list = ['en_US'] # NOTE(luisg): Babel <1.0 used a function called list(), which was # renamed to locale_identifiers() in >=1.0, the requirements master list # requires >=0.9.6, uncapped, so defensively work with both. We can remove # this check when the master list updates to >=1.0, and update all projects list_identifiers = (getattr(localedata, 'list', None) or getattr(localedata, 'locale_identifiers')) locale_identifiers = list_identifiers() for i in locale_identifiers: if find(i) is not None: language_list.append(i) # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they # are perfectly legitimate locales: # https://github.com/mitsuhiko/babel/issues/37 # In Babel 1.3 they fixed the bug and they support these locales, but # they are still not explicitly "listed" by locale_identifiers(). # That is why we add the locales here explicitly if necessary so that # they are listed as supported. aliases = {'zh': 'zh_CN', 'zh_Hant_HK': 'zh_HK', 'zh_Hant': 'zh_TW', 'fil': 'tl_PH'} for (locale, alias) in six.iteritems(aliases): if locale in language_list and alias not in language_list: language_list.append(alias) _AVAILABLE_LANGUAGES[domain] = language_list return copy.copy(language_list) def translate(obj, desired_locale=None): """Gets the translated unicode representation of the given object. If the object is not translatable it is returned as-is. If the locale is None the object is translated to the system locale. :param obj: the object to translate :param desired_locale: the locale to translate the message to, if None the default system locale will be used :returns: the translated object in unicode, or the original object if it could not be translated """ message = obj if not isinstance(message, Message): # If the object to translate is not already translatable, # let's first get its unicode representation message = six.text_type(obj) if isinstance(message, Message): # Even after unicoding() we still need to check if we are # running with translatable unicode before translating return message.translate(desired_locale) return obj def _translate_args(args, desired_locale=None): """Translates all the translatable elements of the given arguments object. This method is used for translating the translatable values in method arguments which include values of tuples or dictionaries. If the object is not a tuple or a dictionary the object itself is translated if it is translatable. If the locale is None the object is translated to the system locale. :param args: the args to translate :param desired_locale: the locale to translate the args to, if None the default system locale will be used :returns: a new args object with the translated contents of the original """ if isinstance(args, tuple): return tuple(translate(v, desired_locale) for v in args) if isinstance(args, dict): translated_dict = {} for (k, v) in six.iteritems(args): translated_v = translate(v, desired_locale) translated_dict[k] = translated_v return translated_dict return translate(args, desired_locale) class TranslationHandler(handlers.MemoryHandler): """Handler that translates records before logging them. The TranslationHandler takes a locale and a target logging.Handler object to forward LogRecord objects to after translating them. This handler depends on Message objects being logged, instead of regular strings. The handler can be configured declaratively in the logging.conf as follows: [handlers] keys = translatedlog, translator [handler_translatedlog] class = handlers.WatchedFileHandler args = ('/var/log/api-localized.log',) formatter = context [handler_translator] class = openstack.common.log.TranslationHandler target = translatedlog args = ('zh_CN',) If the specified locale is not available in the system, the handler will log in the default locale. """ def __init__(self, locale=None, target=None): """Initialize a TranslationHandler :param locale: locale to use for translating messages :param target: logging.Handler object to forward LogRecord objects to after translation """ # NOTE(luisg): In order to allow this handler to be a wrapper for # other handlers, such as a FileHandler, and still be able to # configure it using logging.conf, this handler has to extend # MemoryHandler because only the MemoryHandlers' logging.conf # parsing is implemented such that it accepts a target handler. handlers.MemoryHandler.__init__(self, capacity=0, target=target) self.locale = locale def setFormatter(self, fmt): self.target.setFormatter(fmt) def emit(self, record): # We save the message from the original record to restore it # after translation, so other handlers are not affected by this original_msg = record.msg original_args = record.args try: self._translate_and_log_record(record) finally: record.msg = original_msg record.args = original_args def _translate_and_log_record(self, record): record.msg = translate(record.msg, self.locale) # In addition to translating the message, we also need to translate # arguments that were passed to the log method that were not part # of the main message e.g., log.info(_('Some message %s'), this_one)) record.args = _translate_args(record.args, self.locale) self.target.emit(record) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/fileutils.py0000664000175000017500000000750712332757070025420 0ustar chuckchuck00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import errno import os import tempfile from tempest.openstack.common import excutils from tempest.openstack.common.gettextutils import _ from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) _FILE_CACHE = {} def ensure_tree(path): """Create a directory (and any ancestor directories required) :param path: Directory to create """ try: os.makedirs(path) except OSError as exc: if exc.errno == errno.EEXIST: if not os.path.isdir(path): raise else: raise def read_cached_file(filename, force_reload=False): """Read from a file if it has been modified. :param force_reload: Whether to reload the file. :returns: A tuple with a boolean specifying if the data is fresh or not. """ global _FILE_CACHE if force_reload and filename in _FILE_CACHE: del _FILE_CACHE[filename] reloaded = False mtime = os.path.getmtime(filename) cache_info = _FILE_CACHE.setdefault(filename, {}) if not cache_info or mtime > cache_info.get('mtime', 0): LOG.debug(_("Reloading cached file %s") % filename) with open(filename) as fap: cache_info['data'] = fap.read() cache_info['mtime'] = mtime reloaded = True return (reloaded, cache_info['data']) def delete_if_exists(path, remove=os.unlink): """Delete a file, but ignore file not found error. :param path: File to delete :param remove: Optional function to remove passed path """ try: remove(path) except OSError as e: if e.errno != errno.ENOENT: raise @contextlib.contextmanager def remove_path_on_error(path, remove=delete_if_exists): """Protect code that wants to operate on PATH atomically. Any exception will cause PATH to be removed. :param path: File to work with :param remove: Optional function to remove passed path """ try: yield except Exception: with excutils.save_and_reraise_exception(): remove(path) def file_open(*args, **kwargs): """Open file see built-in file() documentation for more details Note: The reason this is kept in a separate module is to easily be able to provide a stub module that doesn't alter system state at all (for unit tests) """ return file(*args, **kwargs) def write_to_tempfile(content, path=None, suffix='', prefix='tmp'): """Create temporary file or use existing file. This util is needed for creating temporary file with specified content, suffix and prefix. If path is not None, it will be used for writing content. If the path doesn't exist it'll be created. :param content: content for temporary file. :param path: same as parameter 'dir' for mkstemp :param suffix: same as parameter 'suffix' for mkstemp :param prefix: same as parameter 'prefix' for mkstemp For example: it can be used in database tests for creating configuration files. """ if path: ensure_tree(path) (fd, path) = tempfile.mkstemp(suffix=suffix, dir=path, prefix=prefix) try: os.write(fd, content) finally: os.close(fd) return path tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/common/jsonutils.py0000664000175000017500000001507212332757070025446 0ustar chuckchuck00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011 Justin Santa Barbara # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. ''' JSON related utilities. This module provides a few things: 1) A handy function for getting an object down to something that can be JSON serialized. See to_primitive(). 2) Wrappers around loads() and dumps(). The dumps() wrapper will automatically use to_primitive() for you if needed. 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson is available. ''' import datetime import functools import inspect import itertools import json try: import xmlrpclib except ImportError: # NOTE(jaypipes): xmlrpclib was renamed to xmlrpc.client in Python3 # however the function and object call signatures # remained the same. This whole try/except block should # be removed and replaced with a call to six.moves once # six 1.4.2 is released. See http://bit.ly/1bqrVzu import xmlrpc.client as xmlrpclib import six from tempest.openstack.common import gettextutils from tempest.openstack.common import importutils from tempest.openstack.common import timeutils netaddr = importutils.try_import("netaddr") _nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod, inspect.isfunction, inspect.isgeneratorfunction, inspect.isgenerator, inspect.istraceback, inspect.isframe, inspect.iscode, inspect.isbuiltin, inspect.isroutine, inspect.isabstract] _simple_types = (six.string_types + six.integer_types + (type(None), bool, float)) def to_primitive(value, convert_instances=False, convert_datetime=True, level=0, max_depth=3): """Convert a complex object into primitives. Handy for JSON serialization. We can optionally handle instances, but since this is a recursive function, we could have cyclical data structures. To handle cyclical data structures we could track the actual objects visited in a set, but not all objects are hashable. Instead we just track the depth of the object inspections and don't go too deep. Therefore, convert_instances=True is lossy ... be aware. """ # handle obvious types first - order of basic types determined by running # full tests on nova project, resulting in the following counts: # 572754 # 460353 # 379632 # 274610 # 199918 # 114200 # 51817 # 26164 # 6491 # 283 # 19 if isinstance(value, _simple_types): return value if isinstance(value, datetime.datetime): if convert_datetime: return timeutils.strtime(value) else: return value # value of itertools.count doesn't get caught by nasty_type_tests # and results in infinite loop when list(value) is called. if type(value) == itertools.count: return six.text_type(value) # FIXME(vish): Workaround for LP bug 852095. Without this workaround, # tests that raise an exception in a mocked method that # has a @wrap_exception with a notifier will fail. If # we up the dependency to 0.5.4 (when it is released) we # can remove this workaround. if getattr(value, '__module__', None) == 'mox': return 'mock' if level > max_depth: return '?' # The try block may not be necessary after the class check above, # but just in case ... try: recursive = functools.partial(to_primitive, convert_instances=convert_instances, convert_datetime=convert_datetime, level=level, max_depth=max_depth) if isinstance(value, dict): return dict((k, recursive(v)) for k, v in six.iteritems(value)) elif isinstance(value, (list, tuple)): return [recursive(lv) for lv in value] # It's not clear why xmlrpclib created their own DateTime type, but # for our purposes, make it a datetime type which is explicitly # handled if isinstance(value, xmlrpclib.DateTime): value = datetime.datetime(*tuple(value.timetuple())[:6]) if convert_datetime and isinstance(value, datetime.datetime): return timeutils.strtime(value) elif isinstance(value, gettextutils.Message): return value.data elif hasattr(value, 'iteritems'): return recursive(dict(value.iteritems()), level=level + 1) elif hasattr(value, '__iter__'): return recursive(list(value)) elif convert_instances and hasattr(value, '__dict__'): # Likely an instance of something. Watch for cycles. # Ignore class member vars. return recursive(value.__dict__, level=level + 1) elif netaddr and isinstance(value, netaddr.IPAddress): return six.text_type(value) else: if any(test(value) for test in _nasty_type_tests): return six.text_type(value) return value except TypeError: # Class objects are tricky since they may define something like # __iter__ defined but it isn't callable as list(). return six.text_type(value) def dumps(value, default=to_primitive, **kwargs): return json.dumps(value, default=default, **kwargs) def loads(s): return json.loads(s) def load(s): return json.load(s) try: import anyjson except ImportError: pass else: anyjson._modules.append((__name__, 'dumps', TypeError, 'loads', ValueError, 'load')) anyjson.force_implementation(__name__) tempest-2014.1.dev4108.gf22b6cc/tempest/openstack/__init__.py0000664000175000017500000000000012332757070023644 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/0000775000175000017500000000000012332757136021652 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/__init__.py0000664000175000017500000000000012332757070023746 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/0000775000175000017500000000000012332757136023326 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/agents.py0000664000175000017500000000303612332757070025160 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. list_agents = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'agents': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'agent_id': {'type': ['integer', 'string']}, 'hypervisor': {'type': 'string'}, 'os': {'type': 'string'}, 'architecture': {'type': 'string'}, 'version': {'type': 'string'}, 'url': {'type': 'string', 'format': 'uri'}, 'md5hash': {'type': 'string'} }, 'required': ['agent_id', 'hypervisor', 'os', 'architecture', 'version', 'url', 'md5hash'] } } }, 'required': ['agents'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/quotas.py0000664000175000017500000000321612332757070025213 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. common_quota_set = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'quota_set': { 'type': 'object', 'properties': { 'instances': {'type': 'integer'}, 'cores': {'type': 'integer'}, 'ram': {'type': 'integer'}, 'floating_ips': {'type': 'integer'}, 'fixed_ips': {'type': 'integer'}, 'metadata_items': {'type': 'integer'}, 'key_pairs': {'type': 'integer'}, 'security_groups': {'type': 'integer'}, 'security_group_rules': {'type': 'integer'} }, 'required': ['instances', 'cores', 'ram', 'floating_ips', 'fixed_ips', 'metadata_items', 'key_pairs', 'security_groups', 'security_group_rules'] } }, 'required': ['quota_set'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/flavors.py0000664000175000017500000000425112332757070025353 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api_schema.compute import parameter_types list_flavors = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'flavors': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'name': {'type': 'string'}, 'links': parameter_types.links, 'id': {'type': 'string'} }, 'required': ['name', 'links', 'id'] } } }, 'required': ['flavors'] } } common_flavor_info = { 'type': 'object', 'properties': { 'name': {'type': 'string'}, 'links': parameter_types.links, 'ram': {'type': 'integer'}, 'vcpus': {'type': 'integer'}, 'swap': {'type': 'integer'}, 'disk': {'type': 'integer'}, 'id': {'type': 'string'} }, 'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id'] } common_flavor_list_details = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'flavors': { 'type': 'array', 'items': common_flavor_info } }, 'required': ['flavors'] } } common_flavor_details = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'flavor': common_flavor_info }, 'required': ['flavor'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/parameter_types.py0000664000175000017500000000350612332757070027105 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. links = { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'href': { 'type': 'string', 'format': 'uri' }, 'rel': {'type': 'string'} }, 'required': ['href', 'rel'] } } mac_address = { 'type': 'string', 'pattern': '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}' } access_ip_v4 = { 'type': 'string', 'anyOf': [{'format': 'ipv4'}, {'enum': ['']}] } access_ip_v6 = { 'type': 'string', 'anyOf': [{'format': 'ipv6'}, {'enum': ['']}] } addresses = { 'type': 'object', 'patternProperties': { # NOTE: Here is for 'private' or something. '^[a-zA-Z0-9-_.]+$': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'version': {'type': 'integer'}, 'addr': { 'type': 'string', 'anyOf': [ {'format': 'ipv4'}, {'format': 'ipv6'} ] } }, 'required': ['version', 'addr'] } } } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/0000775000175000017500000000000012332757136023656 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/agents.py0000664000175000017500000000124312332757070025506 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. delete_agent = { 'status_code': [204] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/quotas.py0000664000175000017500000000455212332757070025547 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import quotas quota_set = copy.deepcopy(quotas.common_quota_set) quota_set['response_body']['properties']['quota_set']['properties'][ 'id'] = {'type': 'string'} quota_set['response_body']['properties']['quota_set'][ 'required'].extend(['id']) quota_common_info = { 'type': 'object', 'properties': { 'reserved': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'in_use': {'type': 'integer'} }, 'required': ['reserved', 'limit', 'in_use'] } quota_set_detail = copy.deepcopy(quotas.common_quota_set) quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'id'] = {'type': 'string'} quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'instances'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'cores'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'ram'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'floating_ips'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'fixed_ips'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'metadata_items'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'key_pairs'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'security_groups'] = quota_common_info quota_set_detail['response_body']['properties']['quota_set']['properties'][ 'security_group_rules'] = quota_common_info delete_quota = { 'status_code': [204] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/flavors.py0000664000175000017500000000527612332757070025713 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import flavors from tempest.api_schema.compute import flavors_extra_specs list_flavors_details = copy.deepcopy(flavors.common_flavor_list_details) # NOTE- In v3 API, 'swap' comes as '0' not empty string '""' # (In V2 API, it comes as empty string) So leaving 'swap'as integer type only. # Defining extra attributes for V3 flavor schema list_flavors_details['response_body']['properties']['flavors']['items'][ 'properties'].update({'disabled': {'type': 'boolean'}, 'ephemeral': {'type': 'integer'}, 'flavor-access:is_public': {'type': 'boolean'}, 'os-flavor-rxtx:rxtx_factor': {'type': 'number'}}) # 'flavor-access' and 'os-flavor-rxtx' are API extensions. # So they are not 'required'. list_flavors_details['response_body']['properties']['flavors']['items'][ 'required'].extend(['disabled', 'ephemeral']) set_flavor_extra_specs = copy.deepcopy(flavors_extra_specs.flavor_extra_specs) set_flavor_extra_specs['status_code'] = [201] unset_flavor_extra_specs = { 'status_code': [204] } get_flavor_details = copy.deepcopy(flavors.common_flavor_details) # NOTE- In v3 API, 'swap' comes as '0' not empty string '""' # (In V2 API, it comes as empty string) So leaving 'swap'as integer type only. # Defining extra attributes for V3 flavor schema get_flavor_details['response_body']['properties']['flavor'][ 'properties'].update({'disabled': {'type': 'boolean'}, 'ephemeral': {'type': 'integer'}, 'flavor-access:is_public': {'type': 'boolean'}, 'os-flavor-rxtx:rxtx_factor': {'type': 'number'}}) # 'flavor-access' and 'os-flavor-rxtx' are API extensions. # So they are not 'required'. get_flavor_details['response_body']['properties']['flavor'][ 'required'].extend(['disabled', 'ephemeral']) create_flavor_details = copy.deepcopy(get_flavor_details) # Overriding the status code for create flavor V3 API. create_flavor_details['status_code'] = [201] delete_flavor = { 'status_code': [204] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/certificates.py0000664000175000017500000000144312332757070026674 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import certificates create_certificate = copy.deepcopy(certificates._common_schema) create_certificate['status_code'] = [201] tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/hypervisors.py0000664000175000017500000000413712332757070026627 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import hypervisors list_hypervisors_detail = copy.deepcopy( hypervisors.common_list_hypervisors_detail) # Defining extra attributes for V3 show hypervisor schema list_hypervisors_detail['response_body']['properties']['hypervisors'][ 'items']['properties']['os-pci:pci_stats'] = {'type': 'array'} show_hypervisor = copy.deepcopy(hypervisors.common_show_hypervisor) # Defining extra attributes for V3 show hypervisor schema show_hypervisor['response_body']['properties']['hypervisor']['properties'][ 'os-pci:pci_stats'] = {'type': 'array'} hypervisors_servers = copy.deepcopy(hypervisors.common_hypervisors_info) # Defining extra attributes for V3 show hypervisor schema hypervisors_servers['response_body']['properties']['hypervisor']['properties'][ 'servers'] = { 'type': 'array', 'items': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is integer, # but here allows 'string' also because we # will be able to change it to 'uuid' in # the future. 'id': {'type': ['integer', 'string']}, 'name': {'type': 'string'} } } } # V3 API response body always contains the 'servers' attribute even there # is no server (VM) are present on Hypervisor host. hypervisors_servers['response_body']['properties']['hypervisor'][ 'required'] = ['id', 'hypervisor_hostname', 'servers'] tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/keypairs.py0000664000175000017500000000246512332757070026063 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api_schema.compute import keypairs get_keypair = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'keypair': { 'type': 'object', 'properties': { 'public_key': {'type': 'string'}, 'name': {'type': 'string'}, 'fingerprint': {'type': 'string'} }, 'required': ['public_key', 'name', 'fingerprint'] } }, 'required': ['keypair'] } } create_keypair = { 'status_code': [201], 'response_body': keypairs.create_keypair } delete_keypair = { 'status_code': [204], } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/aggregates.py0000664000175000017500000000157412332757070026345 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import aggregates delete_aggregate = { 'status_code': [204] } create_aggregate = copy.deepcopy(aggregates.common_create_aggregate) # V3 API's response status_code is 201 create_aggregate['status_code'] = [201] tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/servers.py0000664000175000017500000000573212332757070025725 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import parameter_types from tempest.api_schema.compute import servers create_server = { 'status_code': [202], 'response_body': { 'type': 'object', 'properties': { 'server': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is uuid, but here allows # 'integer' also because old OpenStack uses 'integer' # as a server id. 'id': {'type': ['integer', 'string']}, 'os-security-groups:security_groups': {'type': 'array'}, 'links': parameter_types.links, 'admin_password': {'type': 'string'}, 'os-access-ips:access_ip_v4': parameter_types.access_ip_v4, 'os-access-ips:access_ip_v6': parameter_types.access_ip_v6 }, # NOTE: os-access-ips:access_ip_v4/v6 are API extension, # and some environments return a response without these # attributes. So they are not 'required'. 'required': ['id', 'os-security-groups:security_groups', 'links', 'admin_password'] } }, 'required': ['server'] } } addresses_v3 = copy.deepcopy(parameter_types.addresses) addresses_v3['patternProperties']['^[a-zA-Z0-9-_.]+$']['items'][ 'properties'].update({ 'type': {'type': 'string'}, 'mac_addr': {'type': 'string'} }) addresses_v3['patternProperties']['^[a-zA-Z0-9-_.]+$']['items'][ 'required'].extend( ['type', 'mac_addr'] ) update_server = copy.deepcopy(servers.base_update_server) update_server['response_body']['properties']['server']['properties'].update({ 'addresses': addresses_v3, 'host_id': {'type': 'string'}, 'os-access-ips:access_ip_v4': parameter_types.access_ip_v4, 'os-access-ips:access_ip_v6': parameter_types.access_ip_v6 }) update_server['response_body']['properties']['server']['required'].append( # NOTE: os-access-ips:access_ip_v4/v6 are API extension, # and some environments return a response without these # attributes. So they are not 'required'. 'host_id' ) attach_detach_volume = { 'status_code': [202] } set_get_server_metadata_item = copy.deepcopy(servers.set_server_metadata) tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/extensions.py0000664000175000017500000000244412332757070026430 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. list_extensions = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'extensions': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'name': {'type': 'string'}, 'alias': {'type': 'string'}, 'description': {'type': 'string'}, 'version': {'type': 'integer'} }, 'required': ['name', 'alias', 'description', 'version'] } } }, 'required': ['extensions'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/__init__.py0000664000175000017500000000000012332757070025752 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/availability_zone.py0000664000175000017500000000365012332757070027736 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import availability_zone as common base = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'availability_zone_info': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'zone_name': {'type': 'string'}, 'zone_state': { 'type': 'object', 'properties': { 'available': {'type': 'boolean'} }, 'required': ['available'] }, # NOTE: Here is the difference between detail and # non-detail 'hosts': {'type': 'null'} }, 'required': ['zone_name', 'zone_state', 'hosts'] } } }, 'required': ['availability_zone_info'] } } get_availability_zone_list = copy.deepcopy(base) get_availability_zone_list_detail = copy.deepcopy(base) get_availability_zone_list_detail['response_body']['properties'][ 'availability_zone_info']['items']['properties']['hosts'] = common.detail tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v3/hosts.py0000664000175000017500000000275012332757070025371 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import hosts startup_host = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'host': hosts.common_start_up_body }, 'required': ['host'] } } # The 'power_action' attribute of 'shutdown_host' API is 'shutdown' shutdown_host = copy.deepcopy(startup_host) shutdown_host['response_body']['properties']['power_action'] = { 'enum': ['shutdown'] } # The 'power_action' attribute of 'reboot_host' API is 'reboot' reboot_host = copy.deepcopy(startup_host) reboot_host['response_body']['properties']['power_action'] = { 'enum': ['reboot'] } update_host = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'host': hosts.update_host_common }, 'required': ['host'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/flavors_extra_specs.py0000664000175000017500000000227612332757070027760 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. flavor_extra_specs = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'extra_specs': { 'type': 'object', 'patternProperties': { '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'} } } }, 'required': ['extra_specs'] } } flavor_extra_specs_key = { 'status_code': [200], 'response_body': { 'type': 'object', 'patternProperties': { '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'} } } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/certificates.py0000664000175000017500000000237012332757070026344 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy _common_schema = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'certificate': { 'type': 'object', 'properties': { 'data': {'type': 'string'}, 'private_key': {'type': 'string'}, }, 'required': ['data', 'private_key'], } }, 'required': ['certificate'], } } get_certificate = copy.deepcopy(_common_schema) get_certificate['response_body']['properties']['certificate'][ 'properties']['private_key'].update({'type': 'null'}) tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/hypervisors.py0000664000175000017500000001751612332757070026304 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy hypervisor_statistics = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'hypervisor_statistics': { 'type': 'object', 'properties': { 'count': {'type': 'integer'}, 'current_workload': {'type': 'integer'}, 'disk_available_least': {'type': 'integer'}, 'free_disk_gb': {'type': 'integer'}, 'free_ram_mb': {'type': 'integer'}, 'local_gb': {'type': 'integer'}, 'local_gb_used': {'type': 'integer'}, 'memory_mb': {'type': 'integer'}, 'memory_mb_used': {'type': 'integer'}, 'running_vms': {'type': 'integer'}, 'vcpus': {'type': 'integer'}, 'vcpus_used': {'type': 'integer'} }, 'required': ['count', 'current_workload', 'disk_available_least', 'free_disk_gb', 'free_ram_mb', 'local_gb', 'local_gb_used', 'memory_mb', 'memory_mb_used', 'running_vms', 'vcpus', 'vcpus_used'] } }, 'required': ['hypervisor_statistics'] } } common_list_hypervisors_detail = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'hypervisors': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'cpu_info': {'type': 'string'}, 'current_workload': {'type': 'integer'}, 'disk_available_least': {'type': ['integer', 'null']}, 'host_ip': { 'type': 'string', 'format': 'ip-address' }, 'free_disk_gb': {'type': 'integer'}, 'free_ram_mb': {'type': 'integer'}, 'hypervisor_hostname': {'type': 'string'}, 'hypervisor_type': {'type': 'string'}, 'hypervisor_version': {'type': 'integer'}, 'id': {'type': ['integer', 'string']}, 'local_gb': {'type': 'integer'}, 'local_gb_used': {'type': 'integer'}, 'memory_mb': {'type': 'integer'}, 'memory_mb_used': {'type': 'integer'}, 'running_vms': {'type': 'integer'}, 'service': { 'type': 'object', 'properties': { 'host': {'type': 'string'}, 'id': {'type': ['integer', 'string']} }, 'required': ['host', 'id'] }, 'vcpus': {'type': 'integer'}, 'vcpus_used': {'type': 'integer'} }, 'required': ['cpu_info', 'current_workload', 'disk_available_least', 'host_ip', 'free_disk_gb', 'free_ram_mb', 'hypervisor_hostname', 'hypervisor_type', 'hypervisor_version', 'id', 'local_gb', 'local_gb_used', 'memory_mb', 'memory_mb_used', 'running_vms', 'service', 'vcpus', 'vcpus_used'] } } }, 'required': ['hypervisors'] } } common_show_hypervisor = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'hypervisor': { 'type': 'object', 'properties': { 'cpu_info': {'type': 'string'}, 'current_workload': {'type': 'integer'}, 'disk_available_least': {'type': 'integer'}, 'host_ip': { 'type': 'string', 'format': 'ip-address' }, 'free_disk_gb': {'type': 'integer'}, 'free_ram_mb': {'type': 'integer'}, 'hypervisor_hostname': {'type': 'string'}, 'hypervisor_type': {'type': 'string'}, 'hypervisor_version': {'type': 'integer'}, 'id': {'type': ['integer', 'string']}, 'local_gb': {'type': 'integer'}, 'local_gb_used': {'type': 'integer'}, 'memory_mb': {'type': 'integer'}, 'memory_mb_used': {'type': 'integer'}, 'running_vms': {'type': 'integer'}, 'service': { 'type': 'object', 'properties': { 'host': {'type': 'string'}, 'id': {'type': ['integer', 'string']} }, 'required': ['host', 'id'] }, 'vcpus': {'type': 'integer'}, 'vcpus_used': {'type': 'integer'} }, 'required': ['cpu_info', 'current_workload', 'disk_available_least', 'host_ip', 'free_disk_gb', 'free_ram_mb', 'hypervisor_hostname', 'hypervisor_type', 'hypervisor_version', 'id', 'local_gb', 'local_gb_used', 'memory_mb', 'memory_mb_used', 'running_vms', 'service', 'vcpus', 'vcpus_used'] } }, 'required': ['hypervisor'] } } common_hypervisors_detail = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'hypervisors': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': ['integer', 'string']}, 'hypervisor_hostname': {'type': 'string'} }, 'required': ['id', 'hypervisor_hostname'] } } }, 'required': ['hypervisors'] } } common_hypervisors_info = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'hypervisor': { 'type': 'object', 'properties': { 'id': {'type': ['integer', 'string']}, 'hypervisor_hostname': {'type': 'string'}, }, 'required': ['id', 'hypervisor_hostname'] } }, 'required': ['hypervisor'] } } hypervisor_uptime = copy.deepcopy(common_hypervisors_info) hypervisor_uptime['response_body']['properties']['hypervisor'][ 'properties']['uptime'] = {'type': 'string'} hypervisor_uptime['response_body']['properties']['hypervisor'][ 'required'] = ['id', 'hypervisor_hostname', 'uptime'] tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/keypairs.py0000664000175000017500000000461012332757070025525 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. list_keypairs = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'keypairs': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'keypair': { 'type': 'object', 'properties': { 'public_key': {'type': 'string'}, 'name': {'type': 'string'}, 'fingerprint': {'type': 'string'} }, 'required': ['public_key', 'name', 'fingerprint'] } }, 'required': ['keypair'] } } }, 'required': ['keypairs'] } } create_keypair = { 'type': 'object', 'properties': { 'keypair': { 'type': 'object', 'properties': { 'fingerprint': {'type': 'string'}, 'name': {'type': 'string'}, 'public_key': {'type': 'string'}, # NOTE: Now the type of 'user_id' is integer, but here # allows 'string' also because we will be able to change # it to 'uuid' in the future. 'user_id': {'type': ['integer', 'string']}, 'private_key': {'type': 'string'} }, # When create keypair API is being called with 'Public key' # (Importing keypair) then, response body does not contain # 'private_key' So it is not defined as 'required' 'required': ['fingerprint', 'name', 'public_key', 'user_id'] } }, 'required': ['keypair'] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/aggregates.py0000664000175000017500000000520412332757070026007 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy aggregate = { 'type': 'object', 'properties': { 'availability_zone': {'type': ['string', 'null']}, 'created_at': {'type': 'string'}, 'deleted': {'type': 'boolean'}, 'deleted_at': {'type': ['string', 'null']}, 'hosts': {'type': 'array'}, 'id': {'type': 'integer'}, 'metadata': {'type': 'object'}, 'name': {'type': 'string'}, 'updated_at': {'type': ['string', 'null']} }, 'required': ['availability_zone', 'created_at', 'deleted', 'deleted_at', 'hosts', 'id', 'metadata', 'name', 'updated_at'] } list_aggregates = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'aggregates': { 'type': 'array', 'items': aggregate } }, 'required': ['aggregates'] } } get_aggregate = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'aggregate': aggregate }, 'required': ['aggregate'] } } aggregate_set_metadata = get_aggregate # The 'updated_at' attribute of 'update_aggregate' can't be null. update_aggregate = copy.deepcopy(get_aggregate) update_aggregate['response_body']['properties']['aggregate']['properties'][ 'updated_at'] = { 'type': 'string' } common_create_aggregate = { 'response_body': { 'type': 'object', 'properties': { 'aggregate': aggregate }, 'required': ['aggregate'] } } # create-aggregate api doesn't have 'hosts' and 'metadata' attributes. del common_create_aggregate['response_body']['properties']['aggregate'][ 'properties']['hosts'] del common_create_aggregate['response_body']['properties']['aggregate'][ 'properties']['metadata'] common_create_aggregate['response_body']['properties']['aggregate'][ 'required'] = ['availability_zone', 'created_at', 'deleted', 'deleted_at', 'id', 'name', 'updated_at'] tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/servers.py0000664000175000017500000000716412332757070025376 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import parameter_types get_password = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'password': {'type': 'string'} }, 'required': ['password'] } } get_vnc_console = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'console': { 'type': 'object', 'properties': { 'type': {'type': 'string'}, 'url': { 'type': 'string', 'format': 'uri' } }, 'required': ['type', 'url'] } }, 'required': ['console'] } } base_update_server = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'server': { 'type': 'object', 'properties': { 'id': {'type': ['integer', 'string']}, 'name': {'type': 'string'}, 'status': {'type': 'string'}, 'image': { 'type': 'object', 'properties': { 'id': {'type': ['integer', 'string']}, 'links': parameter_types.links }, 'required': ['id', 'links'] }, 'flavor': { 'type': 'object', 'properties': { 'id': {'type': ['integer', 'string']}, 'links': parameter_types.links }, 'required': ['id', 'links'] }, 'user_id': {'type': 'string'}, 'tenant_id': {'type': 'string'}, 'created': {'type': 'string'}, 'updated': {'type': 'string'}, 'progress': {'type': 'integer'}, 'metadata': {'type': 'object'}, 'links': parameter_types.links, 'addresses': parameter_types.addresses, }, 'required': ['id', 'name', 'status', 'image', 'flavor', 'user_id', 'tenant_id', 'created', 'updated', 'progress', 'metadata', 'links', 'addresses'] } } } } delete_server = { 'status_code': [204], } set_server_metadata = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'metadata': { 'type': 'object', 'patternProperties': { '^.+$': {'type': 'string'} } } }, 'required': ['metadata'] } } list_server_metadata = copy.deepcopy(set_server_metadata) delete_server_metadata_item = { 'status_code': [204] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/interfaces.py0000664000175000017500000000124712332757070026024 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. delete_interface = { 'status_code': [202] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/__init__.py0000664000175000017500000000000012332757070025422 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/availability_zone.py0000664000175000017500000000260012332757070027400 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # NOTE: This is the detail information for "get az detail" API. # The information is the same between v2 and v3 APIs. detail = { 'type': 'object', 'patternProperties': { # NOTE: Here is for a hostname '^[a-zA-Z0-9-_.]+$': { 'type': 'object', 'patternProperties': { # NOTE: Here is for a service name '^.*$': { 'type': 'object', 'properties': { 'available': {'type': 'boolean'}, 'active': {'type': 'boolean'}, 'updated_at': {'type': 'string'} }, 'required': ['available', 'active', 'updated_at'] } } } } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/services.py0000664000175000017500000000443212332757070025523 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. list_services = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'services': { 'type': 'array', 'items': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is integer, but here # allows 'string' also because we will be able to # change it to 'uuid' in the future. 'id': {'type': ['integer', 'string']}, 'zone': {'type': 'string'}, 'host': {'type': 'string'}, 'state': {'type': 'string'}, 'binary': {'type': 'string'}, 'status': {'type': 'string'}, 'updated_at': {'type': 'string'}, 'disabled_reason': {'type': ['string', 'null']} }, 'required': ['id', 'zone', 'host', 'state', 'binary', 'status', 'updated_at', 'disabled_reason'] } } }, 'required': ['services'] } } enable_service = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'service': { 'type': 'object', 'properties': { 'status': {'type': 'string'}, 'binary': {'type': 'string'}, 'host': {'type': 'string'} }, 'required': ['status', 'binary', 'host'] } }, 'required': ['service'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/hosts.py0000664000175000017500000000533612332757070025044 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. common_start_up_body = { 'type': 'object', 'properties': { 'host': {'type': 'string'}, 'power_action': {'enum': ['startup']} }, 'required': ['host', 'power_action'] } list_hosts = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'hosts': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'host_name': {'type': 'string'}, 'service': {'type': 'string'}, 'zone': {'type': 'string'} }, 'required': ['host_name', 'service', 'zone'] } } }, 'required': ['hosts'] } } show_host_detail = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'host': { 'type': 'array', 'item': { 'type': 'object', 'properties': { 'resource': { 'type': 'object', 'properties': { 'cpu': {'type': 'integer'}, 'disk_gb': {'type': 'integer'}, 'host': {'type': 'string'}, 'memory_mb': {'type': 'integer'}, 'project': {'type': 'string'} }, 'required': ['cpu', 'disk_gb', 'host', 'memory_mb', 'project'] } }, 'required': ['resource'] } } }, 'required': ['host'] } } update_host_common = { 'type': 'object', 'properties': { 'host': {'type': 'string'}, 'maintenance_mode': {'enum': ['on_maintenance', 'off_maintenance']}, 'status': {'enum': ['enabled', 'disabled']} }, 'required': ['host', 'maintenance_mode', 'status'] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/flavors_access.py0000664000175000017500000000227712332757070026702 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. add_remove_list_flavor_access = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'flavor_access': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'flavor_id': {'type': 'string'}, 'tenant_id': {'type': 'string'}, }, 'required': ['flavor_id', 'tenant_id'], } } }, 'required': ['flavor_access'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/0000775000175000017500000000000012332757136023655 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/agents.py0000664000175000017500000000124312332757070025505 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. delete_agent = { 'status_code': [200] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/quotas.py0000664000175000017500000000403612332757070025543 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import quotas quota_set = copy.deepcopy(quotas.common_quota_set) quota_set['response_body']['properties']['quota_set']['properties'][ 'id'] = {'type': 'string'} quota_set['response_body']['properties']['quota_set']['properties'][ 'injected_files'] = {'type': 'integer'} quota_set['response_body']['properties']['quota_set']['properties'][ 'injected_file_content_bytes'] = {'type': 'integer'} quota_set['response_body']['properties']['quota_set']['properties'][ 'injected_file_path_bytes'] = {'type': 'integer'} quota_set['response_body']['properties']['quota_set']['required'].extend([ 'id', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes']) quota_set_update = copy.deepcopy(quotas.common_quota_set) quota_set_update['response_body']['properties']['quota_set']['properties'][ 'injected_files'] = {'type': 'integer'} quota_set_update['response_body']['properties']['quota_set']['properties'][ 'injected_file_content_bytes'] = {'type': 'integer'} quota_set_update['response_body']['properties']['quota_set']['properties'][ 'injected_file_path_bytes'] = {'type': 'integer'} quota_set_update['response_body']['properties']['quota_set'][ 'required'].extend(['injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes']) delete_quota = { 'status_code': [202] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/volumes.py0000664000175000017500000001201512332757070025715 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. create_get_volume = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'volume': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'status': {'type': 'string'}, 'displayName': {'type': ['string', 'null']}, 'availabilityZone': {'type': 'string'}, 'createdAt': {'type': 'string'}, 'displayDescription': {'type': ['string', 'null']}, 'volumeType': {'type': 'string'}, 'snapshotId': {'type': ['string', 'null']}, 'metadata': {'type': 'object'}, 'size': {'type': 'integer'}, 'attachments': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'device': {'type': 'string'}, 'volumeId': {'type': 'string'}, 'serverId': {'type': ['integer', 'string']} } # NOTE- If volume is not attached to any server # then, 'attachments' attributes comes as array # with empty objects "[{}]" due to that elements # of 'attachments' cannot defined as 'required'. # If it would come as empty array "[]" then, # those elements can be defined as 'required'. } } }, 'required': ['id', 'status', 'displayName', 'availabilityZone', 'createdAt', 'displayDescription', 'volumeType', 'snapshotId', 'metadata', 'size', 'attachments'] } }, 'required': ['volume'] } } list_volumes = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'volumes': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'status': {'type': 'string'}, 'displayName': {'type': ['string', 'null']}, 'availabilityZone': {'type': 'string'}, 'createdAt': {'type': 'string'}, 'displayDescription': {'type': ['string', 'null']}, 'volumeType': {'type': 'string'}, 'snapshotId': {'type': ['string', 'null']}, 'metadata': {'type': 'object'}, 'size': {'type': 'integer'}, 'attachments': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'device': {'type': 'string'}, 'volumeId': {'type': 'string'}, 'serverId': {'type': ['integer', 'string']} } # NOTE- If volume is not attached to any server # then, 'attachments' attributes comes as array # with empty object "[{}]" due to that elements # of 'attachments' cannot defined as 'required' # If it would come as empty array "[]" then, # those elements can be defined as 'required'. } } }, 'required': ['id', 'status', 'displayName', 'availabilityZone', 'createdAt', 'displayDescription', 'volumeType', 'snapshotId', 'metadata', 'size', 'attachments'] } } }, 'required': ['volumes'] } } delete_volume = { 'status_code': [202] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/flavors.py0000664000175000017500000000473612332757070025712 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import flavors list_flavors_details = copy.deepcopy(flavors.common_flavor_list_details) # 'swap' attributes comes as integre value but if it is empty it comes as "". # So defining type of as string and integer. list_flavors_details['response_body']['properties']['flavors']['items'][ 'properties']['swap'] = {'type': ['string', 'integer']} # Defining extra attributes for V2 flavor schema list_flavors_details['response_body']['properties']['flavors']['items'][ 'properties'].update({'OS-FLV-DISABLED:disabled': {'type': 'boolean'}, 'os-flavor-access:is_public': {'type': 'boolean'}, 'rxtx_factor': {'type': 'number'}, 'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'}}) # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and 'OS-FLV-EXT-DATA' # are API extensions. So they are not 'required'. unset_flavor_extra_specs = { 'status_code': [200] } create_get_flavor_details = copy.deepcopy(flavors.common_flavor_details) # 'swap' attributes comes as integre value but if it is empty it comes as "". # So defining type of as string and integer. create_get_flavor_details['response_body']['properties']['flavor'][ 'properties']['swap'] = {'type': ['string', 'integer']} # Defining extra attributes for V2 flavor schema create_get_flavor_details['response_body']['properties']['flavor'][ 'properties'].update({'OS-FLV-DISABLED:disabled': {'type': 'boolean'}, 'os-flavor-access:is_public': {'type': 'boolean'}, 'rxtx_factor': {'type': 'number'}, 'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'}}) # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and 'OS-FLV-EXT-DATA' # are API extensions. So they are not 'required'. delete_flavor = { 'status_code': [202] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/limits.py0000664000175000017500000001067512332757070025536 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. get_limit = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'limits': { 'type': 'object', 'properties': { 'absolute': { 'type': 'object', 'properties': { 'maxTotalRAMSize': {'type': 'integer'}, 'totalCoresUsed': {'type': 'integer'}, 'maxTotalInstances': {'type': 'integer'}, 'maxTotalFloatingIps': {'type': 'integer'}, 'totalSecurityGroupsUsed': {'type': 'integer'}, 'maxTotalCores': {'type': 'integer'}, 'totalFloatingIpsUsed': {'type': 'integer'}, 'maxSecurityGroups': {'type': 'integer'}, 'maxServerMeta': {'type': 'integer'}, 'maxPersonality': {'type': 'integer'}, 'maxImageMeta': {'type': 'integer'}, 'maxPersonalitySize': {'type': 'integer'}, 'maxSecurityGroupRules': {'type': 'integer'}, 'maxTotalKeypairs': {'type': 'integer'}, 'totalRAMUsed': {'type': 'integer'}, 'totalInstancesUsed': {'type': 'integer'} }, 'required': ['maxImageMeta', 'maxPersonality', 'maxPersonalitySize', 'maxSecurityGroupRules', 'maxSecurityGroups', 'maxServerMeta', 'maxTotalCores', 'maxTotalFloatingIps', 'maxTotalInstances', 'maxTotalKeypairs', 'maxTotalRAMSize', 'totalCoresUsed', 'totalFloatingIpsUsed', 'totalInstancesUsed', 'totalRAMUsed', 'totalSecurityGroupsUsed'] }, 'rate': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'limit': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'next-available': {'type': 'string'}, 'remaining': {'type': 'integer'}, 'unit': {'type': 'string'}, 'value': {'type': 'integer'}, 'verb': {'type': 'string'} } } }, 'regex': {'type': 'string'}, 'uri': {'type': 'string'} } } } }, 'required': ['absolute', 'rate'] } }, 'required': ['limits'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/certificates.py0000664000175000017500000000137112332757070026673 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import certificates create_certificate = copy.deepcopy(certificates._common_schema) tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/hypervisors.py0000664000175000017500000000276412332757070026632 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import hypervisors hypervisors_servers = copy.deepcopy(hypervisors.common_hypervisors_detail) # Defining extra attributes for V3 show hypervisor schema hypervisors_servers['response_body']['properties']['hypervisors']['items'][ 'properties']['servers'] = { 'type': 'array', 'items': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is integer, # but here allows 'string' also because we # will be able to change it to 'uuid' in # the future. 'id': {'type': ['integer', 'string']}, 'name': {'type': 'string'} } } } # In V2 API, if there is no servers (VM) on the Hypervisor host then 'servers' # attribute will not be present in response body So it is not 'required'. tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/keypairs.py0000664000175000017500000000426612332757070026063 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api_schema.compute import keypairs get_keypair = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'keypair': { 'type': 'object', 'properties': { 'public_key': {'type': 'string'}, 'name': {'type': 'string'}, 'fingerprint': {'type': 'string'}, # NOTE: Now the type of 'user_id' is integer, but here # allows 'string' also because we will be able to change # it to 'uuid' in the future. 'user_id': {'type': ['integer', 'string']}, 'deleted': {'type': 'boolean'}, 'created_at': {'type': 'string'}, 'updated_at': {'type': ['string', 'null']}, 'deleted_at': {'type': ['string', 'null']}, 'id': {'type': 'integer'} }, # When we run the get keypair API, response body includes # all the above mentioned attributes. # But in Nova API sample file, response body includes only # 'public_key', 'name' & 'fingerprint'. So only 'public_key', # 'name' & 'fingerprint' are defined as 'required'. 'required': ['public_key', 'name', 'fingerprint'] } }, 'required': ['keypair'] } } create_keypair = { 'status_code': [200], 'response_body': keypairs.create_keypair } delete_keypair = { 'status_code': [202], } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/tenant_usages.py0000664000175000017500000000576112332757070027075 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy _server_usages = { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'ended_at': { 'oneOf': [ {'type': 'string'}, {'type': 'null'} ] }, 'flavor': {'type': 'string'}, 'hours': {'type': 'number'}, 'instance_id': {'type': 'string'}, 'local_gb': {'type': 'integer'}, 'memory_mb': {'type': 'integer'}, 'name': {'type': 'string'}, 'started_at': {'type': 'string'}, 'state': {'type': 'string'}, 'tenant_id': {'type': 'string'}, 'uptime': {'type': 'integer'}, 'vcpus': {'type': 'integer'}, }, 'required': ['ended_at', 'flavor', 'hours', 'instance_id', 'local_gb', 'memory_mb', 'name', 'started_at', 'state', 'tenant_id', 'uptime', 'vcpus'] } } _tenant_usage_list = { 'type': 'object', 'properties': { 'server_usages': _server_usages, 'start': {'type': 'string'}, 'stop': {'type': 'string'}, 'tenant_id': {'type': 'string'}, 'total_hours': {'type': 'number'}, 'total_local_gb_usage': {'type': 'number'}, 'total_memory_mb_usage': {'type': 'number'}, 'total_vcpus_usage': {'type': 'number'}, }, 'required': ['start', 'stop', 'tenant_id', 'total_hours', 'total_local_gb_usage', 'total_memory_mb_usage', 'total_vcpus_usage'] } # 'required' of get_tenant is different from list_tenant's. _tenant_usage_get = copy.deepcopy(_tenant_usage_list) _tenant_usage_get['required'] = ['server_usages', 'start', 'stop', 'tenant_id', 'total_hours', 'total_local_gb_usage', 'total_memory_mb_usage', 'total_vcpus_usage'] list_tenant = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'tenant_usages': { 'type': 'array', 'items': _tenant_usage_list } }, 'required': ['tenant_usages'] } } get_tenant = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'tenant_usage': _tenant_usage_get }, 'required': ['tenant_usage'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/aggregates.py0000664000175000017500000000157412332757070026344 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import aggregates delete_aggregate = { 'status_code': [200] } create_aggregate = copy.deepcopy(aggregates.common_create_aggregate) # V2 API's response status_code is 200 create_aggregate['status_code'] = [200] tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/floating_ips.py0000664000175000017500000000660612332757070026712 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. list_floating_ips = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'floating_ips': { 'type': 'array', 'items': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is integer, but # here allows 'string' also because we will be # able to change it to 'uuid' in the future. 'id': {'type': ['integer', 'string']}, 'pool': {'type': ['string', 'null']}, 'instance_id': {'type': ['integer', 'string', 'null']}, 'ip': { 'type': 'string', 'format': 'ip-address' }, 'fixed_ip': { 'type': ['string', 'null'], 'format': 'ip-address' } }, 'required': ['id', 'pool', 'instance_id', 'ip', 'fixed_ip'] } } }, 'required': ['floating_ips'] } } floating_ip = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'floating_ip': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is integer, but here allows # 'string' also because we will be able to change it to # 'uuid' in the future. 'id': {'type': ['integer', 'string']}, 'pool': {'type': ['string', 'null']}, 'instance_id': {'type': ['integer', 'string', 'null']}, 'ip': { 'type': 'string', 'format': 'ip-address' }, 'fixed_ip': { 'type': ['string', 'null'], 'format': 'ip-address' } }, 'required': ['id', 'pool', 'instance_id', 'ip', 'fixed_ip'] } }, 'required': ['floating_ip'] } } floating_ip_pools = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'floating_ip_pools': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'name': {'type': 'string'} }, 'required': ['name'] } } }, 'required': ['floating_ip_pools'] } } add_remove_floating_ip = { 'status_code': [202] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/servers.py0000664000175000017500000001022612332757070025716 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import parameter_types from tempest.api_schema.compute import servers create_server = { 'status_code': [202], 'response_body': { 'type': 'object', 'properties': { 'server': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is uuid, but here allows # 'integer' also because old OpenStack uses 'integer' # as a server id. 'id': {'type': ['integer', 'string']}, 'security_groups': {'type': 'array'}, 'links': parameter_types.links, 'adminPass': {'type': 'string'}, 'OS-DCF:diskConfig': {'type': 'string'} }, # NOTE: OS-DCF:diskConfig is API extension, and some # environments return a response without the attribute. # So it is not 'required'. # NOTE: adminPass is not required because it can be deactivated # with nova API flag enable_instance_password=False 'required': ['id', 'security_groups', 'links'] } }, 'required': ['server'] } } update_server = copy.deepcopy(servers.base_update_server) update_server['response_body']['properties']['server']['properties'].update({ 'hostId': {'type': 'string'}, 'OS-DCF:diskConfig': {'type': 'string'}, 'accessIPv4': parameter_types.access_ip_v4, 'accessIPv6': parameter_types.access_ip_v6 }) update_server['response_body']['properties']['server']['required'].append( # NOTE: OS-DCF:diskConfig and accessIPv4/v6 are API # extensions, and some environments return a response # without these attributes. So they are not 'required'. 'hostId' ) list_virtual_interfaces = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'virtual_interfaces': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'mac_address': parameter_types.mac_address, 'OS-EXT-VIF-NET:net_id': {'type': 'string'} }, # 'OS-EXT-VIF-NET:net_id' is API extension So it is # not defined as 'required' 'required': ['id', 'mac_address'] } } }, 'required': ['virtual_interfaces'] } } attach_volume = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'volumeAttachment': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'device': {'type': 'string'}, 'volumeId': {'type': 'string'}, 'serverId': {'type': ['integer', 'string']} }, 'required': ['id', 'device', 'volumeId', 'serverId'] } }, 'required': ['volumeAttachment'] } } detach_volume = { 'status_code': [202] } set_get_server_metadata_item = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'meta': { 'type': 'object', 'patternProperties': { '^.+$': {'type': 'string'} } } }, 'required': ['meta'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/extensions.py0000664000175000017500000000322312332757070026423 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. list_extensions = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'extensions': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'updated': { 'type': 'string', 'format': 'data-time' }, 'name': {'type': 'string'}, 'links': {'type': 'array'}, 'namespace': { 'type': 'string', 'format': 'uri' }, 'alias': {'type': 'string'}, 'description': {'type': 'string'} }, 'required': ['updated', 'name', 'links', 'namespace', 'alias', 'description'] } } }, 'required': ['extensions'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/__init__.py0000664000175000017500000000000012332757070025751 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/availability_zone.py0000664000175000017500000000364012332757070027734 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import availability_zone as common base = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'availabilityZoneInfo': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'zoneName': {'type': 'string'}, 'zoneState': { 'type': 'object', 'properties': { 'available': {'type': 'boolean'} }, 'required': ['available'] }, # NOTE: Here is the difference between detail and # non-detail. 'hosts': {'type': 'null'} }, 'required': ['zoneName', 'zoneState', 'hosts'] } } }, 'required': ['availabilityZoneInfo'] } } get_availability_zone_list = copy.deepcopy(base) get_availability_zone_list_detail = copy.deepcopy(base) get_availability_zone_list_detail['response_body']['properties'][ 'availabilityZoneInfo']['items']['properties']['hosts'] = common.detail tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/hosts.py0000664000175000017500000000237512332757070025373 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from tempest.api_schema.compute import hosts startup_host = { 'status_code': [200], 'response_body': hosts.common_start_up_body } # The 'power_action' attribute of 'shutdown_host' API is 'shutdown' shutdown_host = copy.deepcopy(startup_host) shutdown_host['response_body']['properties']['power_action'] = { 'enum': ['shutdown'] } # The 'power_action' attribute of 'reboot_host' API is 'reboot' reboot_host = copy.deepcopy(startup_host) reboot_host['response_body']['properties']['power_action'] = { 'enum': ['reboot'] } update_host = { 'status_code': [200], 'response_body': hosts.update_host_common } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/images.py0000664000175000017500000000732012332757070025473 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api_schema.compute import parameter_types common_image_schema = { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'status': {'type': 'string'}, 'updated': {'type': 'string'}, 'links': parameter_types.links, 'name': {'type': 'string'}, 'created': {'type': 'string'}, 'minDisk': {'type': 'integer'}, 'minRam': {'type': 'integer'}, 'progress': {'type': 'integer'}, 'metadata': {'type': 'object'}, 'server': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is integer, but here # allows 'string' also because we will be able to # change it to 'uuid' in the future. 'id': {'type': ['integer', 'string']}, 'links': parameter_types.links }, 'required': ['id', 'links'] }, 'OS-EXT-IMG-SIZE:size': {'type': 'integer'} }, # 'server' attributes only comes in response body if image is # associated with any server. 'OS-EXT-IMG-SIZE:size' is API # extension, So those are not defined as 'required'. 'required': ['id', 'status', 'updated', 'links', 'name', 'created', 'minDisk', 'minRam', 'progress', 'metadata'] } get_image = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'image': common_image_schema }, 'required': ['image'] } } list_images = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'images': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'links': parameter_types.links, 'name': {'type': 'string'} }, 'required': ['id', 'links', 'name'] } } }, 'required': ['images'] } } create_image = { 'status_code': [202], 'response_header': { 'type': 'object', 'properties': { 'location': { 'type': 'string', 'format': 'uri' } }, 'required': ['location'] } } delete = { 'status_code': [204] } image_metadata = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'metadata': {'type': 'object'} }, 'required': ['metadata'] } } image_meta_item = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'meta': {'type': 'object'} }, 'required': ['meta'] } } list_images_details = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'images': { 'type': 'array', 'items': common_image_schema } }, 'required': ['images'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/fixed_ips.py0000664000175000017500000000253212332757070026200 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. fixed_ips = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'fixed_ip': { 'type': 'object', 'properties': { 'address': { 'type': 'string', 'format': 'ip-address' }, 'cidr': {'type': 'string'}, 'host': {'type': 'string'}, 'hostname': {'type': 'string'} }, 'required': ['address', 'cidr', 'host', 'hostname'] } }, 'required': ['fixed_ip'] } } fixed_ip_action = { 'status_code': [202], 'response_body': {'type': 'string'} } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/instance_usage_audit_logs.py0000664000175000017500000000404412332757070031430 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. common_instance_usage_audit_log = { 'type': 'object', 'properties': { 'hosts_not_run': { 'type': 'array', 'items': {'type': 'string'} }, 'log': {'type': 'object'}, 'num_hosts': {'type': 'integer'}, 'num_hosts_done': {'type': 'integer'}, 'num_hosts_not_run': {'type': 'integer'}, 'num_hosts_running': {'type': 'integer'}, 'overall_status': {'type': 'string'}, 'period_beginning': {'type': 'string'}, 'period_ending': {'type': 'string'}, 'total_errors': {'type': 'integer'}, 'total_instances': {'type': 'integer'} }, 'required': ['hosts_not_run', 'log', 'num_hosts', 'num_hosts_done', 'num_hosts_not_run', 'num_hosts_running', 'overall_status', 'period_beginning', 'period_ending', 'total_errors', 'total_instances'] } get_instance_usage_audit_log = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'instance_usage_audit_log': common_instance_usage_audit_log }, 'required': ['instance_usage_audit_log'] } } list_instance_usage_audit_log = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'instance_usage_audit_logs': common_instance_usage_audit_log }, 'required': ['instance_usage_audit_logs'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/v2/security_groups.py0000664000175000017500000000602412332757070027474 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. common_security_group_rule = { 'from_port': {'type': ['integer', 'null']}, 'to_port': {'type': ['integer', 'null']}, 'group': { 'type': 'object', 'properties': { 'tenant_id': {'type': 'string'}, 'name': {'type': 'string'} } }, 'ip_protocol': {'type': ['string', 'null']}, # 'parent_group_id' can be UUID so defining it as 'string' also. 'parent_group_id': {'type': ['string', 'integer', 'null']}, 'ip_range': { 'type': 'object', 'properties': { 'cidr': {'type': 'string'} } # When optional argument is provided in request body # like 'group_id' then, attribute 'cidr' does not # comes in response body. So it is not 'required'. }, 'id': {'type': ['string', 'integer']} } common_security_group = { 'type': 'object', 'properties': { 'id': {'type': ['integer', 'string']}, 'name': {'type': 'string'}, 'tenant_id': {'type': 'string'}, 'rules': { 'type': 'array', 'items': { 'type': ['object', 'null'], 'properties': common_security_group_rule } }, 'description': {'type': 'string'}, }, 'required': ['id', 'name', 'tenant_id', 'rules', 'description'], } list_security_groups = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'security_groups': { 'type': 'array', 'items': common_security_group } }, 'required': ['security_groups'] } } get_security_group = create_security_group = update_security_group = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'security_group': common_security_group }, 'required': ['security_group'] } } create_security_group_rule = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'security_group_rule': { 'type': 'object', 'properties': common_security_group_rule, 'required': ['from_port', 'to_port', 'group', 'ip_protocol', 'parent_group_id', 'id', 'ip_range'] } }, 'required': ['security_group_rule'] } } delete_security_group_rule = { 'status_code': [202] } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/migrations.py0000664000175000017500000000460112332757070026052 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. list_migrations = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'migrations': { 'type': 'array', 'items': { 'type': 'object', 'properties': { # NOTE: Now the type of 'id' is integer, but here # allows 'string' also because we will be able to # change it to 'uuid' in the future. 'id': {'type': ['integer', 'string']}, 'status': {'type': 'string'}, 'instance_uuid': {'type': 'string'}, 'source_node': {'type': 'string'}, 'source_compute': {'type': 'string'}, 'dest_node': {'type': 'string'}, 'dest_compute': {'type': 'string'}, 'dest_host': {'type': 'string'}, 'old_instance_type_id': { 'type': ['integer', 'string'] }, 'new_instance_type_id': { 'type': ['integer', 'string'] }, 'created_at': {'type': 'string'}, 'updated_at': {'type': ['string', 'null']} }, 'required': [ 'id', 'status', 'instance_uuid', 'source_node', 'source_compute', 'dest_node', 'dest_compute', 'dest_host', 'old_instance_type_id', 'new_instance_type_id', 'created_at', 'updated_at' ] } } }, 'required': ['migrations'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/api_schema/compute/version.py0000664000175000017500000000415112332757070025363 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. version = { 'status_code': [200], 'response_body': { 'type': 'object', 'properties': { 'version': { 'type': 'object', 'properties': { 'id': {'type': 'string'}, 'links': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'href': {'type': 'string', 'format': 'uri'}, 'rel': {'type': 'string'}, 'type': {'type': 'string'} }, 'required': ['href', 'rel'] } }, 'media-types': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'base': {'type': 'string'}, 'type': {'type': 'string'} }, 'required': ['base', 'type'] } }, 'status': {'type': 'string'}, 'updated': {'type': 'string', 'format': 'date-time'} }, 'required': ['id', 'links', 'media-types', 'status', 'updated'] } }, 'required': ['version'] } } tempest-2014.1.dev4108.gf22b6cc/tempest/clients.py0000664000175000017500000007714312332757070021605 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import keystoneclient.exceptions import keystoneclient.v2_0.client from tempest import auth from tempest.common.rest_client import NegativeRestClient from tempest import config from tempest import exceptions from tempest import manager from tempest.openstack.common import log as logging from tempest.services.baremetal.v1.client_json import BaremetalClientJSON from tempest.services import botoclients from tempest.services.compute.json.agents_client import \ AgentsClientJSON from tempest.services.compute.json.aggregates_client import \ AggregatesClientJSON from tempest.services.compute.json.availability_zone_client import \ AvailabilityZoneClientJSON from tempest.services.compute.json.certificates_client import \ CertificatesClientJSON from tempest.services.compute.json.extensions_client import \ ExtensionsClientJSON from tempest.services.compute.json.fixed_ips_client import FixedIPsClientJSON from tempest.services.compute.json.flavors_client import FlavorsClientJSON from tempest.services.compute.json.floating_ips_client import \ FloatingIPsClientJSON from tempest.services.compute.json.hosts_client import HostsClientJSON from tempest.services.compute.json.hypervisor_client import \ HypervisorClientJSON from tempest.services.compute.json.images_client import ImagesClientJSON from tempest.services.compute.json.instance_usage_audit_log_client import \ InstanceUsagesAuditLogClientJSON from tempest.services.compute.json.interfaces_client import \ InterfacesClientJSON from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON from tempest.services.compute.json.limits_client import LimitsClientJSON from tempest.services.compute.json.migrations_client import \ MigrationsClientJSON from tempest.services.compute.json.quotas_client import QuotasClientJSON from tempest.services.compute.json.security_groups_client import \ SecurityGroupsClientJSON from tempest.services.compute.json.servers_client import ServersClientJSON from tempest.services.compute.json.services_client import ServicesClientJSON from tempest.services.compute.json.tenant_usages_client import \ TenantUsagesClientJSON from tempest.services.compute.json.volumes_extensions_client import \ VolumesExtensionsClientJSON from tempest.services.compute.v3.json.agents_client import AgentsV3ClientJSON from tempest.services.compute.v3.json.aggregates_client import \ AggregatesV3ClientJSON from tempest.services.compute.v3.json.availability_zone_client import \ AvailabilityZoneV3ClientJSON from tempest.services.compute.v3.json.certificates_client import \ CertificatesV3ClientJSON from tempest.services.compute.v3.json.extensions_client import \ ExtensionsV3ClientJSON from tempest.services.compute.v3.json.flavors_client import FlavorsV3ClientJSON from tempest.services.compute.v3.json.hosts_client import HostsV3ClientJSON from tempest.services.compute.v3.json.hypervisor_client import \ HypervisorV3ClientJSON from tempest.services.compute.v3.json.interfaces_client import \ InterfacesV3ClientJSON from tempest.services.compute.v3.json.keypairs_client import \ KeyPairsV3ClientJSON from tempest.services.compute.v3.json.migration_client import \ MigrationsV3ClientJSON from tempest.services.compute.v3.json.quotas_client import \ QuotasV3ClientJSON from tempest.services.compute.v3.json.servers_client import \ ServersV3ClientJSON from tempest.services.compute.v3.json.services_client import \ ServicesV3ClientJSON from tempest.services.compute.v3.json.version_client import \ VersionV3ClientJSON from tempest.services.compute.xml.aggregates_client import AggregatesClientXML from tempest.services.compute.xml.availability_zone_client import \ AvailabilityZoneClientXML from tempest.services.compute.xml.certificates_client import \ CertificatesClientXML from tempest.services.compute.xml.extensions_client import ExtensionsClientXML from tempest.services.compute.xml.fixed_ips_client import FixedIPsClientXML from tempest.services.compute.xml.flavors_client import FlavorsClientXML from tempest.services.compute.xml.floating_ips_client import \ FloatingIPsClientXML from tempest.services.compute.xml.hosts_client import HostsClientXML from tempest.services.compute.xml.hypervisor_client import HypervisorClientXML from tempest.services.compute.xml.images_client import ImagesClientXML from tempest.services.compute.xml.instance_usage_audit_log_client import \ InstanceUsagesAuditLogClientXML from tempest.services.compute.xml.interfaces_client import \ InterfacesClientXML from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML from tempest.services.compute.xml.limits_client import LimitsClientXML from tempest.services.compute.xml.quotas_client import QuotasClientXML from tempest.services.compute.xml.security_groups_client \ import SecurityGroupsClientXML from tempest.services.compute.xml.servers_client import ServersClientXML from tempest.services.compute.xml.services_client import ServicesClientXML from tempest.services.compute.xml.tenant_usages_client import \ TenantUsagesClientXML from tempest.services.compute.xml.volumes_extensions_client import \ VolumesExtensionsClientXML from tempest.services.data_processing.v1_1.client import DataProcessingClient from tempest.services.database.json.flavors_client import \ DatabaseFlavorsClientJSON from tempest.services.identity.json.identity_client import IdentityClientJSON from tempest.services.identity.json.identity_client import TokenClientJSON from tempest.services.identity.v3.json.credentials_client import \ CredentialsClientJSON from tempest.services.identity.v3.json.endpoints_client import \ EndPointClientJSON from tempest.services.identity.v3.json.identity_client import \ IdentityV3ClientJSON from tempest.services.identity.v3.json.identity_client import V3TokenClientJSON from tempest.services.identity.v3.json.policy_client import PolicyClientJSON from tempest.services.identity.v3.json.service_client import \ ServiceClientJSON from tempest.services.identity.v3.xml.credentials_client import \ CredentialsClientXML from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML from tempest.services.identity.v3.xml.identity_client import \ IdentityV3ClientXML from tempest.services.identity.v3.xml.identity_client import V3TokenClientXML from tempest.services.identity.v3.xml.policy_client import PolicyClientXML from tempest.services.identity.v3.xml.service_client import \ ServiceClientXML from tempest.services.identity.xml.identity_client import IdentityClientXML from tempest.services.identity.xml.identity_client import TokenClientXML from tempest.services.image.v1.json.image_client import ImageClientJSON from tempest.services.image.v2.json.image_client import ImageClientV2JSON from tempest.services.network.json.network_client import NetworkClientJSON from tempest.services.network.xml.network_client import NetworkClientXML from tempest.services.object_storage.account_client import AccountClient from tempest.services.object_storage.account_client import \ AccountClientCustomizedHeader from tempest.services.object_storage.container_client import ContainerClient from tempest.services.object_storage.object_client import ObjectClient from tempest.services.object_storage.object_client import \ ObjectClientCustomizedHeader from tempest.services.orchestration.json.orchestration_client import \ OrchestrationClient from tempest.services.queuing.json.queuing_client import QueuingClientJSON from tempest.services.telemetry.json.telemetry_client import \ TelemetryClientJSON from tempest.services.telemetry.xml.telemetry_client import \ TelemetryClientXML from tempest.services.volume.json.admin.volume_hosts_client import \ VolumeHostsClientJSON from tempest.services.volume.json.admin.volume_quotas_client import \ VolumeQuotasClientJSON from tempest.services.volume.json.admin.volume_services_client import \ VolumesServicesClientJSON from tempest.services.volume.json.admin.volume_types_client import \ VolumeTypesClientJSON from tempest.services.volume.json.backups_client import BackupsClientJSON from tempest.services.volume.json.extensions_client import \ ExtensionsClientJSON as VolumeExtensionClientJSON from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON from tempest.services.volume.json.volumes_client import VolumesClientJSON from tempest.services.volume.v2.json.volumes_client import VolumesV2ClientJSON from tempest.services.volume.v2.xml.volumes_client import VolumesV2ClientXML from tempest.services.volume.xml.admin.volume_hosts_client import \ VolumeHostsClientXML from tempest.services.volume.xml.admin.volume_quotas_client import \ VolumeQuotasClientXML from tempest.services.volume.xml.admin.volume_services_client import \ VolumesServicesClientXML from tempest.services.volume.xml.admin.volume_types_client import \ VolumeTypesClientXML from tempest.services.volume.xml.backups_client import BackupsClientXML from tempest.services.volume.xml.extensions_client import \ ExtensionsClientXML as VolumeExtensionClientXML from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML from tempest.services.volume.xml.volumes_client import VolumesClientXML CONF = config.CONF LOG = logging.getLogger(__name__) class Manager(manager.Manager): """ Top level manager for OpenStack tempest clients """ def __init__(self, credentials=None, interface='json', service=None): # Set interface and client type first self.interface = interface self.client_type = 'tempest' # super cares for credentials validation super(Manager, self).__init__(credentials=credentials) if self.interface == 'xml': self.certificates_client = CertificatesClientXML( self.auth_provider) self.servers_client = ServersClientXML(self.auth_provider) self.limits_client = LimitsClientXML(self.auth_provider) self.images_client = ImagesClientXML(self.auth_provider) self.keypairs_client = KeyPairsClientXML(self.auth_provider) self.quotas_client = QuotasClientXML(self.auth_provider) self.flavors_client = FlavorsClientXML(self.auth_provider) self.extensions_client = ExtensionsClientXML(self.auth_provider) self.volumes_extensions_client = VolumesExtensionsClientXML( self.auth_provider) self.floating_ips_client = FloatingIPsClientXML( self.auth_provider) self.backups_client = BackupsClientXML(self.auth_provider) self.snapshots_client = SnapshotsClientXML(self.auth_provider) self.volumes_client = VolumesClientXML(self.auth_provider) self.volumes_v2_client = VolumesV2ClientXML(self.auth_provider) self.volume_types_client = VolumeTypesClientXML( self.auth_provider) self.identity_client = IdentityClientXML(self.auth_provider) self.identity_v3_client = IdentityV3ClientXML( self.auth_provider) self.security_groups_client = SecurityGroupsClientXML( self.auth_provider) self.interfaces_client = InterfacesClientXML(self.auth_provider) self.endpoints_client = EndPointClientXML(self.auth_provider) self.fixed_ips_client = FixedIPsClientXML(self.auth_provider) self.availability_zone_client = AvailabilityZoneClientXML( self.auth_provider) self.service_client = ServiceClientXML(self.auth_provider) self.volume_services_client = VolumesServicesClientXML( self.auth_provider) self.aggregates_client = AggregatesClientXML(self.auth_provider) self.services_client = ServicesClientXML(self.auth_provider) self.tenant_usages_client = TenantUsagesClientXML( self.auth_provider) self.policy_client = PolicyClientXML(self.auth_provider) self.hosts_client = HostsClientXML(self.auth_provider) self.hypervisor_client = HypervisorClientXML(self.auth_provider) self.network_client = NetworkClientXML(self.auth_provider) self.credentials_client = CredentialsClientXML( self.auth_provider) self.instance_usages_audit_log_client = \ InstanceUsagesAuditLogClientXML(self.auth_provider) self.volume_hosts_client = VolumeHostsClientXML( self.auth_provider) self.volume_quotas_client = VolumeQuotasClientXML( self.auth_provider) self.volumes_extension_client = VolumeExtensionClientXML( self.auth_provider) if CONF.service_available.ceilometer: self.telemetry_client = TelemetryClientXML( self.auth_provider) self.token_client = TokenClientXML() self.token_v3_client = V3TokenClientXML() elif self.interface == 'json': self.certificates_client = CertificatesClientJSON( self.auth_provider) self.certificates_v3_client = CertificatesV3ClientJSON( self.auth_provider) self.baremetal_client = BaremetalClientJSON(self.auth_provider) self.servers_client = ServersClientJSON(self.auth_provider) self.servers_v3_client = ServersV3ClientJSON(self.auth_provider) self.limits_client = LimitsClientJSON(self.auth_provider) self.images_client = ImagesClientJSON(self.auth_provider) self.keypairs_v3_client = KeyPairsV3ClientJSON( self.auth_provider) self.keypairs_client = KeyPairsClientJSON(self.auth_provider) self.keypairs_v3_client = KeyPairsV3ClientJSON( self.auth_provider) self.quotas_client = QuotasClientJSON(self.auth_provider) self.quotas_v3_client = QuotasV3ClientJSON(self.auth_provider) self.flavors_client = FlavorsClientJSON(self.auth_provider) self.flavors_v3_client = FlavorsV3ClientJSON(self.auth_provider) self.extensions_v3_client = ExtensionsV3ClientJSON( self.auth_provider) self.extensions_client = ExtensionsClientJSON( self.auth_provider) self.volumes_extensions_client = VolumesExtensionsClientJSON( self.auth_provider) self.floating_ips_client = FloatingIPsClientJSON( self.auth_provider) self.backups_client = BackupsClientJSON(self.auth_provider) self.snapshots_client = SnapshotsClientJSON(self.auth_provider) self.volumes_client = VolumesClientJSON(self.auth_provider) self.volumes_v2_client = VolumesV2ClientJSON(self.auth_provider) self.volume_types_client = VolumeTypesClientJSON( self.auth_provider) self.identity_client = IdentityClientJSON(self.auth_provider) self.identity_v3_client = IdentityV3ClientJSON( self.auth_provider) self.security_groups_client = SecurityGroupsClientJSON( self.auth_provider) self.interfaces_v3_client = InterfacesV3ClientJSON( self.auth_provider) self.interfaces_client = InterfacesClientJSON( self.auth_provider) self.endpoints_client = EndPointClientJSON(self.auth_provider) self.fixed_ips_client = FixedIPsClientJSON(self.auth_provider) self.availability_zone_v3_client = AvailabilityZoneV3ClientJSON( self.auth_provider) self.availability_zone_client = AvailabilityZoneClientJSON( self.auth_provider) self.services_v3_client = ServicesV3ClientJSON( self.auth_provider) self.service_client = ServiceClientJSON(self.auth_provider) self.volume_services_client = VolumesServicesClientJSON( self.auth_provider) self.agents_v3_client = AgentsV3ClientJSON(self.auth_provider) self.aggregates_v3_client = AggregatesV3ClientJSON( self.auth_provider) self.aggregates_client = AggregatesClientJSON( self.auth_provider) self.services_client = ServicesClientJSON(self.auth_provider) self.tenant_usages_client = TenantUsagesClientJSON( self.auth_provider) self.version_v3_client = VersionV3ClientJSON(self.auth_provider) self.migrations_v3_client = MigrationsV3ClientJSON( self.auth_provider) self.policy_client = PolicyClientJSON(self.auth_provider) self.hosts_client = HostsClientJSON(self.auth_provider) self.hypervisor_v3_client = HypervisorV3ClientJSON( self.auth_provider) self.hypervisor_client = HypervisorClientJSON( self.auth_provider) self.network_client = NetworkClientJSON(self.auth_provider) self.credentials_client = CredentialsClientJSON( self.auth_provider) self.instance_usages_audit_log_client = \ InstanceUsagesAuditLogClientJSON(self.auth_provider) self.volume_hosts_client = VolumeHostsClientJSON( self.auth_provider) self.volume_quotas_client = VolumeQuotasClientJSON( self.auth_provider) self.volumes_extension_client = VolumeExtensionClientJSON( self.auth_provider) self.hosts_v3_client = HostsV3ClientJSON(self.auth_provider) self.database_flavors_client = DatabaseFlavorsClientJSON( self.auth_provider) self.queuing_client = QueuingClientJSON(self.auth_provider) if CONF.service_available.ceilometer: self.telemetry_client = TelemetryClientJSON( self.auth_provider) self.token_client = TokenClientJSON() self.token_v3_client = V3TokenClientJSON() self.negative_client = NegativeRestClient(self.auth_provider) self.negative_client.service = service else: msg = "Unsupported interface type `%s'" % interface raise exceptions.InvalidConfiguration(msg) # TODO(andreaf) EC2 client still do their auth, v2 only ec2_client_args = (self.credentials.username, self.credentials.password, CONF.identity.uri, self.credentials.tenant_name) # common clients self.account_client = AccountClient(self.auth_provider) self.agents_client = AgentsClientJSON(self.auth_provider) if CONF.service_available.glance: self.image_client = ImageClientJSON(self.auth_provider) self.image_client_v2 = ImageClientV2JSON(self.auth_provider) self.container_client = ContainerClient(self.auth_provider) self.object_client = ObjectClient(self.auth_provider) self.orchestration_client = OrchestrationClient( self.auth_provider) self.ec2api_client = botoclients.APIClientEC2(*ec2_client_args) self.s3_client = botoclients.ObjectClientS3(*ec2_client_args) self.custom_object_client = ObjectClientCustomizedHeader( self.auth_provider) self.custom_account_client = \ AccountClientCustomizedHeader(self.auth_provider) self.data_processing_client = DataProcessingClient( self.auth_provider) self.migrations_client = MigrationsClientJSON(self.auth_provider) class AltManager(Manager): """ Manager object that uses the alt_XXX credentials for its managed client objects """ def __init__(self, interface='json', service=None): super(AltManager, self).__init__( credentials=auth.get_default_credentials('alt_user'), interface=interface, service=service) class AdminManager(Manager): """ Manager object that uses the admin credentials for its managed client objects """ def __init__(self, interface='json', service=None): super(AdminManager, self).__init__( credentials=auth.get_default_credentials('identity_admin'), interface=interface, service=service) class ComputeAdminManager(Manager): """ Manager object that uses the compute_admin credentials for its managed client objects """ def __init__(self, interface='json', service=None): base = super(ComputeAdminManager, self) base.__init__( credentials=auth.get_default_credentials('compute_admin'), interface=interface, service=service) class OfficialClientManager(manager.Manager): """ Manager that provides access to the official python clients for calling various OpenStack APIs. """ NOVACLIENT_VERSION = '2' CINDERCLIENT_VERSION = '1' HEATCLIENT_VERSION = '1' IRONICCLIENT_VERSION = '1' SAHARACLIENT_VERSION = '1.1' def __init__(self, credentials): # FIXME(andreaf) Auth provider for client_type 'official' is # not implemented yet, setting to 'tempest' for now. self.client_type = 'tempest' self.interface = None # super cares for credentials validation super(OfficialClientManager, self).__init__(credentials=credentials) self.baremetal_client = self._get_baremetal_client() self.compute_client = self._get_compute_client(credentials) self.identity_client = self._get_identity_client(credentials) self.image_client = self._get_image_client() self.network_client = self._get_network_client() self.volume_client = self._get_volume_client(credentials) self.object_storage_client = self._get_object_storage_client( credentials) self.orchestration_client = self._get_orchestration_client( credentials) self.data_processing_client = self._get_data_processing_client( credentials) def _get_roles(self): admin_credentials = auth.get_default_credentials('identity_admin') keystone_admin = self._get_identity_client(admin_credentials) username = self.credentials.username tenant_name = self.credentials.tenant_name user_id = keystone_admin.users.find(name=username).id tenant_id = keystone_admin.tenants.find(name=tenant_name).id roles = keystone_admin.roles.roles_for_user( user=user_id, tenant=tenant_id) return [r.name for r in roles] def _get_compute_client(self, credentials): # Novaclient will not execute operations for anyone but the # identified user, so a new client needs to be created for # each user that operations need to be performed for. if not CONF.service_available.nova: return None import novaclient.client auth_url = CONF.identity.uri dscv = CONF.identity.disable_ssl_certificate_validation region = CONF.identity.region client_args = (credentials.username, credentials.password, credentials.tenant_name, auth_url) # Create our default Nova client to use in testing service_type = CONF.compute.catalog_type endpoint_type = CONF.compute.endpoint_type return novaclient.client.Client(self.NOVACLIENT_VERSION, *client_args, service_type=service_type, endpoint_type=endpoint_type, region_name=region, no_cache=True, insecure=dscv, http_log_debug=True) def _get_image_client(self): if not CONF.service_available.glance: return None import glanceclient token = self.identity_client.auth_token region = CONF.identity.region endpoint_type = CONF.image.endpoint_type endpoint = self.identity_client.service_catalog.url_for( attr='region', filter_value=region, service_type=CONF.image.catalog_type, endpoint_type=endpoint_type) dscv = CONF.identity.disable_ssl_certificate_validation return glanceclient.Client('1', endpoint=endpoint, token=token, insecure=dscv) def _get_volume_client(self, credentials): if not CONF.service_available.cinder: return None import cinderclient.client auth_url = CONF.identity.uri region = CONF.identity.region endpoint_type = CONF.volume.endpoint_type dscv = CONF.identity.disable_ssl_certificate_validation return cinderclient.client.Client(self.CINDERCLIENT_VERSION, credentials.username, credentials.password, credentials.tenant_name, auth_url, region_name=region, endpoint_type=endpoint_type, insecure=dscv, http_log_debug=True) def _get_object_storage_client(self, credentials): if not CONF.service_available.swift: return None import swiftclient auth_url = CONF.identity.uri # add current tenant to swift operator role group. admin_credentials = auth.get_default_credentials('identity_admin') keystone_admin = self._get_identity_client(admin_credentials) # enable test user to operate swift by adding operator role to him. roles = keystone_admin.roles.list() operator_role = CONF.object_storage.operator_role member_role = [role for role in roles if role.name == operator_role][0] # NOTE(maurosr): This is surrounded in the try-except block cause # neutron tests doesn't have tenant isolation. try: keystone_admin.roles.add_user_role(self.identity_client.user_id, member_role.id, self.identity_client.tenant_id) except keystoneclient.exceptions.Conflict: pass endpoint_type = CONF.object_storage.endpoint_type os_options = {'endpoint_type': endpoint_type} return swiftclient.Connection(auth_url, credentials.username, credentials.password, tenant_name=credentials.tenant_name, auth_version='2', os_options=os_options) def _get_orchestration_client(self, credentials): if not CONF.service_available.heat: return None import heatclient.client keystone = self._get_identity_client(credentials) region = CONF.identity.region endpoint_type = CONF.orchestration.endpoint_type token = keystone.auth_token service_type = CONF.orchestration.catalog_type try: endpoint = keystone.service_catalog.url_for( attr='region', filter_value=region, service_type=service_type, endpoint_type=endpoint_type) except keystoneclient.exceptions.EndpointNotFound: return None else: return heatclient.client.Client(self.HEATCLIENT_VERSION, endpoint, token=token, username=credentials.username, password=credentials.password) def _get_identity_client(self, credentials): # This identity client is not intended to check the security # of the identity service, so use admin credentials by default. auth_url = CONF.identity.uri dscv = CONF.identity.disable_ssl_certificate_validation return keystoneclient.v2_0.client.Client( username=credentials.username, password=credentials.password, tenant_name=credentials.tenant_name, auth_url=auth_url, insecure=dscv) def _get_baremetal_client(self): # ironic client is currently intended to by used by admin users if not CONF.service_available.ironic: return None import ironicclient.client roles = self._get_roles() if CONF.identity.admin_role not in roles: return None auth_url = CONF.identity.uri api_version = self.IRONICCLIENT_VERSION insecure = CONF.identity.disable_ssl_certificate_validation service_type = CONF.baremetal.catalog_type endpoint_type = CONF.baremetal.endpoint_type creds = { 'os_username': self.credentials.username, 'os_password': self.credentials.password, 'os_tenant_name': self.credentials.tenant_name } try: return ironicclient.client.get_client( api_version=api_version, os_auth_url=auth_url, insecure=insecure, os_service_type=service_type, os_endpoint_type=endpoint_type, **creds) except keystoneclient.exceptions.EndpointNotFound: return None def _get_network_client(self): # The intended configuration is for the network client to have # admin privileges and indicate for whom resources are being # created via a 'tenant_id' parameter. This will often be # preferable to authenticating as a specific user because # working with certain resources (public routers and networks) # often requires admin privileges anyway. if not CONF.service_available.neutron: return None import neutronclient.v2_0.client credentials = auth.get_default_credentials('identity_admin') auth_url = CONF.identity.uri dscv = CONF.identity.disable_ssl_certificate_validation endpoint_type = CONF.network.endpoint_type return neutronclient.v2_0.client.Client( username=credentials.username, password=credentials.password, tenant_name=credentials.tenant_name, endpoint_type=endpoint_type, auth_url=auth_url, insecure=dscv) def _get_data_processing_client(self, credentials): if not CONF.service_available.sahara: # Sahara isn't available return None import saharaclient.client endpoint_type = CONF.data_processing.endpoint_type catalog_type = CONF.data_processing.catalog_type auth_url = CONF.identity.uri client = saharaclient.client.Client( self.SAHARACLIENT_VERSION, credentials.username, credentials.password, project_name=credentials.tenant_name, endpoint_type=endpoint_type, service_type=catalog_type, auth_url=auth_url) return client tempest-2014.1.dev4108.gf22b6cc/tempest/stress/0000775000175000017500000000000012332757136021104 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/stress/stressaction.py0000664000175000017500000000615612332757070024204 0ustar chuckchuck00000000000000# (c) Copyright 2013 Hewlett-Packard Development Company, L.P. # # 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 signal import sys from tempest.openstack.common import log as logging class StressAction(object): def __init__(self, manager, max_runs=None, stop_on_error=False): full_cname = self.__module__ + "." + self.__class__.__name__ self.logger = logging.getLogger(full_cname) self.manager = manager self.max_runs = max_runs self.stop_on_error = stop_on_error def _shutdown_handler(self, signal, frame): try: self.tearDown() except Exception: self.logger.exception("Error while tearDown") sys.exit(0) @property def action(self): """This methods returns the action. Overload this if you create a stress test wrapper. """ return self.__class__.__name__ def setUp(self, **kwargs): """This method is called before the run method to help the test initialize any structures. kwargs contains arguments passed in from the configuration json file. setUp doesn't count against the time duration. """ self.logger.debug("setUp") def tearDown(self): """This method is called to do any cleanup after the test is complete. """ self.logger.debug("tearDown") def execute(self, shared_statistic): """This is the main execution entry point called by the driver. We register a signal handler to allow us to tearDown gracefully, and then exit. We also keep track of how many runs we do. """ signal.signal(signal.SIGHUP, self._shutdown_handler) signal.signal(signal.SIGTERM, self._shutdown_handler) while self.max_runs is None or (shared_statistic['runs'] < self.max_runs): self.logger.debug("Trigger new run (run %d)" % shared_statistic['runs']) try: self.run() except Exception: shared_statistic['fails'] += 1 self.logger.exception("Failure in run") finally: shared_statistic['runs'] += 1 if self.stop_on_error and (shared_statistic['fails'] > 1): self.logger.warn("Stop process due to" "\"stop-on-error\" argument") self.tearDown() sys.exit(1) def run(self): """This method is where the stress test code runs.""" raise NotImplemented() tempest-2014.1.dev4108.gf22b6cc/tempest/stress/tools/0000775000175000017500000000000012332757136022244 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/stress/tools/cleanup.py0000775000175000017500000000127412332757070024251 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2013 Quanta Research Cambridge, 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 tempest.stress import cleanup cleanup.cleanup() tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/0000775000175000017500000000000012332757136022544 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/volume_attach_verify.py0000664000175000017500000002421212332757070027333 0ustar chuckchuck00000000000000# 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 tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config import tempest.stress.stressaction as stressaction import tempest.test import re CONF = config.CONF class VolumeVerifyStress(stressaction.StressAction): def _create_keypair(self): keyname = data_utils.rand_name("key") resp, self.key = self.manager.keypairs_client.create_keypair(keyname) assert(resp.status == 200) def _delete_keypair(self): resp, _ = self.manager.keypairs_client.delete_keypair(self.key['name']) assert(resp.status == 202) def _create_vm(self): self.name = name = data_utils.rand_name("instance") servers_client = self.manager.servers_client self.logger.info("creating %s" % name) vm_args = self.vm_extra_args.copy() vm_args['security_groups'] = [self.sec_grp] vm_args['key_name'] = self.key['name'] resp, server = servers_client.create_server(name, self.image, self.flavor, **vm_args) self.server_id = server['id'] assert(resp.status == 202) self.manager.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') def _destroy_vm(self): self.logger.info("deleting server: %s" % self.server_id) resp, _ = self.manager.servers_client.delete_server(self.server_id) assert(resp.status == 204) # It cannot be 204 if I had to wait.. self.manager.servers_client.wait_for_server_termination(self.server_id) self.logger.info("deleted server: %s" % self.server_id) def _create_sec_group(self): sec_grp_cli = self.manager.security_groups_client s_name = data_utils.rand_name('sec_grp-') s_description = data_utils.rand_name('desc-') _, self.sec_grp = sec_grp_cli.create_security_group(s_name, s_description) create_rule = sec_grp_cli.create_security_group_rule create_rule(self.sec_grp['id'], 'tcp', 22, 22) create_rule(self.sec_grp['id'], 'icmp', -1, -1) def _destroy_sec_grp(self): sec_grp_cli = self.manager.security_groups_client sec_grp_cli.delete_security_group(self.sec_grp['id']) def _create_floating_ip(self): floating_cli = self.manager.floating_ips_client _, self.floating = floating_cli.create_floating_ip(self.floating_pool) def _destroy_floating_ip(self): cli = self.manager.floating_ips_client cli.delete_floating_ip(self.floating['id']) cli.wait_for_resource_deletion(self.floating['id']) self.logger.info("Deleted Floating IP %s", str(self.floating['ip'])) def _create_volume(self): name = data_utils.rand_name("volume") self.logger.info("creating volume: %s" % name) volumes_client = self.manager.volumes_client resp, self.volume = volumes_client.create_volume(size=1, display_name= name) assert(resp.status == 200) volumes_client.wait_for_volume_status(self.volume['id'], 'available') self.logger.info("created volume: %s" % self.volume['id']) def _delete_volume(self): self.logger.info("deleting volume: %s" % self.volume['id']) volumes_client = self.manager.volumes_client resp, _ = volumes_client.delete_volume(self.volume['id']) assert(resp.status == 202) volumes_client.wait_for_resource_deletion(self.volume['id']) self.logger.info("deleted volume: %s" % self.volume['id']) def _wait_disassociate(self): cli = self.manager.floating_ips_client def func(): _, floating = cli.get_floating_ip_details(self.floating['id']) return floating['instance_id'] is None if not tempest.test.call_until_true(func, CONF.compute.build_timeout, CONF.compute.build_interval): raise RuntimeError("IP disassociate timeout!") def new_server_ops(self): self._create_vm() cli = self.manager.floating_ips_client cli.associate_floating_ip_to_server(self.floating['ip'], self.server_id) if self.ssh_test_before_attach and self.enable_ssh_verify: self.logger.info("Scanning for block devices via ssh on %s" % self.server_id) self.part_wait(self.detach_match_count) def setUp(self, **kwargs): """Note able configuration combinations: Closest options to the test_stamp_pattern: new_server = True new_volume = True enable_ssh_verify = True ssh_test_before_attach = False Just attaching: new_server = False new_volume = False enable_ssh_verify = True ssh_test_before_attach = True Mostly API load by repeated attachment: new_server = False new_volume = False enable_ssh_verify = False ssh_test_before_attach = False Minimal Nova load, but cinder load not decreased: new_server = False new_volume = True enable_ssh_verify = True ssh_test_before_attach = True """ self.image = CONF.compute.image_ref self.flavor = CONF.compute.flavor_ref self.vm_extra_args = kwargs.get('vm_extra_args', {}) self.floating_pool = kwargs.get('floating_pool', None) self.new_volume = kwargs.get('new_volume', True) self.new_server = kwargs.get('new_server', False) self.enable_ssh_verify = kwargs.get('enable_ssh_verify', True) self.ssh_test_before_attach = kwargs.get('ssh_test_before_attach', False) self.part_line_re = re.compile(kwargs.get('part_line_re', '.*vd.*')) self.detach_match_count = kwargs.get('detach_match_count', 1) self.attach_match_count = kwargs.get('attach_match_count', 2) self.part_name = kwargs.get('part_name', '/dev/vdc') self._create_floating_ip() self._create_sec_group() self._create_keypair() private_key = self.key['private_key'] username = CONF.compute.image_ssh_user self.remote_client = remote_client.RemoteClient(self.floating['ip'], username, pkey=private_key) if not self.new_volume: self._create_volume() if not self.new_server: self.new_server_ops() # now we just test is number of partition increased or decrised def part_wait(self, num_match): def _part_state(): self.partitions = self.remote_client.get_partitions().split('\n') matching = 0 for part_line in self.partitions[1:]: if self.part_line_re.match(part_line): matching += 1 return matching == num_match if tempest.test.call_until_true(_part_state, CONF.compute.build_timeout, CONF.compute.build_interval): return else: raise RuntimeError("Unexpected partitions: %s", str(self.partitions)) def run(self): if self.new_server: self.new_server_ops() if self.new_volume: self._create_volume() servers_client = self.manager.servers_client self.logger.info("attach volume (%s) to vm %s" % (self.volume['id'], self.server_id)) resp, body = servers_client.attach_volume(self.server_id, self.volume['id'], self.part_name) assert(resp.status == 200) self.manager.volumes_client.wait_for_volume_status(self.volume['id'], 'in-use') if self.enable_ssh_verify: self.logger.info("Scanning for new block device on %s" % self.server_id) self.part_wait(self.attach_match_count) resp, body = servers_client.detach_volume(self.server_id, self.volume['id']) assert(resp.status == 202) self.manager.volumes_client.wait_for_volume_status(self.volume['id'], 'available') if self.enable_ssh_verify: self.logger.info("Scanning for block device disapperance on %s" % self.server_id) self.part_wait(self.detach_match_count) if self.new_volume: self._delete_volume() if self.new_server: self._destroy_vm() def tearDown(self): cli = self.manager.floating_ips_client cli.disassociate_floating_ip_from_server(self.floating['ip'], self.server_id) self._wait_disassociate() if not self.new_server: self._destroy_vm() self._delete_keypair() self._destroy_floating_ip() self._destroy_sec_grp() if not self.new_volume: self._delete_volume() tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/server_create_destroy.py0000664000175000017500000000325412332757070027521 0ustar chuckchuck00000000000000# Copyright 2013 Quanta Research Cambridge, 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 tempest.common.utils import data_utils from tempest import config import tempest.stress.stressaction as stressaction CONF = config.CONF class ServerCreateDestroyTest(stressaction.StressAction): def setUp(self, **kwargs): self.image = CONF.compute.image_ref self.flavor = CONF.compute.flavor_ref def run(self): name = data_utils.rand_name("instance") self.logger.info("creating %s" % name) resp, server = self.manager.servers_client.create_server( name, self.image, self.flavor) server_id = server['id'] assert(resp.status == 202) self.manager.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.logger.info("created %s" % server_id) self.logger.info("deleting %s" % name) resp, _ = self.manager.servers_client.delete_server(server_id) assert(resp.status == 204) self.manager.servers_client.wait_for_server_termination(server_id) self.logger.info("deleted %s" % server_id) tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/volume_create_delete.py0000664000175000017500000000265412332757070027276 0ustar chuckchuck00000000000000# 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 tempest.common.utils import data_utils import tempest.stress.stressaction as stressaction class VolumeCreateDeleteTest(stressaction.StressAction): def run(self): name = data_utils.rand_name("volume") self.logger.info("creating %s" % name) volumes_client = self.manager.volumes_client resp, volume = volumes_client.create_volume(size=1, display_name=name) assert(resp.status == 200) vol_id = volume['id'] volumes_client.wait_for_volume_status(vol_id, 'available') self.logger.info("created %s" % volume['id']) self.logger.info("deleting %s" % name) resp, _ = volumes_client.delete_volume(vol_id) assert(resp.status == 202) volumes_client.wait_for_resource_deletion(vol_id) self.logger.info("deleted %s" % vol_id) tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/unit_test.py0000664000175000017500000000642212332757070025135 0ustar chuckchuck00000000000000# 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 tempest import config from tempest.openstack.common import importutils from tempest.openstack.common import log as logging import tempest.stress.stressaction as stressaction CONF = config.CONF class SetUpClassRunTime(object): process = 'process' action = 'action' application = 'application' allowed = set((process, action, application)) @classmethod def validate(cls, name): if name not in cls.allowed: raise KeyError("\'%s\' not a valid option" % name) class UnitTest(stressaction.StressAction): """This is a special action for running existing unittests as stress test. You need to pass ``test_method`` and ``class_setup_per`` using ``kwargs`` in the JSON descriptor; ``test_method`` should be the fully qualified name of a unittest, ``class_setup_per`` should be one from: ``application``: once in the stress job lifetime ``process``: once in the worker process lifetime ``action``: on each action Not all combination working in every case. """ def setUp(self, **kwargs): method = kwargs['test_method'].split('.') self.test_method = method.pop() self.klass = importutils.import_class('.'.join(method)) self.logger = logging.getLogger('.'.join(method)) # valid options are 'process', 'application' , 'action' self.class_setup_per = kwargs.get('class_setup_per', SetUpClassRunTime.process) SetUpClassRunTime.validate(self.class_setup_per) if self.class_setup_per == SetUpClassRunTime.application: self.klass.setUpClass() self.setupclass_called = False @property def action(self): if self.test_method: return self.test_method return super(UnitTest, self).action def run_core(self): res = self.klass(self.test_method).run() if res.errors: raise RuntimeError(res.errors) def run(self): if self.class_setup_per != SetUpClassRunTime.application: if (self.class_setup_per == SetUpClassRunTime.action or self.setupclass_called is False): self.klass.setUpClass() self.setupclass_called = True try: self.run_core() except Exception as e: raise e finally: if (CONF.stress.leave_dirty_stack is False and self.class_setup_per == SetUpClassRunTime.action): self.klass.tearDownClass() else: self.run_core() def tearDown(self): if self.class_setup_per != SetUpClassRunTime.action: self.klass.tearDownClass() tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/__init__.py0000664000175000017500000000000012332757070024640 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/volume_attach_delete.py0000664000175000017500000000637512332757070027303 0ustar chuckchuck00000000000000# (c) 2013 Deutsche Telekom AG # 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 tempest.common.utils import data_utils from tempest import config import tempest.stress.stressaction as stressaction CONF = config.CONF class VolumeAttachDeleteTest(stressaction.StressAction): def setUp(self, **kwargs): self.image = CONF.compute.image_ref self.flavor = CONF.compute.flavor_ref def run(self): # Step 1: create volume name = data_utils.rand_name("volume") self.logger.info("creating volume: %s" % name) resp, volume = self.manager.volumes_client.create_volume(size=1, display_name= name) assert(resp.status == 200) self.manager.volumes_client.wait_for_volume_status(volume['id'], 'available') self.logger.info("created volume: %s" % volume['id']) # Step 2: create vm instance vm_name = data_utils.rand_name("instance") self.logger.info("creating vm: %s" % vm_name) resp, server = self.manager.servers_client.create_server( vm_name, self.image, self.flavor) server_id = server['id'] assert(resp.status == 202) self.manager.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.logger.info("created vm %s" % server_id) # Step 3: attach volume to vm self.logger.info("attach volume (%s) to vm %s" % (volume['id'], server_id)) resp, body = self.manager.servers_client.attach_volume(server_id, volume['id'], '/dev/vdc') assert(resp.status == 200) self.manager.volumes_client.wait_for_volume_status(volume['id'], 'in-use') self.logger.info("volume (%s) attached to vm %s" % (volume['id'], server_id)) # Step 4: delete vm self.logger.info("deleting vm: %s" % vm_name) resp, _ = self.manager.servers_client.delete_server(server_id) assert(resp.status == 204) self.manager.servers_client.wait_for_server_termination(server_id) self.logger.info("deleted vm: %s" % server_id) # Step 5: delete volume self.logger.info("deleting volume: %s" % volume['id']) resp, _ = self.manager.volumes_client.delete_volume(volume['id']) assert(resp.status == 202) self.manager.volumes_client.wait_for_resource_deletion(volume['id']) self.logger.info("deleted volume: %s" % volume['id']) tempest-2014.1.dev4108.gf22b6cc/tempest/stress/actions/ssh_floating.py0000664000175000017500000001732212332757070025600 0ustar chuckchuck00000000000000# 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 socket import subprocess from tempest.common.utils import data_utils from tempest import config import tempest.stress.stressaction as stressaction import tempest.test CONF = config.CONF class FloatingStress(stressaction.StressAction): # from the scenario manager def ping_ip_address(self, ip_address): cmd = ['ping', '-c1', '-w1', ip_address] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait() success = proc.returncode == 0 return success def tcp_connect_scan(self, addr, port): # like tcp s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((addr, port)) except socket.error as exc: self.logger.info("%s(%s): %s", self.server_id, self.floating['ip'], str(exc)) return False self.logger.info("%s(%s): Connected :)", self.server_id, self.floating['ip']) s.close() return True def check_port_ssh(self): def func(): return self.tcp_connect_scan(self.floating['ip'], 22) if not tempest.test.call_until_true(func, self.check_timeout, self.check_interval): raise RuntimeError("Cannot connect to the ssh port.") def check_icmp_echo(self): self.logger.info("%s(%s): Pinging..", self.server_id, self.floating['ip']) def func(): return self.ping_ip_address(self.floating['ip']) if not tempest.test.call_until_true(func, self.check_timeout, self.check_interval): raise RuntimeError("%s(%s): Cannot ping the machine.", self.server_id, self.floating['ip']) self.logger.info("%s(%s): pong :)", self.server_id, self.floating['ip']) def _create_vm(self): self.name = name = data_utils.rand_name("instance") servers_client = self.manager.servers_client self.logger.info("creating %s" % name) vm_args = self.vm_extra_args.copy() vm_args['security_groups'] = [self.sec_grp] resp, server = servers_client.create_server(name, self.image, self.flavor, **vm_args) self.server_id = server['id'] assert(resp.status == 202) if self.wait_after_vm_create: self.manager.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') def _destroy_vm(self): self.logger.info("deleting %s" % self.server_id) resp, _ = self.manager.servers_client.delete_server(self.server_id) assert(resp.status == 204) # It cannot be 204 if I had to wait.. self.manager.servers_client.wait_for_server_termination(self.server_id) self.logger.info("deleted %s" % self.server_id) def _create_sec_group(self): sec_grp_cli = self.manager.security_groups_client s_name = data_utils.rand_name('sec_grp-') s_description = data_utils.rand_name('desc-') _, self.sec_grp = sec_grp_cli.create_security_group(s_name, s_description) create_rule = sec_grp_cli.create_security_group_rule create_rule(self.sec_grp['id'], 'tcp', 22, 22) create_rule(self.sec_grp['id'], 'icmp', -1, -1) def _destroy_sec_grp(self): sec_grp_cli = self.manager.security_groups_client sec_grp_cli.delete_security_group(self.sec_grp['id']) def _create_floating_ip(self): floating_cli = self.manager.floating_ips_client _, self.floating = floating_cli.create_floating_ip(self.floating_pool) def _destroy_floating_ip(self): cli = self.manager.floating_ips_client cli.delete_floating_ip(self.floating['id']) cli.wait_for_resource_deletion(self.floating['id']) self.logger.info("Deleted Floating IP %s", str(self.floating['ip'])) def setUp(self, **kwargs): self.image = CONF.compute.image_ref self.flavor = CONF.compute.flavor_ref self.vm_extra_args = kwargs.get('vm_extra_args', {}) self.wait_after_vm_create = kwargs.get('wait_after_vm_create', True) self.new_vm = kwargs.get('new_vm', False) self.new_sec_grp = kwargs.get('new_sec_group', False) self.new_floating = kwargs.get('new_floating', False) self.reboot = kwargs.get('reboot', False) self.floating_pool = kwargs.get('floating_pool', None) self.verify = kwargs.get('verify', ('check_port_ssh', 'check_icmp_echo')) self.check_timeout = kwargs.get('check_timeout', 120) self.check_interval = kwargs.get('check_interval', 1) self.wait_for_disassociate = kwargs.get('wait_for_disassociate', True) # allocate floating if not self.new_floating: self._create_floating_ip() # add security group if not self.new_sec_grp: self._create_sec_group() # create vm if not self.new_vm: self._create_vm() def wait_disassociate(self): cli = self.manager.floating_ips_client def func(): _, floating = cli.get_floating_ip_details(self.floating['id']) return floating['instance_id'] is None if not tempest.test.call_until_true(func, self.check_timeout, self.check_interval): raise RuntimeError("IP disassociate timeout!") def run_core(self): cli = self.manager.floating_ips_client cli.associate_floating_ip_to_server(self.floating['ip'], self.server_id) for method in self.verify: m = getattr(self, method) m() cli.disassociate_floating_ip_from_server(self.floating['ip'], self.server_id) if self.wait_for_disassociate: self.wait_disassociate() def run(self): if self.new_sec_grp: self._create_sec_group() if self.new_floating: self._create_floating_ip() if self.new_vm: self._create_vm() if self.reboot: self.manager.servers_client.reboot(self.server_id, 'HARD') self.manager.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') self.run_core() if self.new_vm: self._destroy_vm() if self.new_floating: self._destroy_floating_ip() if self.new_sec_grp: self._destroy_sec_grp() def tearDown(self): if not self.new_vm: self._destroy_vm() if not self.new_floating: self._destroy_floating_ip() if not self.new_sec_grp: self._destroy_sec_grp() tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/0000775000175000017500000000000012332757136021657 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/volume-attach-verify.json0000664000175000017500000000052412332757070026623 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.volume_attach_verify.VolumeVerifyStress", "threads": 1, "use_admin": false, "use_isolated_tenants": false, "kwargs": {"vm_extra_args": {}, "new_volume": true, "new_server": false, "ssh_test_before_attach": false, "enable_ssh_verify": true} } ] tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/server-create-destroy-test.json0000664000175000017500000000026012332757070027760 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.server_create_destroy.ServerCreateDestroyTest", "threads": 8, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} } ] tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/volume-attach-delete-test.json0000664000175000017500000000025612332757070027540 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.volume_attach_delete.VolumeAttachDeleteTest", "threads": 4, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} } ] tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/volume-create-delete-test.json0000664000175000017500000000025612332757070027537 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.volume_create_delete.VolumeCreateDeleteTest", "threads": 4, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} } ] tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/sample-unit-test.json0000664000175000017500000000046012332757070025762 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.unit_test.UnitTest", "threads": 8, "use_admin": false, "use_isolated_tenants": false, "kwargs": {"test_method": "tempest.cli.simple_read_only.test_glance.SimpleReadOnlyGlanceClientTest.test_glance_fake_action", "class_setup_per": "process"} } ] tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/stress-tox-job.json0000664000175000017500000000101412332757070025446 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.server_create_destroy.ServerCreateDestroyTest", "threads": 8, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} }, {"action": "tempest.stress.actions.volume_create_delete.VolumeCreateDeleteTest", "threads": 4, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} }, {"action": "tempest.stress.actions.volume_attach_delete.VolumeAttachDeleteTest", "threads": 2, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} } ] tempest-2014.1.dev4108.gf22b6cc/tempest/stress/etc/ssh_floating.json0000664000175000017500000000101312332757070025222 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.ssh_floating.FloatingStress", "threads": 8, "use_admin": false, "use_isolated_tenants": false, "kwargs": {"vm_extra_args": {}, "new_vm": true, "new_sec_group": true, "new_floating": true, "verify": ["check_icmp_echo", "check_port_ssh"], "check_timeout": 120, "check_interval": 1, "wait_after_vm_create": true, "wait_for_disassociate": true, "reboot": false} } ] tempest-2014.1.dev4108.gf22b6cc/tempest/stress/cleanup.py0000664000175000017500000000756412332757070023116 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2013 Quanta Research Cambridge, 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 tempest import clients from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) def cleanup(): admin_manager = clients.AdminManager() _, body = admin_manager.servers_client.list_servers({"all_tenants": True}) LOG.info("Cleanup::remove %s servers" % len(body['servers'])) for s in body['servers']: try: admin_manager.servers_client.delete_server(s['id']) except Exception: pass for s in body['servers']: try: admin_manager.servers_client.wait_for_server_termination(s['id']) except Exception: pass _, keypairs = admin_manager.keypairs_client.list_keypairs() LOG.info("Cleanup::remove %s keypairs" % len(keypairs)) for k in keypairs: try: admin_manager.keypairs_client.delete_keypair(k['name']) except Exception: pass secgrp_client = admin_manager.security_groups_client _, secgrp = secgrp_client.list_security_groups({"all_tenants": True}) secgrp_del = [grp for grp in secgrp if grp['name'] != 'default'] LOG.info("Cleanup::remove %s Security Group" % len(secgrp_del)) for g in secgrp_del: try: secgrp_client.delete_security_group(g['id']) except Exception: pass _, floating_ips = admin_manager.floating_ips_client.list_floating_ips() LOG.info("Cleanup::remove %s floating ips" % len(floating_ips)) for f in floating_ips: try: admin_manager.floating_ips_client.delete_floating_ip(f['id']) except Exception: pass _, users = admin_manager.identity_client.get_users() LOG.info("Cleanup::remove %s users" % len(users)) for user in users: if user['name'].startswith("stress_user"): admin_manager.identity_client.delete_user(user['id']) _, tenants = admin_manager.identity_client.list_tenants() LOG.info("Cleanup::remove %s tenants" % len(tenants)) for tenant in tenants: if tenant['name'].startswith("stress_tenant"): admin_manager.identity_client.delete_tenant(tenant['id']) # We have to delete snapshots first or # volume deletion may block _, snaps = admin_manager.snapshots_client.\ list_snapshots({"all_tenants": True}) LOG.info("Cleanup::remove %s snapshots" % len(snaps)) for v in snaps: try: admin_manager.snapshots_client.\ wait_for_snapshot_status(v['id'], 'available') admin_manager.snapshots_client.delete_snapshot(v['id']) except Exception: pass for v in snaps: try: admin_manager.snapshots_client.wait_for_resource_deletion(v['id']) except Exception: pass _, vols = admin_manager.volumes_client.list_volumes({"all_tenants": True}) LOG.info("Cleanup::remove %s volumes" % len(vols)) for v in vols: try: admin_manager.volumes_client.\ wait_for_volume_status(v['id'], 'available') admin_manager.volumes_client.delete_volume(v['id']) except Exception: pass for v in vols: try: admin_manager.volumes_client.wait_for_resource_deletion(v['id']) except Exception: pass tempest-2014.1.dev4108.gf22b6cc/tempest/stress/__init__.py0000664000175000017500000000000012332757070023200 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/stress/run_stress.py0000775000175000017500000001144512332757070023672 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2013 Quanta Research Cambridge, 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 argparse import inspect import json import sys from testtools import testsuite try: from unittest import loader except ImportError: # unittest in python 2.6 does not contain loader, so uses unittest2 from unittest2 import loader from tempest.openstack.common import log as logging from tempest.stress import driver LOG = logging.getLogger(__name__) def discover_stress_tests(path="./", filter_attr=None, call_inherited=False): """Discovers all tempest tests and create action out of them """ LOG.info("Start test discovery") tests = [] testloader = loader.TestLoader() list = testloader.discover(path) for func in (testsuite.iterate_tests(list)): attrs = [] try: method_name = getattr(func, '_testMethodName') full_name = "%s.%s.%s" % (func.__module__, func.__class__.__name__, method_name) test_func = getattr(func, method_name) # NOTE(mkoderer): this contains a list of all type attributes attrs = getattr(test_func, "__testtools_attrs") except Exception: next if 'stress' in attrs: if filter_attr is not None and not filter_attr in attrs: continue class_setup_per = getattr(test_func, "st_class_setup_per") action = {'action': "tempest.stress.actions.unit_test.UnitTest", 'kwargs': {"test_method": full_name, "class_setup_per": class_setup_per } } if (not call_inherited and getattr(test_func, "st_allow_inheritance") is not True): class_structure = inspect.getmro(test_func.im_class) if test_func.__name__ not in class_structure[0].__dict__: continue tests.append(action) return tests def main(ns): result = 0 if not ns.all: tests = json.load(open(ns.tests, 'r')) else: tests = discover_stress_tests(filter_attr=ns.type, call_inherited=ns.call_inherited) if ns.serial: for test in tests: step_result = driver.stress_openstack([test], ns.duration, ns.number, ns.stop) # NOTE(mkoderer): we just save the last result code if (step_result != 0): result = step_result if ns.stop: return result else: result = driver.stress_openstack(tests, ns.duration, ns.number, ns.stop) return result parser = argparse.ArgumentParser(description='Run stress tests') parser.add_argument('-d', '--duration', default=300, type=int, help="Duration of test in secs") parser.add_argument('-s', '--serial', action='store_true', help="Trigger running tests serially") parser.add_argument('-S', '--stop', action='store_true', default=False, help="Stop on first error") parser.add_argument('-n', '--number', type=int, help="How often an action is executed for each process") group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-a', '--all', action='store_true', help="Execute all stress tests") parser.add_argument('-T', '--type', help="Filters tests of a certain type (e.g. gate)") parser.add_argument('-i', '--call-inherited', action='store_true', default=False, help="Call also inherited function with stress attribute") group.add_argument('-t', "--tests", nargs='?', help="Name of the file with test description") if __name__ == "__main__": try: sys.exit(main(parser.parse_args())) except Exception: LOG.exception("Failure in the stress test framework") sys.exit(1) tempest-2014.1.dev4108.gf22b6cc/tempest/stress/driver.py0000664000175000017500000002062512332757070022753 0ustar chuckchuck00000000000000# Copyright 2013 Quanta Research Cambridge, 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 multiprocessing import os import signal import time from six import moves from tempest import auth from tempest import clients from tempest.common import ssh from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import importutils from tempest.openstack.common import log as logging from tempest.stress import cleanup CONF = config.CONF LOG = logging.getLogger(__name__) processes = [] def do_ssh(command, host, ssh_user, ssh_key=None): ssh_client = ssh.Client(host, ssh_user, key_filename=ssh_key) try: return ssh_client.exec_command(command) except exceptions.SSHExecCommandFailed: LOG.error('do_ssh raise exception. command:%s, host:%s.' % (command, host)) return None def _get_compute_nodes(controller, ssh_user, ssh_key=None): """ Returns a list of active compute nodes. List is generated by running nova-manage on the controller. """ nodes = [] cmd = "nova-manage service list | grep ^nova-compute" output = do_ssh(cmd, controller, ssh_user, ssh_key) if not output: return nodes # For example: nova-compute xg11eth0 nova enabled :-) 2011-10-31 18:57:46 # This is fragile but there is, at present, no other way to get this info. for line in output.split('\n'): words = line.split() if len(words) > 0 and words[4] == ":-)": nodes.append(words[1]) return nodes def _has_error_in_logs(logfiles, nodes, ssh_user, ssh_key=None, stop_on_error=False): """ Detect errors in the nova log files on the controller and compute nodes. """ grep = 'egrep "ERROR|TRACE" %s' % logfiles ret = False for node in nodes: errors = do_ssh(grep, node, ssh_user, ssh_key) if len(errors) > 0: LOG.error('%s: %s' % (node, errors)) ret = True if stop_on_error: break return ret def sigchld_handler(signalnum, frame): """ Signal handler (only active if stop_on_error is True). """ for process in processes: if (not process['process'].is_alive() and process['process'].exitcode != 0): signal.signal(signalnum, signal.SIG_DFL) terminate_all_processes() break def terminate_all_processes(check_interval=20): """ Goes through the process list and terminates all child processes. """ LOG.info("Stopping all processes.") for process in processes: if process['process'].is_alive(): try: process['process'].terminate() except Exception: pass time.sleep(check_interval) for process in processes: if process['process'].is_alive(): try: pid = process['process'].pid LOG.warn("Process %d hangs. Send SIGKILL." % pid) os.kill(pid, signal.SIGKILL) except Exception: pass process['process'].join() def stress_openstack(tests, duration, max_runs=None, stop_on_error=False): """ Workload driver. Executes an action function against a nova-cluster. """ admin_manager = clients.AdminManager() ssh_user = CONF.stress.target_ssh_user ssh_key = CONF.stress.target_private_key_path logfiles = CONF.stress.target_logfiles log_check_interval = int(CONF.stress.log_check_interval) default_thread_num = int(CONF.stress.default_thread_number_per_action) if logfiles: controller = CONF.stress.target_controller computes = _get_compute_nodes(controller, ssh_user, ssh_key) for node in computes: do_ssh("rm -f %s" % logfiles, node, ssh_user, ssh_key) for test in tests: if test.get('use_admin', False): manager = admin_manager else: manager = clients.Manager() for p_number in moves.xrange(test.get('threads', default_thread_num)): if test.get('use_isolated_tenants', False): username = data_utils.rand_name("stress_user") tenant_name = data_utils.rand_name("stress_tenant") password = "pass" identity_client = admin_manager.identity_client _, tenant = identity_client.create_tenant(name=tenant_name) identity_client.create_user(username, password, tenant['id'], "email") creds = auth.get_credentials(username=username, password=password, tenant_name=tenant_name) manager = clients.Manager(credentials=creds) test_obj = importutils.import_class(test['action']) test_run = test_obj(manager, max_runs, stop_on_error) kwargs = test.get('kwargs', {}) test_run.setUp(**dict(kwargs.iteritems())) LOG.debug("calling Target Object %s" % test_run.__class__.__name__) mp_manager = multiprocessing.Manager() shared_statistic = mp_manager.dict() shared_statistic['runs'] = 0 shared_statistic['fails'] = 0 p = multiprocessing.Process(target=test_run.execute, args=(shared_statistic,)) process = {'process': p, 'p_number': p_number, 'action': test_run.action, 'statistic': shared_statistic} processes.append(process) p.start() if stop_on_error: # NOTE(mkoderer): only the parent should register the handler signal.signal(signal.SIGCHLD, sigchld_handler) end_time = time.time() + duration had_errors = False try: while True: if max_runs is None: remaining = end_time - time.time() if remaining <= 0: break else: remaining = log_check_interval all_proc_term = True for process in processes: if process['process'].is_alive(): all_proc_term = False break if all_proc_term: break time.sleep(min(remaining, log_check_interval)) if stop_on_error: if any([True for proc in processes if proc['statistic']['fails'] > 0]): break if not logfiles: continue if _has_error_in_logs(logfiles, computes, ssh_user, ssh_key, stop_on_error): had_errors = True break except KeyboardInterrupt: LOG.warning("Interrupted, going to print statistics and exit ...") if stop_on_error: signal.signal(signal.SIGCHLD, signal.SIG_DFL) terminate_all_processes() sum_fails = 0 sum_runs = 0 LOG.info("Statistics (per process):") for process in processes: if process['statistic']['fails'] > 0: had_errors = True sum_runs += process['statistic']['runs'] sum_fails += process['statistic']['fails'] LOG.info(" Process %d (%s): Run %d actions (%d failed)" % (process['p_number'], process['action'], process['statistic']['runs'], process['statistic']['fails'])) LOG.info("Summary:") LOG.info("Run %d actions (%d failed)" % (sum_runs, sum_fails)) if not had_errors and CONF.stress.full_clean_stack: LOG.info("cleaning up") cleanup.cleanup() if had_errors: return 1 else: return 0 tempest-2014.1.dev4108.gf22b6cc/tempest/stress/README.rst0000664000175000017500000000431612332757070022574 0ustar chuckchuck00000000000000Tempest Field Guide to Stress Tests =================================== OpenStack is a distributed, asynchronous system that is prone to race condition bugs. These bugs will not be easily found during functional testing but will be encountered by users in large deployments in a way that is hard to debug. The stress test tries to cause these bugs to happen in a more controlled environment. Environment ----------- This particular framework assumes your working Nova cluster understands Nova API 2.0. The stress tests can read the logs from the cluster. To enable this you have to provide the hostname to call 'nova-manage' and the private key and user name for ssh to the cluster in the [stress] section of tempest.conf. You also need to provide the location of the log files: target_logfiles = "regexp to all log files to be checked for errors" target_private_key_path = "private ssh key for controller and log file nodes" target_ssh_user = "username for controller and log file nodes" target_controller = "hostname or ip of controller node (for nova-manage) log_check_interval = "time between checking logs for errors (default 60s)" To activate logging on your console please make sure that you activate `use_stderr` in tempest.conf or use the default `logging.conf.sample` file. Running default stress test set ------------------------------- The stress test framework can automatically discover test inside the tempest test suite. All test flag with the `@stresstest` decorator will be executed. In order to use this discovery you have to be in the tempest root directory and execute the following: tempest/stress/run_stress.py -a -d 30 Running the sample test ----------------------- To test installation, do the following (from the tempest/stress directory): ./run_stress.py -t etc/server-create-destroy-test.json -d 30 This sample test tries to create a few VMs and kill a few VMs. Additional Tools ---------------- Sometimes the tests don't finish, or there are failures. In these cases, you may want to clean out the nova cluster. We have provided some scripts to do this in the ``tools`` subdirectory. You can use the following script to destroy any keypairs, floating ips, and servers: tempest/stress/tools/cleanup.py tempest-2014.1.dev4108.gf22b6cc/tempest/manager.py0000664000175000017500000000470212332757070021545 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import auth from tempest import config from tempest import exceptions CONF = config.CONF class Manager(object): """ Base manager class Manager objects are responsible for providing a configuration object and a client object for a test case to use in performing actions. """ def __init__(self, credentials=None): """ We allow overriding of the credentials used within the various client classes managed by the Manager object. Left as None, the standard username/password/tenant_name[/domain_name] is used. :param credentials: Override of the credentials """ self.auth_version = CONF.identity.auth_version if credentials is None: self.credentials = auth.get_default_credentials('user') else: self.credentials = credentials # Check if passed or default credentials are valid if not self.credentials.is_valid(): raise exceptions.InvalidCredentials() # Creates an auth provider for the credentials self.auth_provider = self.get_auth_provider(self.credentials) # FIXME(andreaf) unused self.client_attr_names = [] @classmethod def get_auth_provider_class(cls, auth_version): if auth_version == 'v2': return auth.KeystoneV2AuthProvider else: return auth.KeystoneV3AuthProvider def get_auth_provider(self, credentials): if credentials is None: raise exceptions.InvalidCredentials( 'Credentials must be specified') auth_provider_class = self.get_auth_provider_class(self.auth_version) return auth_provider_class( client_type=getattr(self, 'client_type', None), interface=getattr(self, 'interface', None), credentials=credentials) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/0000775000175000017500000000000012332757136021753 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/0000775000175000017500000000000012332757136022716 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_s3_objects.py0000664000175000017500000000357212332757070026371 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import boto.s3.key from tempest.common.utils import data_utils from tempest import test from tempest.thirdparty.boto import test as boto_test class S3BucketsTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(S3BucketsTest, cls).setUpClass() cls.client = cls.os.s3_client @test.attr(type='smoke') def test_create_get_delete_object(self): # S3 Create, get and delete object bucket_name = data_utils.rand_name("s3bucket-") object_name = data_utils.rand_name("s3object-") content = 'x' * 42 bucket = self.client.create_bucket(bucket_name) self.addResourceCleanUp(self.destroy_bucket, self.client.connection_data, bucket_name) self.assertTrue(bucket.name == bucket_name) with contextlib.closing(boto.s3.key.Key(bucket)) as key: key.key = object_name key.set_contents_from_string(content) readback = key.get_contents_as_string() self.assertTrue(readback == content) bucket.delete_key(key) self.assertBotoError(self.s3_error_code.client.NoSuchKey, key.get_contents_as_string) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_s3_buckets.py0000664000175000017500000000327512332757070026400 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import data_utils from tempest import test from tempest.thirdparty.boto import test as boto_test class S3BucketsTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(S3BucketsTest, cls).setUpClass() cls.client = cls.os.s3_client @test.skip_because(bug="1076965") @test.attr(type='smoke') def test_create_and_get_delete_bucket(self): # S3 Create, get and delete bucket bucket_name = data_utils.rand_name("s3bucket-") cleanup_key = self.addResourceCleanUp(self.client.delete_bucket, bucket_name) bucket = self.client.create_bucket(bucket_name) self.assertTrue(bucket.name == bucket_name) bucket = self.client.get_bucket(bucket_name) self.assertTrue(bucket.name == bucket_name) self.client.delete_bucket(bucket_name) self.assertBotoError(self.s3_error_code.client.NoSuchBucket, self.client.get_bucket, bucket_name) self.cancelResourceCleanUp(cleanup_key) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_ec2_keys.py0000664000175000017500000000537112332757070026036 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import data_utils from tempest import test from tempest.thirdparty.boto import test as boto_test def compare_key_pairs(a, b): return (a.name == b.name and a.fingerprint == b.fingerprint) class EC2KeysTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(EC2KeysTest, cls).setUpClass() cls.client = cls.os.ec2api_client cls.ec = cls.ec2_error_code # TODO(afazekas): merge create, delete, get test cases @test.attr(type='smoke') def test_create_ec2_keypair(self): # EC2 create KeyPair key_name = data_utils.rand_name("keypair-") self.addResourceCleanUp(self.client.delete_key_pair, key_name) keypair = self.client.create_key_pair(key_name) self.assertTrue(compare_key_pairs(keypair, self.client.get_key_pair(key_name))) @test.skip_because(bug="1072318") @test.attr(type='smoke') def test_delete_ec2_keypair(self): # EC2 delete KeyPair key_name = data_utils.rand_name("keypair-") self.client.create_key_pair(key_name) self.client.delete_key_pair(key_name) self.assertIsNone(self.client.get_key_pair(key_name)) @test.attr(type='smoke') def test_get_ec2_keypair(self): # EC2 get KeyPair key_name = data_utils.rand_name("keypair-") self.addResourceCleanUp(self.client.delete_key_pair, key_name) keypair = self.client.create_key_pair(key_name) self.assertTrue(compare_key_pairs(keypair, self.client.get_key_pair(key_name))) @test.attr(type='smoke') def test_duplicate_ec2_keypair(self): # EC2 duplicate KeyPair key_name = data_utils.rand_name("keypair-") self.addResourceCleanUp(self.client.delete_key_pair, key_name) keypair = self.client.create_key_pair(key_name) self.assertBotoError(self.ec.client.InvalidKeyPair.Duplicate, self.client.create_key_pair, key_name) self.assertTrue(compare_key_pairs(keypair, self.client.get_key_pair(key_name))) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_ec2_network.py0000664000175000017500000000342212332757070026547 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import test from tempest.thirdparty.boto import test as boto_test class EC2NetworkTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(EC2NetworkTest, cls).setUpClass() cls.client = cls.os.ec2api_client # Note(afazekas): these tests for things duable without an instance @test.skip_because(bug="1080406") @test.attr(type='smoke') def test_disassociate_not_associated_floating_ip(self): # EC2 disassociate not associated floating ip ec2_codes = self.ec2_error_code address = self.client.allocate_address() public_ip = address.public_ip rcuk = self.addResourceCleanUp(self.client.release_address, public_ip) addresses_get = self.client.get_all_addresses(addresses=(public_ip,)) self.assertEqual(len(addresses_get), 1) self.assertEqual(addresses_get[0].public_ip, public_ip) self.assertBotoError(ec2_codes.client.InvalidAssociationID.NotFound, address.disassociate) self.client.release_address(public_ip) self.cancelResourceCleanUp(rcuk) self.assertAddressReleasedWait(address) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/__init__.py0000664000175000017500000000000012332757070025012 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_s3_ec2_images.py0000664000175000017500000001307512332757070026735 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from tempest.common.utils import data_utils from tempest import config from tempest import test from tempest.thirdparty.boto import test as boto_test from tempest.thirdparty.boto.utils import s3 CONF = config.CONF class S3ImagesTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(S3ImagesTest, cls).setUpClass() if not cls.conclusion['A_I_IMAGES_READY']: raise cls.skipException("".join(("EC2 ", cls.__name__, ": requires ami/aki/ari manifest"))) cls.s3_client = cls.os.s3_client cls.images_client = cls.os.ec2api_client cls.materials_path = CONF.boto.s3_materials_path cls.ami_manifest = CONF.boto.ami_manifest cls.aki_manifest = CONF.boto.aki_manifest cls.ari_manifest = CONF.boto.ari_manifest cls.ami_path = cls.materials_path + os.sep + cls.ami_manifest cls.aki_path = cls.materials_path + os.sep + cls.aki_manifest cls.ari_path = cls.materials_path + os.sep + cls.ari_manifest cls.bucket_name = data_utils.rand_name("bucket-") bucket = cls.s3_client.create_bucket(cls.bucket_name) cls.addResourceCleanUp(cls.destroy_bucket, cls.s3_client.connection_data, cls.bucket_name) s3.s3_upload_dir(bucket, cls.materials_path) @test.attr(type='smoke') def test_register_get_deregister_ami_image(self): # Register and deregister ami image image = {"name": data_utils.rand_name("ami-name-"), "location": self.bucket_name + "/" + self.ami_manifest, "type": "ami"} image["image_id"] = self.images_client.register_image( name=image["name"], image_location=image["location"]) # NOTE(afazekas): delete_snapshot=True might trigger boto lib? bug image["cleanUp"] = self.addResourceCleanUp( self.images_client.deregister_image, image["image_id"]) self.assertEqual(image["image_id"][0:3], image["type"]) retrieved_image = self.images_client.get_image(image["image_id"]) self.assertTrue(retrieved_image.name == image["name"]) self.assertTrue(retrieved_image.id == image["image_id"]) if retrieved_image.state != "available": self.assertImageStateWait(retrieved_image, "available") self.images_client.deregister_image(image["image_id"]) self.assertNotIn(image["image_id"], str( self.images_client.get_all_images())) self.cancelResourceCleanUp(image["cleanUp"]) def test_register_get_deregister_aki_image(self): # Register and deregister aki image image = {"name": data_utils.rand_name("aki-name-"), "location": self.bucket_name + "/" + self.aki_manifest, "type": "aki"} image["image_id"] = self.images_client.register_image( name=image["name"], image_location=image["location"]) image["cleanUp"] = self.addResourceCleanUp( self.images_client.deregister_image, image["image_id"]) self.assertEqual(image["image_id"][0:3], image["type"]) retrieved_image = self.images_client.get_image(image["image_id"]) self.assertTrue(retrieved_image.name == image["name"]) self.assertTrue(retrieved_image.id == image["image_id"]) self.assertIn(retrieved_image.state, self.valid_image_state) if retrieved_image.state != "available": self.assertImageStateWait(retrieved_image, "available") self.images_client.deregister_image(image["image_id"]) self.assertNotIn(image["image_id"], str( self.images_client.get_all_images())) self.cancelResourceCleanUp(image["cleanUp"]) def test_register_get_deregister_ari_image(self): # Register and deregister ari image image = {"name": data_utils.rand_name("ari-name-"), "location": "/" + self.bucket_name + "/" + self.ari_manifest, "type": "ari"} image["image_id"] = self.images_client.register_image( name=image["name"], image_location=image["location"]) image["cleanUp"] = self.addResourceCleanUp( self.images_client.deregister_image, image["image_id"]) self.assertEqual(image["image_id"][0:3], image["type"]) retrieved_image = self.images_client.get_image(image["image_id"]) self.assertIn(retrieved_image.state, self.valid_image_state) if retrieved_image.state != "available": self.assertImageStateWait(retrieved_image, "available") self.assertIn(retrieved_image.state, self.valid_image_state) self.assertTrue(retrieved_image.name == image["name"]) self.assertTrue(retrieved_image.id == image["image_id"]) self.images_client.deregister_image(image["image_id"]) self.cancelResourceCleanUp(image["cleanUp"]) # TODO(afazekas): less copy-paste style tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test.py0000664000175000017500000006545412332757070024262 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import logging as orig_logging import os import re import six import urlparse import boto from boto import ec2 from boto import exception from boto import s3 import keystoneclient.exceptions import tempest.clients from tempest.common.utils import file_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging import tempest.test from tempest.thirdparty.boto.utils import wait CONF = config.CONF LOG = logging.getLogger(__name__) def decision_maker(): A_I_IMAGES_READY = True # ari,ami,aki S3_CAN_CONNECT_ERROR = None EC2_CAN_CONNECT_ERROR = None secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system id_matcher = re.compile("[A-Za-z0-9]{20,}") def all_read(*args): return all(map(file_utils.have_effective_read_access, args)) materials_path = CONF.boto.s3_materials_path ami_path = materials_path + os.sep + CONF.boto.ami_manifest aki_path = materials_path + os.sep + CONF.boto.aki_manifest ari_path = materials_path + os.sep + CONF.boto.ari_manifest A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path) boto_logger = logging.getLogger('boto') level = boto_logger.logger.level boto_logger.logger.setLevel(orig_logging.CRITICAL) # suppress logging # for these def _cred_sub_check(connection_data): if not id_matcher.match(connection_data["aws_access_key_id"]): raise Exception("Invalid AWS access Key") if not secret_matcher.match(connection_data["aws_secret_access_key"]): raise Exception("Invalid AWS secret Key") raise Exception("Unknown (Authentication?) Error") openstack = tempest.clients.Manager() try: if urlparse.urlparse(CONF.boto.ec2_url).hostname is None: raise Exception("Failed to get hostname from the ec2_url") ec2client = openstack.ec2api_client try: ec2client.get_all_regions() except exception.BotoServerError as exc: if exc.error_code is None: raise Exception("EC2 target does not looks EC2 service") _cred_sub_check(ec2client.connection_data) except keystoneclient.exceptions.Unauthorized: EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\ " faild to get them even by keystoneclient" except Exception as exc: EC2_CAN_CONNECT_ERROR = str(exc) try: if urlparse.urlparse(CONF.boto.s3_url).hostname is None: raise Exception("Failed to get hostname from the s3_url") s3client = openstack.s3_client try: s3client.get_bucket("^INVALID*#()@INVALID.") except exception.BotoServerError as exc: if exc.status == 403: _cred_sub_check(s3client.connection_data) except Exception as exc: S3_CAN_CONNECT_ERROR = str(exc) except keystoneclient.exceptions.Unauthorized: S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\ " faild to get them even by keystoneclient" boto_logger.logger.setLevel(level) return {'A_I_IMAGES_READY': A_I_IMAGES_READY, 'S3_CAN_CONNECT_ERROR': S3_CAN_CONNECT_ERROR, 'EC2_CAN_CONNECT_ERROR': EC2_CAN_CONNECT_ERROR} class BotoExceptionMatcher(object): STATUS_RE = r'[45]\d\d' CODE_RE = '.*' # regexp makes sense in group match def match(self, exc): """:returns: Retruns with an error string if not matches, returns with None when matches. """ if not isinstance(exc, exception.BotoServerError): return "%r not an BotoServerError instance" % exc LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code) if re.match(self.STATUS_RE, str(exc.status)) is None: return ("Status code (%s) does not match" "the expected re pattern \"%s\"" % (exc.status, self.STATUS_RE)) if re.match(self.CODE_RE, str(exc.error_code)) is None: return ("Error code (%s) does not match" + "the expected re pattern \"%s\"") %\ (exc.error_code, self.CODE_RE) return None class ClientError(BotoExceptionMatcher): STATUS_RE = r'4\d\d' class ServerError(BotoExceptionMatcher): STATUS_RE = r'5\d\d' def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher): """ Usable for adding an ExceptionMatcher(s) into the exception tree. The not leaf elements does wildcard match """ # in error_code just literal and '.' characters expected if not isinstance(error_data, six.string_types): (error_code, status_code) = map(str, error_data) else: status_code = None error_code = error_data parts = error_code.split('.') basematch = "" num_parts = len(parts) max_index = num_parts - 1 add_cls = error_cls for i_part in six.moves.xrange(num_parts): part = parts[i_part] leaf = i_part == max_index if not leaf: match = basematch + part + "[.].*" else: match = basematch + part basematch += part + "[.]" if not hasattr(add_cls, part): cls_dict = {"CODE_RE": match} if leaf and status_code is not None: cls_dict["STATUS_RE"] = status_code cls = type(part, (base, ), cls_dict) setattr(add_cls, part, cls()) add_cls = cls elif leaf: raise LookupError("Tries to redefine an error code \"%s\"" % part) else: add_cls = getattr(add_cls, part) # TODO(afazekas): classmethod handling def friendly_function_name_simple(call_able): name = "" if hasattr(call_able, "im_class"): name += call_able.im_class.__name__ + "." name += call_able.__name__ return name def friendly_function_call_str(call_able, *args, **kwargs): string = friendly_function_name_simple(call_able) string += "(" + ", ".join(map(str, args)) if len(kwargs): if len(args): string += ", " string += ", ".join("=".join(map(str, (key, value))) for (key, value) in kwargs.items()) return string + ")" class BotoTestCase(tempest.test.BaseTestCase): """Recommended to use as base class for boto related test.""" @classmethod def setUpClass(cls): super(BotoTestCase, cls).setUpClass() cls.conclusion = decision_maker() cls.os = cls.get_client_manager() # The trash contains cleanup functions and paramaters in tuples # (function, *args, **kwargs) cls._resource_trash_bin = {} cls._sequence = -1 if (hasattr(cls, "EC2") and cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None): raise cls.skipException("EC2 " + cls.__name__ + ": " + cls.conclusion['EC2_CAN_CONNECT_ERROR']) if (hasattr(cls, "S3") and cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None): raise cls.skipException("S3 " + cls.__name__ + ": " + cls.conclusion['S3_CAN_CONNECT_ERROR']) @classmethod def addResourceCleanUp(cls, function, *args, **kwargs): """Adds CleanUp callable, used by tearDownClass. Recommended to a use (deep)copy on the mutable args. """ cls._sequence = cls._sequence + 1 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs) return cls._sequence @classmethod def cancelResourceCleanUp(cls, key): """Cancel Clean up request.""" del cls._resource_trash_bin[key] # TODO(afazekas): Add "with" context handling def assertBotoError(self, excMatcher, callableObj, *args, **kwargs): """Example usage: self.assertBotoError(self.ec2_error_code.client. InvalidKeyPair.Duplicate, self.client.create_keypair, key_name) """ try: callableObj(*args, **kwargs) except exception.BotoServerError as exc: error_msg = excMatcher.match(exc) if error_msg is not None: raise self.failureException, error_msg else: raise self.failureException, "BotoServerError not raised" @classmethod def tearDownClass(cls): """Calls the callables added by addResourceCleanUp, when you overwrite this function don't forget to call this too. """ fail_count = 0 trash_keys = sorted(cls._resource_trash_bin, reverse=True) for key in trash_keys: (function, pos_args, kw_args) = cls._resource_trash_bin[key] try: func_name = friendly_function_call_str(function, *pos_args, **kw_args) LOG.debug("Cleaning up: %s" % func_name) function(*pos_args, **kw_args) except BaseException: fail_count += 1 LOG.exception("Cleanup failed %s" % func_name) finally: del cls._resource_trash_bin[key] cls.clear_isolated_creds() super(BotoTestCase, cls).tearDownClass() # NOTE(afazekas): let the super called even on exceptions # The real exceptions already logged, if the super throws another, # does not causes hidden issues if fail_count: raise exceptions.TearDownException(num=fail_count) ec2_error_code = BotoExceptionMatcher() # InsufficientInstanceCapacity can be both server and client error ec2_error_code.server = ServerError() ec2_error_code.client = ClientError() s3_error_code = BotoExceptionMatcher() s3_error_code.server = ServerError() s3_error_code.client = ClientError() valid_image_state = set(('available', 'pending', 'failed')) # NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have # a good mapping, because it uses memory, but not really a running machine valid_instance_state = set(('pending', 'running', 'shutting-down', 'terminated', 'stopping', 'stopped', 'paused')) valid_volume_status = set(('creating', 'available', 'in-use', 'deleting', 'deleted', 'error')) valid_snapshot_status = set(('pending', 'completed', 'error')) gone_set = set(('_GONE',)) @classmethod def get_lfunction_gone(cls, obj): """If the object is instance of a well know type returns back with with the correspoding function otherwise it assumes the obj itself is the function. """ ec = cls.ec2_error_code if isinstance(obj, ec2.instance.Instance): colusure_matcher = ec.client.InvalidInstanceID.NotFound status_attr = "state" elif isinstance(obj, ec2.image.Image): colusure_matcher = ec.client.InvalidAMIID.NotFound status_attr = "state" elif isinstance(obj, ec2.snapshot.Snapshot): colusure_matcher = ec.client.InvalidSnapshot.NotFound status_attr = "status" elif isinstance(obj, ec2.volume.Volume): colusure_matcher = ec.client.InvalidVolume.NotFound status_attr = "status" else: return obj def _status(): try: obj.update(validate=True) except ValueError: return "_GONE" except exception.EC2ResponseError as exc: if colusure_matcher.match(exc) is None: return "_GONE" else: raise return getattr(obj, status_attr) return _status def state_wait_gone(self, lfunction, final_set, valid_set): if not isinstance(final_set, set): final_set = set((final_set,)) final_set |= self.gone_set lfunction = self.get_lfunction_gone(lfunction) state = wait.state_wait(lfunction, final_set, valid_set) self.assertIn(state, valid_set | self.gone_set) return state def waitImageState(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_image_state) def waitInstanceState(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_instance_state) def waitSnapshotStatus(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_snapshot_status) def waitVolumeStatus(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_volume_status) def assertImageStateWait(self, lfunction, wait_for): state = self.waitImageState(lfunction, wait_for) self.assertIn(state, wait_for) def assertInstanceStateWait(self, lfunction, wait_for): state = self.waitInstanceState(lfunction, wait_for) self.assertIn(state, wait_for) def assertVolumeStatusWait(self, lfunction, wait_for): state = self.waitVolumeStatus(lfunction, wait_for) self.assertIn(state, wait_for) def assertSnapshotStatusWait(self, lfunction, wait_for): state = self.waitSnapshotStatus(lfunction, wait_for) self.assertIn(state, wait_for) def assertAddressDissasociatedWait(self, address): def _disassociate(): cli = self.ec2_client addresses = cli.get_all_addresses(addresses=(address.public_ip,)) if len(addresses) != 1: return "INVALID" if addresses[0].instance_id: LOG.info("%s associated to %s", address.public_ip, addresses[0].instance_id) return "ASSOCIATED" return "DISASSOCIATED" state = wait.state_wait(_disassociate, "DISASSOCIATED", set(("ASSOCIATED", "DISASSOCIATED"))) self.assertEqual(state, "DISASSOCIATED") def assertAddressReleasedWait(self, address): def _address_delete(): # NOTE(afazekas): the filter gives back IP # even if it is not associated to my tenant if (address.public_ip not in map(lambda a: a.public_ip, self.ec2_client.get_all_addresses())): return "DELETED" return "NOTDELETED" state = wait.state_wait(_address_delete, "DELETED") self.assertEqual(state, "DELETED") def assertReSearch(self, regexp, string): if re.search(regexp, string) is None: raise self.failureException("regexp: '%s' not found in '%s'" % (regexp, string)) def assertNotReSearch(self, regexp, string): if re.search(regexp, string) is not None: raise self.failureException("regexp: '%s' found in '%s'" % (regexp, string)) def assertReMatch(self, regexp, string): if re.match(regexp, string) is None: raise self.failureException("regexp: '%s' not matches on '%s'" % (regexp, string)) def assertNotReMatch(self, regexp, string): if re.match(regexp, string) is not None: raise self.failureException("regexp: '%s' matches on '%s'" % (regexp, string)) @classmethod def destroy_bucket(cls, connection_data, bucket): """Destroys the bucket and its content, just for teardown.""" exc_num = 0 try: with contextlib.closing( boto.connect_s3(**connection_data)) as conn: if isinstance(bucket, basestring): bucket = conn.lookup(bucket) assert isinstance(bucket, s3.bucket.Bucket) for obj in bucket.list(): try: bucket.delete_key(obj.key) obj.close() except BaseException: LOG.exception("Failed to delete key %s " % obj.key) exc_num += 1 conn.delete_bucket(bucket) except BaseException: LOG.exception("Failed to destroy bucket %s " % bucket) exc_num += 1 if exc_num: raise exceptions.TearDownException(num=exc_num) @classmethod def destroy_reservation(cls, reservation): """Terminate instances in a reservation, just for teardown.""" exc_num = 0 def _instance_state(): try: instance.update(validate=True) except ValueError: return "_GONE" except exception.EC2ResponseError as exc: if cls.ec2_error_code.\ client.InvalidInstanceID.NotFound.match(exc) is None: return "_GONE" # NOTE(afazekas): incorrect code, # but the resource must be destoreyd if exc.error_code == "InstanceNotFound": return "_GONE" return instance.state for instance in reservation.instances: try: instance.terminate() wait.re_search_wait(_instance_state, "_GONE") except BaseException: LOG.exception("Failed to terminate instance %s " % instance) exc_num += 1 if exc_num: raise exceptions.TearDownException(num=exc_num) # NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult # to write better teardown @classmethod def destroy_security_group_wait(cls, group): """Delete group. Use just for teardown! """ # NOTE(afazekas): should wait/try until all related instance terminates group.delete() @classmethod def destroy_volume_wait(cls, volume): """Delete volume, tryies to detach first. Use just for teardown! """ exc_num = 0 snaps = volume.snapshots() if len(snaps): LOG.critical("%s Volume has %s snapshot(s)", volume.id, map(snaps.id, snaps)) # NOTE(afazekas): detaching/attching not valid EC2 status def _volume_state(): volume.update(validate=True) try: if volume.status != "available": volume.detach(force=True) except BaseException: LOG.exception("Failed to detach volume %s" % volume) # exc_num += 1 "nonlocal" not in python2 return volume.status try: wait.re_search_wait(_volume_state, "available") # not validates status LOG.info(_volume_state()) volume.delete() except BaseException: LOG.exception("Failed to delete volume %s" % volume) exc_num += 1 if exc_num: raise exceptions.TearDownException(num=exc_num) @classmethod def destroy_snapshot_wait(cls, snapshot): """delete snaphot, wait until not exists.""" snapshot.delete() def _update(): snapshot.update(validate=True) wait.wait_exception(_update) # you can specify tuples if you want to specify the status pattern for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure', 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation', 'DiskImageSizeTooLarge', 'FilterLimitExceeded', 'Gateway.NotAttached', 'IdempotentParameterMismatch', 'IncorrectInstanceState', 'IncorrectState', 'InstanceLimitExceeded', 'InsufficientInstanceCapacity', 'InsufficientReservedInstancesCapacity', 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue', 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound', 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound', 'InvalidAttachment.NotFound', 'InvalidConversionTaskId', 'InvalidCustomerGateway.DuplicateIpAddress', 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse', 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat', 'InvalidFilter', 'InvalidGatewayID.NotFound', 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed', 'InvalidGroup.InUse', 'InvalidGroup.NotFound', 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed', 'InvalidInstanceID.NotFound', 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse', 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format', 'InvalidKeyPair.NotFound', 'InvalidManifest', 'InvalidNetworkAclEntry.NotFound', 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination', 'InvalidParameterValue', 'InvalidPermission.Duplicate', 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed', 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound', 'InvalidRouteTableID.NotFound', 'InvalidSecurity.RequestHasExpired', 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound', 'InvalidUserID.Malformed', 'InvalidReservedInstancesId', 'InvalidReservedInstancesOfferingId', 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate', 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch', 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound', 'InvalidVpnConnectionID.NotFound', 'InvalidVpnGatewayID.NotFound', 'InvalidZone.NotFound', 'LegacySecurityGroup', 'MissingParameter', 'NetworkAclEntryAlreadyExists', 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded', 'NonEBSInstance', 'PendingSnapshotLimitExceeded', 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded', 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated', 'ResourceLimitExceeded', 'RouteAlreadyExists', 'RouteLimitExceeded', 'RouteTableLimitExceeded', 'RulesPerSecurityGroupLimitExceeded', 'SecurityGroupLimitExceeded', 'SecurityGroupsPerInstanceLimitExceeded', 'SnapshotLimitExceeded', 'SubnetLimitExceeded', 'UnknownParameter', 'UnsupportedOperation', 'VolumeLimitExceeded', 'VpcLimitExceeded', 'VpnConnectionLimitExceeded', 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'): _add_matcher_class(BotoTestCase.ec2_error_code.client, code, base=ClientError) for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity', 'InsufficientReservedInstanceCapacity', 'InternalError', 'Unavailable'): _add_matcher_class(BotoTestCase.ec2_error_code.server, code, base=ServerError) for code in (('AccessDenied', 403), ('AccountProblem', 403), ('AmbiguousGrantByEmailAddress', 400), ('BadDigest', 400), ('BucketAlreadyExists', 409), ('BucketAlreadyOwnedByYou', 409), ('BucketNotEmpty', 409), ('CredentialsNotSupported', 400), ('CrossLocationLoggingProhibited', 403), ('EntityTooSmall', 400), ('EntityTooLarge', 400), ('ExpiredToken', 400), ('IllegalVersioningConfigurationException', 400), ('IncompleteBody', 400), ('IncorrectNumberOfFilesInPostRequest', 400), ('InlineDataTooLarge', 400), ('InvalidAccessKeyId', 403), 'InvalidAddressingHeader', ('InvalidArgument', 400), ('InvalidBucketName', 400), ('InvalidBucketState', 409), ('InvalidDigest', 400), ('InvalidLocationConstraint', 400), ('InvalidPart', 400), ('InvalidPartOrder', 400), ('InvalidPayer', 403), ('InvalidPolicyDocument', 400), ('InvalidRange', 416), ('InvalidRequest', 400), ('InvalidSecurity', 403), ('InvalidSOAPRequest', 400), ('InvalidStorageClass', 400), ('InvalidTargetBucketForLogging', 400), ('InvalidToken', 400), ('InvalidURI', 400), ('KeyTooLong', 400), ('MalformedACLError', 400), ('MalformedPOSTRequest', 400), ('MalformedXML', 400), ('MaxMessageLengthExceeded', 400), ('MaxPostPreDataLengthExceededError', 400), ('MetadataTooLarge', 400), ('MethodNotAllowed', 405), ('MissingAttachment'), ('MissingContentLength', 411), ('MissingRequestBodyError', 400), ('MissingSecurityElement', 400), ('MissingSecurityHeader', 400), ('NoLoggingStatusForKey', 400), ('NoSuchBucket', 404), ('NoSuchKey', 404), ('NoSuchLifecycleConfiguration', 404), ('NoSuchUpload', 404), ('NoSuchVersion', 404), ('NotSignedUp', 403), ('NotSuchBucketPolicy', 404), ('OperationAborted', 409), ('PermanentRedirect', 301), ('PreconditionFailed', 412), ('Redirect', 307), ('RequestIsNotMultiPartContent', 400), ('RequestTimeout', 400), ('RequestTimeTooSkewed', 403), ('RequestTorrentOfBucketError', 400), ('SignatureDoesNotMatch', 403), ('TemporaryRedirect', 307), ('TokenRefreshRequired', 400), ('TooManyBuckets', 400), ('UnexpectedContent', 400), ('UnresolvableGrantByEmailAddress', 400), ('UserKeyMustBeSpecified', 400)): _add_matcher_class(BotoTestCase.s3_error_code.client, code, base=ClientError) for code in (('InternalError', 500), ('NotImplemented', 501), ('ServiceUnavailable', 503), ('SlowDown', 503)): _add_matcher_class(BotoTestCase.s3_error_code.server, code, base=ServerError) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/utils/0000775000175000017500000000000012332757136024056 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/utils/wait.py0000664000175000017500000001060412332757070025372 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import time import boto.exception import testtools from tempest import config from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) def state_wait(lfunction, final_set=set(), valid_set=None): # TODO(afazekas): evaluate using ABC here if not isinstance(final_set, set): final_set = set((final_set,)) if not isinstance(valid_set, set) and valid_set is not None: valid_set = set((valid_set,)) start_time = time.time() old_status = status = lfunction() while True: if status != old_status: LOG.info('State transition "%s" ==> "%s" %d second', old_status, status, time.time() - start_time) if status in final_set: return status if valid_set is not None and status not in valid_set: return status dtime = time.time() - start_time if dtime > CONF.boto.build_timeout: raise testtools.TestCase\ .failureException("State change timeout exceeded!" '(%ds) While waiting' 'for %s at "%s"' % (dtime, final_set, status)) time.sleep(CONF.boto.build_interval) old_status = status status = lfunction() def re_search_wait(lfunction, regexp): """Stops waiting on success.""" start_time = time.time() while True: text = lfunction() result = re.search(regexp, text) if result is not None: LOG.info('Pattern "%s" found in %d second in "%s"', regexp, time.time() - start_time, text) return result dtime = time.time() - start_time if dtime > CONF.boto.build_timeout: raise testtools.TestCase\ .failureException('Pattern find timeout exceeded!' '(%ds) While waiting for' '"%s" pattern in "%s"' % (dtime, regexp, text)) time.sleep(CONF.boto.build_interval) def wait_no_exception(lfunction, exc_class=None, exc_matcher=None): """Stops waiting on success.""" start_time = time.time() if exc_matcher is not None: exc_class = boto.exception.BotoServerError if exc_class is None: exc_class = BaseException while True: result = None try: result = lfunction() LOG.info('No Exception in %d second', time.time() - start_time) return result except exc_class as exc: if exc_matcher is not None: res = exc_matcher.match(exc) if res is not None: LOG.info(res) raise exc # Let the other exceptions propagate dtime = time.time() - start_time if dtime > CONF.boto.build_timeout: raise testtools.TestCase\ .failureException("Wait timeout exceeded! (%ds)" % dtime) time.sleep(CONF.boto.build_interval) # NOTE(afazekas): EC2/boto normally raise exception instead of empty list def wait_exception(lfunction): """Returns with the exception or raises one.""" start_time = time.time() while True: try: lfunction() except BaseException as exc: LOG.info('Exception in %d second', time.time() - start_time) return exc dtime = time.time() - start_time if dtime > CONF.boto.build_timeout: raise testtools.TestCase\ .failureException("Wait timeout exceeded! (%ds)" % dtime) time.sleep(CONF.boto.build_interval) # TODO(afazekas): consider strategy design pattern.. tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/utils/s3.py0000664000175000017500000000301612332757070024752 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import os import re import boto import boto.s3.key from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) def s3_upload_dir(bucket, path, prefix="", connection_data=None): if isinstance(bucket, basestring): with contextlib.closing(boto.connect_s3(**connection_data)) as conn: bucket = conn.lookup(bucket) for root, dirs, files in os.walk(path): for fil in files: with contextlib.closing(boto.s3.key.Key(bucket)) as key: source = root + os.sep + fil target = re.sub("^" + re.escape(path) + "?/", prefix, source) if os.sep != '/': target = re.sub(re.escape(os.sep), '/', target) key.key = target LOG.info("Uploading %s to %s/%s", source, bucket.name, target) key.set_contents_from_filename(source) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/utils/__init__.py0000664000175000017500000000000012332757070026152 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_ec2_security_groups.py0000664000175000017500000000654112332757070030331 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common.utils import data_utils from tempest import test from tempest.thirdparty.boto import test as boto_test class EC2SecurityGroupTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(EC2SecurityGroupTest, cls).setUpClass() cls.client = cls.os.ec2api_client @test.attr(type='smoke') def test_create_authorize_security_group(self): # EC2 Create, authorize/revoke security group group_name = data_utils.rand_name("securty_group-") group_description = group_name + " security group description " group = self.client.create_security_group(group_name, group_description) self.addResourceCleanUp(self.client.delete_security_group, group_name) groups_get = self.client.get_all_security_groups( groupnames=(group_name,)) self.assertEqual(len(groups_get), 1) group_get = groups_get[0] self.assertEqual(group.name, group_get.name) self.assertEqual(group.name, group_get.name) # ping (icmp_echo) and other icmp allowed from everywhere # from_port and to_port act as icmp type success = self.client.authorize_security_group(group_name, ip_protocol="icmp", cidr_ip="0.0.0.0/0", from_port=-1, to_port=-1) self.assertTrue(success) # allow standard ssh port from anywhere success = self.client.authorize_security_group(group_name, ip_protocol="tcp", cidr_ip="0.0.0.0/0", from_port=22, to_port=22) self.assertTrue(success) # TODO(afazekas): Duplicate tests group_get = self.client.get_all_security_groups( groupnames=(group_name,))[0] # remove listed rules for ip_permission in group_get.rules: for cidr in ip_permission.grants: self.assertTrue(self.client.revoke_security_group(group_name, ip_protocol=ip_permission.ip_protocol, cidr_ip=cidr, from_port=ip_permission.from_port, to_port=ip_permission.to_port)) group_get = self.client.get_all_security_groups( groupnames=(group_name,))[0] # all rules shuld be removed now self.assertEqual(0, len(group_get.rules)) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_ec2_instance_run.py0000664000175000017500000003441312332757070027552 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from boto import exception from tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging from tempest import test from tempest.thirdparty.boto import test as boto_test from tempest.thirdparty.boto.utils import s3 from tempest.thirdparty.boto.utils import wait CONF = config.CONF LOG = logging.getLogger(__name__) class InstanceRunTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(InstanceRunTest, cls).setUpClass() if not cls.conclusion['A_I_IMAGES_READY']: raise cls.skipException("".join(("EC2 ", cls.__name__, ": requires ami/aki/ari manifest"))) cls.s3_client = cls.os.s3_client cls.ec2_client = cls.os.ec2api_client cls.zone = CONF.boto.aws_zone cls.materials_path = CONF.boto.s3_materials_path ami_manifest = CONF.boto.ami_manifest aki_manifest = CONF.boto.aki_manifest ari_manifest = CONF.boto.ari_manifest cls.instance_type = CONF.boto.instance_type cls.bucket_name = data_utils.rand_name("s3bucket-") cls.keypair_name = data_utils.rand_name("keypair-") cls.keypair = cls.ec2_client.create_key_pair(cls.keypair_name) cls.addResourceCleanUp(cls.ec2_client.delete_key_pair, cls.keypair_name) bucket = cls.s3_client.create_bucket(cls.bucket_name) cls.addResourceCleanUp(cls.destroy_bucket, cls.s3_client.connection_data, cls.bucket_name) s3.s3_upload_dir(bucket, cls.materials_path) cls.images = {"ami": {"name": data_utils.rand_name("ami-name-"), "location": cls.bucket_name + "/" + ami_manifest}, "aki": {"name": data_utils.rand_name("aki-name-"), "location": cls.bucket_name + "/" + aki_manifest}, "ari": {"name": data_utils.rand_name("ari-name-"), "location": cls.bucket_name + "/" + ari_manifest}} for image in cls.images.itervalues(): image["image_id"] = cls.ec2_client.register_image( name=image["name"], image_location=image["location"]) cls.addResourceCleanUp(cls.ec2_client.deregister_image, image["image_id"]) for image in cls.images.itervalues(): def _state(): retr = cls.ec2_client.get_image(image["image_id"]) return retr.state state = wait.state_wait(_state, "available") if state != "available": for _image in cls.images.itervalues(): cls.ec2_client.deregister_image(_image["image_id"]) raise exceptions.EC2RegisterImageException(image_id= image["image_id"]) @test.attr(type='smoke') def test_run_idempotent_instances(self): # EC2 run instances idempotently def _run_instance(client_token): reservation = self.ec2_client.run_instances( image_id=self.images["ami"]["image_id"], kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type, client_token=client_token) rcuk = self.addResourceCleanUp(self.destroy_reservation, reservation) return (reservation, rcuk) def _terminate_reservation(reservation, rcuk): for instance in reservation.instances: instance.terminate() self.cancelResourceCleanUp(rcuk) reservation_1, rcuk_1 = _run_instance('token_1') reservation_2, rcuk_2 = _run_instance('token_2') reservation_1a, rcuk_1a = _run_instance('token_1') self.assertIsNotNone(reservation_1) self.assertIsNotNone(reservation_2) self.assertIsNotNone(reservation_1a) # same reservation for token_1 self.assertEqual(reservation_1.id, reservation_1a.id) # Cancel cleanup -- since it's a duplicate, it's # handled by rcuk1 self.cancelResourceCleanUp(rcuk_1a) _terminate_reservation(reservation_1, rcuk_1) _terminate_reservation(reservation_2, rcuk_2) @test.attr(type='smoke') def test_run_stop_terminate_instance(self): # EC2 run, stop and terminate instance image_ami = self.ec2_client.get_image(self.images["ami"] ["image_id"]) reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type) rcuk = self.addResourceCleanUp(self.destroy_reservation, reservation) for instance in reservation.instances: LOG.info("state: %s", instance.state) if instance.state != "running": self.assertInstanceStateWait(instance, "running") for instance in reservation.instances: instance.stop() LOG.info("state: %s", instance.state) if instance.state != "stopped": self.assertInstanceStateWait(instance, "stopped") for instance in reservation.instances: instance.terminate() self.cancelResourceCleanUp(rcuk) @test.attr(type='smoke') def test_run_stop_terminate_instance_with_tags(self): # EC2 run, stop and terminate instance with tags image_ami = self.ec2_client.get_image(self.images["ami"] ["image_id"]) reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type) rcuk = self.addResourceCleanUp(self.destroy_reservation, reservation) for instance in reservation.instances: LOG.info("state: %s", instance.state) if instance.state != "running": self.assertInstanceStateWait(instance, "running") instance.add_tag('key1', value='value1') tags = self.ec2_client.get_all_tags() self.assertEqual(tags[0].name, 'key1') self.assertEqual(tags[0].value, 'value1') tags = self.ec2_client.get_all_tags(filters={'key': 'key1'}) self.assertEqual(tags[0].name, 'key1') self.assertEqual(tags[0].value, 'value1') tags = self.ec2_client.get_all_tags(filters={'value': 'value1'}) self.assertEqual(tags[0].name, 'key1') self.assertEqual(tags[0].value, 'value1') tags = self.ec2_client.get_all_tags(filters={'key': 'value2'}) self.assertEqual(len(tags), 0, str(tags)) for instance in reservation.instances: instance.remove_tag('key1', value='value1') tags = self.ec2_client.get_all_tags() self.assertEqual(len(tags), 0, str(tags)) for instance in reservation.instances: instance.stop() LOG.info("state: %s", instance.state) if instance.state != "stopped": self.assertInstanceStateWait(instance, "stopped") for instance in reservation.instances: instance.terminate() self.cancelResourceCleanUp(rcuk) @test.skip_because(bug="1098891") @test.attr(type='smoke') def test_run_terminate_instance(self): # EC2 run, terminate immediately image_ami = self.ec2_client.get_image(self.images["ami"] ["image_id"]) reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type) for instance in reservation.instances: instance.terminate() try: instance.update(validate=True) except ValueError: pass except exception.EC2ResponseError as exc: if self.ec2_error_code.\ client.InvalidInstanceID.NotFound.match(exc): pass else: raise else: self.assertNotEqual(instance.state, "running") @test.attr(type='smoke') def test_compute_with_volumes(self): # EC2 1. integration test (not strict) image_ami = self.ec2_client.get_image(self.images["ami"]["image_id"]) sec_group_name = data_utils.rand_name("securitygroup-") group_desc = sec_group_name + " security group description " security_group = self.ec2_client.create_security_group(sec_group_name, group_desc) self.addResourceCleanUp(self.destroy_security_group_wait, security_group) self.assertTrue( self.ec2_client.authorize_security_group( sec_group_name, ip_protocol="icmp", cidr_ip="0.0.0.0/0", from_port=-1, to_port=-1)) self.assertTrue( self.ec2_client.authorize_security_group( sec_group_name, ip_protocol="tcp", cidr_ip="0.0.0.0/0", from_port=22, to_port=22)) reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type, key_name=self.keypair_name, security_groups=(sec_group_name,)) LOG.debug("Instance booted - state: %s", reservation.instances[0].state) self.addResourceCleanUp(self.destroy_reservation, reservation) volume = self.ec2_client.create_volume(1, self.zone) LOG.debug("Volume created - status: %s", volume.status) self.addResourceCleanUp(self.destroy_volume_wait, volume) instance = reservation.instances[0] if instance.state != "running": self.assertInstanceStateWait(instance, "running") LOG.debug("Instance now running - state: %s", instance.state) address = self.ec2_client.allocate_address() rcuk_a = self.addResourceCleanUp(address.delete) self.assertTrue(address.associate(instance.id)) rcuk_da = self.addResourceCleanUp(address.disassociate) # TODO(afazekas): ping test. dependecy/permission ? self.assertVolumeStatusWait(volume, "available") # NOTE(afazekas): it may be reports available before it is available ssh = remote_client.RemoteClient(address.public_ip, CONF.compute.ssh_user, pkey=self.keypair.material) text = data_utils.rand_name("Pattern text for console output -") resp = ssh.write_to_console(text) self.assertFalse(resp) def _output(): output = instance.get_console_output() return output.output wait.re_search_wait(_output, text) part_lines = ssh.get_partitions().split('\n') volume.attach(instance.id, "/dev/vdh") def _volume_state(): """Return volume state realizing that 'in-use' is overloaded.""" volume.update(validate=True) status = volume.status attached = volume.attach_data.status LOG.debug("Volume %s is in status: %s, attach_status: %s", volume.id, status, attached) # Nova reports 'in-use' on 'attaching' volumes because we # have a single volume status, and EC2 has 2. Ensure that # if we aren't attached yet we return something other than # 'in-use' if status == 'in-use' and attached != 'attached': return 'attaching' else: return status wait.re_search_wait(_volume_state, "in-use") # NOTE(afazekas): Different Hypervisor backends names # differently the devices, # now we just test is the partition number increased/decrised def _part_state(): current = ssh.get_partitions().split('\n') LOG.debug("Partition map for instance: %s", current) if current > part_lines: return 'INCREASE' if current < part_lines: return 'DECREASE' return 'EQUAL' wait.state_wait(_part_state, 'INCREASE') part_lines = ssh.get_partitions().split('\n') # TODO(afazekas): Resource compare to the flavor settings volume.detach() self.assertVolumeStatusWait(_volume_state, "available") wait.re_search_wait(_volume_state, "available") wait.state_wait(_part_state, 'DECREASE') instance.stop() address.disassociate() self.assertAddressDissasociatedWait(address) self.cancelResourceCleanUp(rcuk_da) address.release() self.assertAddressReleasedWait(address) self.cancelResourceCleanUp(rcuk_a) LOG.debug("Instance %s state: %s", instance.id, instance.state) if instance.state != "stopped": self.assertInstanceStateWait(instance, "stopped") # TODO(afazekas): move steps from teardown to the test case # TODO(afazekas): Snapshot/volume read/write test case tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/boto/test_ec2_volumes.py0000664000175000017500000000515112332757070026551 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import config from tempest.openstack.common import log as logging from tempest import test from tempest.thirdparty.boto import test as boto_test CONF = config.CONF LOG = logging.getLogger(__name__) def compare_volumes(a, b): return (a.id == b.id and a.size == b.size) class EC2VolumesTest(boto_test.BotoTestCase): @classmethod def setUpClass(cls): super(EC2VolumesTest, cls).setUpClass() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.os.ec2api_client cls.zone = CONF.boto.aws_zone @test.attr(type='smoke') def test_create_get_delete(self): # EC2 Create, get, delete Volume volume = self.client.create_volume(1, self.zone) cuk = self.addResourceCleanUp(self.client.delete_volume, volume.id) self.assertIn(volume.status, self.valid_volume_status) retrieved = self.client.get_all_volumes((volume.id,)) self.assertEqual(1, len(retrieved)) self.assertTrue(compare_volumes(volume, retrieved[0])) self.assertVolumeStatusWait(volume, "available") self.client.delete_volume(volume.id) self.cancelResourceCleanUp(cuk) @test.attr(type='smoke') def test_create_volume_from_snapshot(self): # EC2 Create volume from snapshot volume = self.client.create_volume(1, self.zone) self.addResourceCleanUp(self.client.delete_volume, volume.id) self.assertVolumeStatusWait(volume, "available") snap = self.client.create_snapshot(volume.id) self.addResourceCleanUp(self.destroy_snapshot_wait, snap) self.assertSnapshotStatusWait(snap, "completed") svol = self.client.create_volume(1, self.zone, snapshot=snap) cuk = self.addResourceCleanUp(svol.delete) self.assertVolumeStatusWait(svol, "available") svol.delete() self.cancelResourceCleanUp(cuk) tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/__init__.py0000664000175000017500000000000012332757070024047 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/thirdparty/README.rst0000664000175000017500000000202412332757070023435 0ustar chuckchuck00000000000000Tempest Field Guide to Third Party API tests ============================================ What are these tests? --------------------- Third party tests are tests for non native OpenStack APIs that are part of OpenStack projects. If we ship an API, we're really required to ensure that it's working. An example is that Nova Compute currently has EC2 API support in tree, which should be tested as part of normal process. Why are these tests in tempest? ------------------------------- If we ship an API in an OpenStack component, there should be tests in tempest to exercise it in some way. Scope of these tests -------------------- Third party API testing should be limited to the functional testing of third party API compliance. Complex scenarios should be avoided, and instead exercised with the OpenStack API, unless the third party API can't be tested without those scenarios. Whenever possible third party API testing should use a client as close to the third party API as possible. The point of these tests is API validation. tempest-2014.1.dev4108.gf22b6cc/tempest/hacking/0000775000175000017500000000000012332757136021165 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/hacking/checks.py0000664000175000017500000000764612332757070023011 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 re import pep8 PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron', 'trove', 'ironic', 'savanna', 'heat', 'ceilometer', 'marconi', 'sahara'] PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS)) TEST_DEFINITION = re.compile(r'^\s*def test.*') SETUPCLASS_DEFINITION = re.compile(r'^\s*def setUpClass') SCENARIO_DECORATOR = re.compile(r'\s*@.*services\((.*)\)') VI_HEADER_RE = re.compile(r"^#\s+vim?:.+") def import_no_clients_in_api(physical_line, filename): """Check for client imports from tempest/api tests T102: Cannot import OpenStack python clients """ if "tempest/api" in filename: res = PYTHON_CLIENT_RE.match(physical_line) if res: return (physical_line.find(res.group(1)), ("T102: python clients import not allowed" " in tempest/api/* tests")) def scenario_tests_need_service_tags(physical_line, filename, previous_logical): """Check that scenario tests have service tags T104: Scenario tests require a services decorator """ if 'tempest/scenario/test_' in filename: if TEST_DEFINITION.match(physical_line): if not SCENARIO_DECORATOR.match(previous_logical): return (physical_line.find('def'), "T104: Scenario tests require a service decorator") def no_setupclass_for_unit_tests(physical_line, filename): if pep8.noqa(physical_line): return if 'tempest/tests' in filename: if SETUPCLASS_DEFINITION.match(physical_line): return (physical_line.find('def'), "T105: setUpClass can not be used with unit tests") def no_vi_headers(physical_line, line_number, lines): """Check for vi editor configuration in source files. By default vi modelines can only appear in the first or last 5 lines of a source file. T106 """ # NOTE(gilliard): line_number is 1-indexed if line_number <= 5 or line_number > len(lines) - 5: if VI_HEADER_RE.match(physical_line): return 0, "T106: Don't put vi configuration in source files" def service_tags_not_in_module_path(physical_line, filename): """Check that a service tag isn't in the module path A service tag should only be added if the service name isn't already in the module path. T107 """ # NOTE(mtreinish) Scenario tests always need service tags, but subdirs are # created for services like heat which would cause false negatives for # those tests, so just exclude the scenario tests. if 'tempest/scenario' not in filename: matches = SCENARIO_DECORATOR.match(physical_line) if matches: services = matches.group(1).split(',') for service in services: service_name = service.strip().strip("'") modulepath = os.path.split(filename)[0] if service_name in modulepath: return (physical_line.find(service_name), "T107: service tag should not be in path") def factory(register): register(import_no_clients_in_api) register(scenario_tests_need_service_tags) register(no_setupclass_for_unit_tests) register(no_vi_headers) register(service_tags_not_in_module_path) tempest-2014.1.dev4108.gf22b6cc/tempest/hacking/__init__.py0000664000175000017500000000000012332757070023261 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/auth.py0000664000175000017500000006253612332757070021105 0ustar chuckchuck00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import datetime import exceptions import re import urlparse from tempest import config from tempest.services.identity.json import identity_client as json_id from tempest.services.identity.v3.json import identity_client as json_v3id from tempest.services.identity.v3.xml import identity_client as xml_v3id from tempest.services.identity.xml import identity_client as xml_id from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class AuthProvider(object): """ Provide authentication """ def __init__(self, credentials, client_type='tempest', interface=None): """ :param credentials: credentials for authentication :param client_type: 'tempest' or 'official' :param interface: 'json' or 'xml'. Applicable for tempest client only """ credentials = self._convert_credentials(credentials) if self.check_credentials(credentials): self.credentials = credentials else: raise TypeError("Invalid credentials") self.client_type = client_type self.interface = interface if self.client_type == 'tempest' and self.interface is None: self.interface = 'json' self.cache = None self.alt_auth_data = None self.alt_part = None def _convert_credentials(self, credentials): # Support dict credentials for backwards compatibility if isinstance(credentials, dict): return get_credentials(**credentials) else: return credentials def __str__(self): return "Creds :{creds}, client type: {client_type}, interface: " \ "{interface}, cached auth data: {cache}".format( creds=self.credentials, client_type=self.client_type, interface=self.interface, cache=self.cache ) def _decorate_request(self, filters, method, url, headers=None, body=None, auth_data=None): """ Decorate request with authentication data """ raise NotImplementedError def _get_auth(self): raise NotImplementedError def _fill_credentials(self, auth_data_body): raise NotImplementedError def fill_credentials(self): """ Fill credentials object with data from auth """ auth_data = self.get_auth() self._fill_credentials(auth_data[1]) return self.credentials @classmethod def check_credentials(cls, credentials): """ Verify credentials are valid. """ return isinstance(credentials, Credentials) and credentials.is_valid() @property def auth_data(self): return self.get_auth() @auth_data.deleter def auth_data(self): self.clear_auth() def get_auth(self): """ Returns auth from cache if available, else auth first """ if self.cache is None or self.is_expired(self.cache): self.set_auth() return self.cache def set_auth(self): """ Forces setting auth, ignores cache if it exists. Refills credentials """ self.cache = self._get_auth() self._fill_credentials(self.cache[1]) def clear_auth(self): """ Can be called to clear the access cache so that next request will fetch a new token and base_url. """ self.cache = None self.credentials.reset() def is_expired(self, auth_data): raise NotImplementedError def auth_request(self, method, url, headers=None, body=None, filters=None): """ Obtains auth data and decorates a request with that. :param method: HTTP method of the request :param url: relative URL of the request (path) :param headers: HTTP headers of the request :param body: HTTP body in case of POST / PUT :param filters: select a base URL out of the catalog :returns a Tuple (url, headers, body) """ orig_req = dict(url=url, headers=headers, body=body) auth_url, auth_headers, auth_body = self._decorate_request( filters, method, url, headers, body) auth_req = dict(url=auth_url, headers=auth_headers, body=auth_body) # Overwrite part if the request if it has been requested if self.alt_part is not None: if self.alt_auth_data is not None: alt_url, alt_headers, alt_body = self._decorate_request( filters, method, url, headers, body, auth_data=self.alt_auth_data) alt_auth_req = dict(url=alt_url, headers=alt_headers, body=alt_body) auth_req[self.alt_part] = alt_auth_req[self.alt_part] else: # If alt auth data is None, skip auth in the requested part auth_req[self.alt_part] = orig_req[self.alt_part] # Next auth request will be normal, unless otherwise requested self.reset_alt_auth_data() return auth_req['url'], auth_req['headers'], auth_req['body'] def reset_alt_auth_data(self): """ Configure auth provider to provide valid authentication data """ self.alt_part = None self.alt_auth_data = None def set_alt_auth_data(self, request_part, auth_data): """ Configure auth provider to provide alt authentication data on a part of the *next* auth_request. If credentials are None, set invalid data. :param request_part: request part to contain invalid auth: url, headers, body :param auth_data: alternative auth_data from which to get the invalid data to be injected """ self.alt_part = request_part self.alt_auth_data = auth_data def base_url(self, filters, auth_data=None): """ Extracts the base_url based on provided filters """ raise NotImplementedError class KeystoneAuthProvider(AuthProvider): token_expiry_threshold = datetime.timedelta(seconds=60) def __init__(self, credentials, client_type='tempest', interface=None): super(KeystoneAuthProvider, self).__init__(credentials, client_type, interface) self.auth_client = self._auth_client() def _decorate_request(self, filters, method, url, headers=None, body=None, auth_data=None): if auth_data is None: auth_data = self.auth_data token, _ = auth_data base_url = self.base_url(filters=filters, auth_data=auth_data) # build authenticated request # returns new request, it does not touch the original values _headers = copy.deepcopy(headers) if headers is not None else {} _headers['X-Auth-Token'] = token if url is None or url == "": _url = base_url else: # Join base URL and url, and remove multiple contiguous slashes _url = "/".join([base_url, url]) parts = [x for x in urlparse.urlparse(_url)] parts[2] = re.sub("/{2,}", "/", parts[2]) _url = urlparse.urlunparse(parts) # no change to method or body return _url, _headers, body def _auth_client(self): raise NotImplementedError def _auth_params(self): raise NotImplementedError def _get_auth(self): # Bypasses the cache if self.client_type == 'tempest': auth_func = getattr(self.auth_client, 'get_token') auth_params = self._auth_params() # returns token, auth_data token, auth_data = auth_func(**auth_params) return token, auth_data else: raise NotImplementedError def get_token(self): return self.auth_data[0] class KeystoneV2AuthProvider(KeystoneAuthProvider): EXPIRY_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ' def _auth_client(self): if self.client_type == 'tempest': if self.interface == 'json': return json_id.TokenClientJSON() else: return xml_id.TokenClientXML() else: raise NotImplementedError def _auth_params(self): if self.client_type == 'tempest': return dict( user=self.credentials.username, password=self.credentials.password, tenant=self.credentials.tenant_name, auth_data=True) else: raise NotImplementedError def _fill_credentials(self, auth_data_body): tenant = auth_data_body['token']['tenant'] user = auth_data_body['user'] if self.credentials.tenant_name is None: self.credentials.tenant_name = tenant['name'] if self.credentials.tenant_id is None: self.credentials.tenant_id = tenant['id'] if self.credentials.username is None: self.credentials.username = user['name'] if self.credentials.user_id is None: self.credentials.user_id = user['id'] def base_url(self, filters, auth_data=None): """ Filters can be: - service: compute, image, etc - region: the service region - endpoint_type: adminURL, publicURL, internalURL - api_version: replace catalog version with this - skip_path: take just the base URL """ if auth_data is None: auth_data = self.auth_data token, _auth_data = auth_data service = filters.get('service') region = filters.get('region') endpoint_type = filters.get('endpoint_type', 'publicURL') if service is None: raise exceptions.EndpointNotFound("No service provided") _base_url = None for ep in _auth_data['serviceCatalog']: if ep["type"] == service: for _ep in ep['endpoints']: if region is not None and _ep['region'] == region: _base_url = _ep.get(endpoint_type) if not _base_url: # No region matching, use the first _base_url = ep['endpoints'][0].get(endpoint_type) break if _base_url is None: raise exceptions.EndpointNotFound(service) parts = urlparse.urlparse(_base_url) if filters.get('api_version', None) is not None: path = "/" + filters['api_version'] noversion_path = "/".join(parts.path.split("/")[2:]) if noversion_path != "": path += "/" + noversion_path _base_url = _base_url.replace(parts.path, path) if filters.get('skip_path', None) is not None: _base_url = _base_url.replace(parts.path, "/") return _base_url def is_expired(self, auth_data): _, access = auth_data expiry = datetime.datetime.strptime(access['token']['expires'], self.EXPIRY_DATE_FORMAT) return expiry - self.token_expiry_threshold <= \ datetime.datetime.utcnow() class KeystoneV3AuthProvider(KeystoneAuthProvider): EXPIRY_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' def _auth_client(self): if self.client_type == 'tempest': if self.interface == 'json': return json_v3id.V3TokenClientJSON() else: return xml_v3id.V3TokenClientXML() else: raise NotImplementedError def _auth_params(self): if self.client_type == 'tempest': return dict( user=self.credentials.username, password=self.credentials.password, tenant=self.credentials.tenant_name, domain=self.credentials.user_domain_name, auth_data=True) else: raise NotImplementedError def _fill_credentials(self, auth_data_body): # project or domain, depending on the scope project = auth_data_body.get('project', None) domain = auth_data_body.get('domain', None) # user is always there user = auth_data_body['user'] # Set project fields if project is not None: if self.credentials.project_name is None: self.credentials.project_name = project['name'] if self.credentials.project_id is None: self.credentials.project_id = project['id'] if self.credentials.project_domain_id is None: self.credentials.project_domain_id = project['domain']['id'] if self.credentials.project_domain_name is None: self.credentials.project_domain_name = \ project['domain']['name'] # Set domain fields if domain is not None: if self.credentials.domain_id is None: self.credentials.domain_id = domain['id'] if self.credentials.domain_name is None: self.credentials.domain_name = domain['name'] # Set user fields if self.credentials.username is None: self.credentials.username = user['name'] if self.credentials.user_id is None: self.credentials.user_id = user['id'] if self.credentials.user_domain_id is None: self.credentials.user_domain_id = user['domain']['id'] if self.credentials.user_domain_name is None: self.credentials.user_domain_name = user['domain']['name'] def base_url(self, filters, auth_data=None): """ Filters can be: - service: compute, image, etc - region: the service region - endpoint_type: adminURL, publicURL, internalURL - api_version: replace catalog version with this - skip_path: take just the base URL """ if auth_data is None: auth_data = self.auth_data token, _auth_data = auth_data service = filters.get('service') region = filters.get('region') endpoint_type = filters.get('endpoint_type', 'public') if service is None: raise exceptions.EndpointNotFound("No service provided") if 'URL' in endpoint_type: endpoint_type = endpoint_type.replace('URL', '') _base_url = None catalog = _auth_data['catalog'] # Select entries with matching service type service_catalog = [ep for ep in catalog if ep['type'] == service] if len(service_catalog) > 0: service_catalog = service_catalog[0]['endpoints'] else: # No matching service raise exceptions.EndpointNotFound(service) # Filter by endpoint type (interface) filtered_catalog = [ep for ep in service_catalog if ep['interface'] == endpoint_type] if len(filtered_catalog) == 0: # No matching type, keep all and try matching by region at least filtered_catalog = service_catalog # Filter by region filtered_catalog = [ep for ep in filtered_catalog if ep['region'] == region] if len(filtered_catalog) == 0: # No matching region, take the first endpoint filtered_catalog = [service_catalog[0]] # There should be only one match. If not take the first. _base_url = filtered_catalog[0].get('url', None) if _base_url is None: raise exceptions.EndpointNotFound(service) parts = urlparse.urlparse(_base_url) if filters.get('api_version', None) is not None: path = "/" + filters['api_version'] noversion_path = "/".join(parts.path.split("/")[2:]) if noversion_path != "": path += "/" + noversion_path _base_url = _base_url.replace(parts.path, path) if filters.get('skip_path', None) is not None: _base_url = _base_url.replace(parts.path, "/") return _base_url def is_expired(self, auth_data): _, access = auth_data expiry = datetime.datetime.strptime(access['expires_at'], self.EXPIRY_DATE_FORMAT) return expiry - self.token_expiry_threshold <= \ datetime.datetime.utcnow() def get_default_credentials(credential_type, fill_in=True): """ Returns configured credentials of the specified type based on the configured auth_version """ return get_credentials(fill_in=fill_in, credential_type=credential_type) def get_credentials(credential_type=None, fill_in=True, **kwargs): """ Builds a credentials object based on the configured auth_version :param credential_type (string): requests credentials from tempest configuration file. Valid values are defined in Credentials.TYPE. :param kwargs (dict): take into account only if credential_type is not specified or None. Dict of credential key/value pairs Examples: Returns credentials from the provided parameters: >>> get_credentials(username='foo', password='bar') Returns credentials from tempest configuration: >>> get_credentials(credential_type='user') """ if CONF.identity.auth_version == 'v2': credential_class = KeystoneV2Credentials auth_provider_class = KeystoneV2AuthProvider elif CONF.identity.auth_version == 'v3': credential_class = KeystoneV3Credentials auth_provider_class = KeystoneV3AuthProvider else: raise exceptions.InvalidConfiguration('Unsupported auth version') if credential_type is not None: creds = credential_class.get_default(credential_type) else: creds = credential_class(**kwargs) # Fill in the credentials fields that were not specified if fill_in: auth_provider = auth_provider_class(creds) creds = auth_provider.fill_credentials() return creds class Credentials(object): """ Set of credentials for accessing OpenStack services ATTRIBUTES: list of valid class attributes representing credentials. TYPES: types of credentials available in the configuration file. For each key there's a tuple (section, prefix) to match the configuration options. """ ATTRIBUTES = [] TYPES = { 'identity_admin': ('identity', 'admin'), 'compute_admin': ('compute_admin', None), 'user': ('identity', None), 'alt_user': ('identity', 'alt') } def __init__(self, **kwargs): """ Enforce the available attributes at init time (only). Additional attributes can still be set afterwards if tests need to do so. """ self._initial = kwargs self._apply_credentials(kwargs) def _apply_credentials(self, attr): for key in attr.keys(): if key in self.ATTRIBUTES: setattr(self, key, attr[key]) else: raise exceptions.InvalidCredentials def __str__(self): """ Represent only attributes included in self.ATTRIBUTES """ _repr = dict((k, getattr(self, k)) for k in self.ATTRIBUTES) return str(_repr) def __eq__(self, other): """ Credentials are equal if attributes in self.ATTRIBUTES are equal """ return str(self) == str(other) def __getattr__(self, key): # If an attribute is set, __getattr__ is not invoked # If an attribute is not set, and it is a known one, return None if key in self.ATTRIBUTES: return None else: raise AttributeError def __delitem__(self, key): # For backwards compatibility, support dict behaviour if key in self.ATTRIBUTES: delattr(self, key) else: raise AttributeError def get(self, item, default): # In this patch act as dict for backward compatibility try: return getattr(self, item) except AttributeError: return default @classmethod def get_default(cls, credentials_type): if credentials_type not in cls.TYPES: raise exceptions.InvalidCredentials() creds = cls._get_default(credentials_type) if not creds.is_valid(): raise exceptions.InvalidConfiguration() return creds @classmethod def _get_default(cls, credentials_type): raise NotImplementedError def is_valid(self): raise NotImplementedError def reset(self): # First delete all known attributes for key in self.ATTRIBUTES: if getattr(self, key) is not None: delattr(self, key) # Then re-apply initial setup self._apply_credentials(self._initial) class KeystoneV2Credentials(Credentials): CONF_ATTRIBUTES = ['username', 'password', 'tenant_name'] ATTRIBUTES = ['user_id', 'tenant_id'] ATTRIBUTES.extend(CONF_ATTRIBUTES) @classmethod def _get_default(cls, credentials_type='user'): params = {} section, prefix = cls.TYPES[credentials_type] for attr in cls.CONF_ATTRIBUTES: _section = getattr(CONF, section) if prefix is None: params[attr] = getattr(_section, attr) else: params[attr] = getattr(_section, prefix + "_" + attr) return cls(**params) def is_valid(self): """ Minimum set of valid credentials, are username and password. Tenant is optional. """ return None not in (self.username, self.password) class KeystoneV3Credentials(KeystoneV2Credentials): """ Credentials suitable for the Keystone Identity V3 API """ CONF_ATTRIBUTES = ['domain_name', 'password', 'tenant_name', 'username'] ATTRIBUTES = ['project_domain_id', 'project_domain_name', 'project_id', 'project_name', 'tenant_id', 'tenant_name', 'user_domain_id', 'user_domain_name', 'user_id'] ATTRIBUTES.extend(CONF_ATTRIBUTES) def __init__(self, **kwargs): """ If domain is not specified, load the one configured for the identity manager. """ domain_fields = set(x for x in self.ATTRIBUTES if 'domain' in x) if not domain_fields.intersection(kwargs.keys()): kwargs['user_domain_name'] = CONF.identity.admin_domain_name super(KeystoneV3Credentials, self).__init__(**kwargs) def __setattr__(self, key, value): parent = super(KeystoneV3Credentials, self) # for tenant_* set both project and tenant if key == 'tenant_id': parent.__setattr__('project_id', value) elif key == 'tenant_name': parent.__setattr__('project_name', value) # for project_* set both project and tenant if key == 'project_id': parent.__setattr__('tenant_id', value) elif key == 'project_name': parent.__setattr__('tenant_name', value) # for *_domain_* set both user and project if not set yet if key == 'user_domain_id': if self.project_domain_id is None: parent.__setattr__('project_domain_id', value) if key == 'project_domain_id': if self.user_domain_id is None: parent.__setattr__('user_domain_id', value) if key == 'user_domain_name': if self.project_domain_name is None: parent.__setattr__('project_domain_name', value) if key == 'project_domain_name': if self.user_domain_name is None: parent.__setattr__('user_domain_name', value) # support domain_name coming from config if key == 'domain_name': parent.__setattr__('user_domain_name', value) parent.__setattr__('project_domain_name', value) # finally trigger default behaviour for all attributes parent.__setattr__(key, value) def is_valid(self): """ Valid combinations of v3 credentials (excluding token, scope) - User id, password (optional domain) - User name, password and its domain id/name For the scope, valid combinations are: - None - Project id (optional domain) - Project name and its domain id/name """ valid_user_domain = any( [self.user_domain_id is not None, self.user_domain_name is not None]) valid_project_domain = any( [self.project_domain_id is not None, self.project_domain_name is not None]) valid_user = any( [self.user_id is not None, self.username is not None and valid_user_domain]) valid_project = any( [self.project_name is None and self.project_id is None, self.project_id is not None, self.project_name is not None and valid_project_domain]) return all([self.password is not None, valid_user, valid_project]) tempest-2014.1.dev4108.gf22b6cc/tempest/cmd/0000775000175000017500000000000012332757136020324 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/cmd/verify_tempest_config.py0000775000175000017500000003277412332757070025305 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2013 IBM Corp. # # 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 argparse import json import os import sys import urlparse import httplib2 from six.moves import configparser from tempest import clients from tempest import config CONF = config.CONF RAW_HTTP = httplib2.Http() CONF_FILE = None OUTFILE = sys.stdout def _get_config_file(): default_config_dir = os.path.join(os.path.abspath( os.path.dirname(os.path.dirname(__file__))), "etc") default_config_file = "tempest.conf" conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', default_config_dir) conf_file = os.environ.get('TEMPEST_CONFIG', default_config_file) path = os.path.join(conf_dir, conf_file) fd = open(path, 'rw') return fd def change_option(option, group, value): config_parse = configparser.SafeConfigParser() config_parse.optionxform = str config_parse.readfp(CONF_FILE) if not config_parse.has_section(group): config_parse.add_section(group) config_parse.set(group, option, str(value)) global OUTFILE config_parse.write(OUTFILE) def print_and_or_update(option, group, value, update): print('Config option %s in group %s should be changed to: %s' % (option, group, value)) if update: change_option(option, group, value) def verify_glance_api_versions(os, update): # Check glance api versions __, versions = os.image_client.get_versions() if CONF.image_feature_enabled.api_v1 != ('v1.1' in versions or 'v1.0' in versions): print_and_or_update('api_v1', 'image_feature_enabled', not CONF.image_feature_enabled.api_v1, update) if CONF.image_feature_enabled.api_v2 != ('v2.0' in versions): print_and_or_update('api_v2', 'image_feature_enabled', not CONF.image_feature_enabled.api_v2, update) def _get_unversioned_endpoint(base_url): endpoint_parts = urlparse.urlparse(base_url) endpoint = endpoint_parts.scheme + '://' + endpoint_parts.netloc return endpoint def _get_api_versions(os, service): client_dict = { 'nova': os.servers_client, 'keystone': os.identity_client, 'cinder': os.volumes_client, } client_dict[service].skip_path() endpoint = _get_unversioned_endpoint(client_dict[service].base_url) __, body = RAW_HTTP.request(endpoint, 'GET') client_dict[service].reset_path() body = json.loads(body) if service == 'keystone': versions = map(lambda x: x['id'], body['versions']['values']) else: versions = map(lambda x: x['id'], body['versions']) return versions def verify_keystone_api_versions(os, update): # Check keystone api versions versions = _get_api_versions(os, 'keystone') if CONF.identity_feature_enabled.api_v2 != ('v2.0' in versions): print_and_or_update('api_v2', 'identity_feature_enabled', not CONF.identity_feature_enabled.api_v2, update) if CONF.identity_feature_enabled.api_v3 != ('v3.0' in versions): print_and_or_update('api_v3', 'identity_feature_enabled', not CONF.identity_feature_enabled.api_v3, update) def verify_nova_api_versions(os, update): versions = _get_api_versions(os, 'nova') if CONF.compute_feature_enabled.api_v3 != ('v3.0' in versions): print_and_or_update('api_v3', 'compute_feature_enabled', not CONF.compute_feature_enabled.api_v3, update) def verify_cinder_api_versions(os, update): # Check cinder api versions versions = _get_api_versions(os, 'cinder') if CONF.volume_feature_enabled.api_v1 != ('v1.0' in versions): print_and_or_update('api_v1', 'volume_feature_enabled', not CONF.volume_feature_enabled.api_v1, update) if CONF.volume_feature_enabled.api_v2 != ('v2.0' in versions): print_and_or_update('api_v2', 'volume_feature_enabled', not CONF.volume_feature_enabled.api_v2, update) def get_extension_client(os, service): extensions_client = { 'nova': os.extensions_client, 'nova_v3': os.extensions_v3_client, 'cinder': os.volumes_extension_client, 'neutron': os.network_client, 'swift': os.account_client, } if service not in extensions_client: print('No tempest extensions client for %s' % service) exit(1) return extensions_client[service] def get_enabled_extensions(service): extensions_options = { 'nova': CONF.compute_feature_enabled.api_extensions, 'nova_v3': CONF.compute_feature_enabled.api_v3_extensions, 'cinder': CONF.volume_feature_enabled.api_extensions, 'neutron': CONF.network_feature_enabled.api_extensions, 'swift': CONF.object_storage_feature_enabled.discoverable_apis, } if service not in extensions_options: print('No supported extensions list option for %s' % service) exit(1) return extensions_options[service] def verify_extensions(os, service, results): extensions_client = get_extension_client(os, service) __, resp = extensions_client.list_extensions() if isinstance(resp, dict): # Neutron's extension 'name' field has is not a single word (it has # spaces in the string) Since that can't be used for list option the # api_extension option in the network-feature-enabled group uses alias # instead of name. if service == 'neutron': extensions = map(lambda x: x['alias'], resp['extensions']) elif service == 'swift': # Remove Swift general information from extensions list resp.pop('swift') extensions = resp.keys() else: extensions = map(lambda x: x['name'], resp['extensions']) else: extensions = map(lambda x: x['name'], resp) if not results.get(service): results[service] = {} extensions_opt = get_enabled_extensions(service) if extensions_opt[0] == 'all': results[service]['extensions'] = extensions return results # Verify that all configured extensions are actually enabled for extension in extensions_opt: results[service][extension] = extension in extensions # Verify that there aren't additional extensions enabled that aren't # specified in the config list for extension in extensions: if extension not in extensions_opt: results[service][extension] = False return results def display_results(results, update, replace): update_dict = { 'swift': 'object-storage-feature-enabled', 'nova': 'compute-feature-enabled', 'nova_v3': 'compute-feature-enabled', 'cinder': 'volume-feature-enabled', 'neutron': 'network-feature-enabled', } for service in results: # If all extensions are specified as being enabled there is no way to # verify this so we just assume this to be true if results[service].get('extensions'): if replace: output_list = results[service].get('extensions') else: output_list = ['all'] else: extension_list = get_enabled_extensions(service) output_list = [] for extension in results[service]: if not results[service][extension]: if extension in extension_list: print("%s extension: %s should not be included in the " "list of enabled extensions" % (service, extension)) else: print("%s extension: %s should be included in the list" " of enabled extensions" % (service, extension)) output_list.append(extension) else: output_list.append(extension) if update: # Sort List output_list.sort() # Convert list to a string output_string = ', '.join(output_list) if service == 'swift': change_option('discoverable_apis', update_dict[service], output_string) elif service == 'nova_v3': change_option('api_v3_extensions', update_dict[service], output_string) else: change_option('api_extensions', update_dict[service], output_string) def check_service_availability(os, update): services = [] avail_services = [] codename_match = { 'volume': 'cinder', 'network': 'neutron', 'image': 'glance', 'object_storage': 'swift', 'compute': 'nova', 'orchestration': 'heat', 'metering': 'ceilometer', 'telemetry': 'ceilometer', 'data_processing': 'sahara', 'baremetal': 'ironic', 'identity': 'keystone', 'queuing': 'marconi', 'database': 'trove' } # Get catalog list for endpoints to use for validation __, endpoints = os.endpoints_client.list_endpoints() for endpoint in endpoints: __, service = os.service_client.get_service(endpoint['service_id']) services.append(service['type']) # Pull all catalog types from config file and compare against endpoint list for cfgname in dir(CONF._config): cfg = getattr(CONF, cfgname) catalog_type = getattr(cfg, 'catalog_type', None) if not catalog_type: continue else: if cfgname == 'identity': # Keystone is a required service for tempest continue if catalog_type not in services: if getattr(CONF.service_available, codename_match[cfgname]): print('Endpoint type %s not found either disable service ' '%s or fix the catalog_type in the config file' % ( catalog_type, codename_match[cfgname])) if update: change_option(codename_match[cfgname], 'service_available', False) else: if not getattr(CONF.service_available, codename_match[cfgname]): print('Endpoint type %s is available, service %s should be' ' set as available in the config file.' % ( catalog_type, codename_match[cfgname])) if update: change_option(codename_match[cfgname], 'service_available', True) else: avail_services.append(codename_match[cfgname]) return avail_services def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('-u', '--update', action='store_true', help='Update the config file with results from api ' 'queries. This assumes whatever is set in the ' 'config file is incorrect. In the case of ' 'endpoint checks where it could either be the ' 'incorrect catalog type or the service available ' 'option the service available option is assumed ' 'to be incorrect and is thus changed') parser.add_argument('-o', '--output', help="Output file to write an updated config file to. " "This has to be a separate file from the " "original config file. If one isn't specified " "with -u the new config file will be printed to " "STDOUT") parser.add_argument('-r', '--replace-ext', action='store_true', help="If specified the all option will be replaced " "with a full list of extensions") args = parser.parse_args() return args def main(): print('Running config verification...') opts = parse_args() update = opts.update replace = opts.replace_ext global CONF_FILE global OUTFILE if update: CONF_FILE = _get_config_file() if opts.output: OUTFILE = open(opts.output, 'w+') os = clients.ComputeAdminManager(interface='json') services = check_service_availability(os, update) results = {} for service in ['nova', 'nova_v3', 'cinder', 'neutron', 'swift']: if service == 'nova_v3' and 'nova' not in services: continue elif service not in services: continue results = verify_extensions(os, service, results) verify_keystone_api_versions(os, update) verify_glance_api_versions(os, update) verify_nova_api_versions(os, update) verify_cinder_api_versions(os, update) display_results(results, update, replace) if CONF_FILE: CONF_FILE.close() OUTFILE.close() if __name__ == "__main__": main() tempest-2014.1.dev4108.gf22b6cc/tempest/cmd/__init__.py0000664000175000017500000000000012332757070022420 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/__init__.py0000664000175000017500000000000012332757070021655 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/test_discover/0000775000175000017500000000000012332757136022436 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/test_discover/test_discover.py0000664000175000017500000000241212332757070025661 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 sys if sys.version_info >= (2, 7): import unittest else: import unittest2 as unittest def load_tests(loader, tests, pattern): suite = unittest.TestSuite() base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0] base_path = os.path.split(base_path)[0] for test_dir in ['./tempest/api', './tempest/cli', './tempest/scenario', './tempest/thirdparty']: if not pattern: suite.addTests(loader.discover(test_dir, top_level_dir=base_path)) else: suite.addTests(loader.discover(test_dir, pattern=pattern, top_level_dir=base_path)) return suite tempest-2014.1.dev4108.gf22b6cc/tempest/test_discover/__init__.py0000664000175000017500000000000012332757070024532 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/test.py0000664000175000017500000005606212332757070021120 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import atexit import functools import json import os import re import sys import time import urllib import uuid import fixtures import testresources import testscenarios import testtools from tempest import clients import tempest.common.generator.valid_generator as valid from tempest.common import isolated_creds from tempest import config from tempest import exceptions from tempest.openstack.common import importutils from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) CONF = config.CONF # All the successful HTTP status codes from RFC 2616 HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206) def attr(*args, **kwargs): """A decorator which applies the testtools attr decorator This decorator applies the testtools.testcase.attr if it is in the list of attributes to testtools we want to apply. """ def decorator(f): if 'type' in kwargs and isinstance(kwargs['type'], str): f = testtools.testcase.attr(kwargs['type'])(f) if kwargs['type'] == 'smoke': f = testtools.testcase.attr('gate')(f) elif 'type' in kwargs and isinstance(kwargs['type'], list): for attr in kwargs['type']: f = testtools.testcase.attr(attr)(f) if attr == 'smoke': f = testtools.testcase.attr('gate')(f) return f return decorator def safe_setup(f): """A decorator used to wrap the setUpClass for cleaning up resources when setUpClass failed. """ def decorator(cls): try: f(cls) except Exception as se: etype, value, trace = sys.exc_info() LOG.exception("setUpClass failed: %s" % se) try: cls.tearDownClass() except Exception as te: LOG.exception("tearDownClass failed: %s" % te) try: raise etype(value), None, trace finally: del trace # for avoiding circular refs return decorator def services(*args, **kwargs): """A decorator used to set an attr for each service used in a test case This decorator applies a testtools attr for each service that gets exercised by a test case. """ service_list = { 'compute': CONF.service_available.nova, 'image': CONF.service_available.glance, 'baremetal': CONF.service_available.ironic, 'volume': CONF.service_available.cinder, 'orchestration': CONF.service_available.heat, # NOTE(mtreinish) nova-network will provide networking functionality # if neutron isn't available, so always set to True. 'network': True, 'identity': True, 'object_storage': CONF.service_available.swift, 'dashboard': CONF.service_available.horizon, } def decorator(f): for service in args: if service not in service_list: raise exceptions.InvalidServiceTag('%s is not a valid service' % service) attr(type=list(args))(f) @functools.wraps(f) def wrapper(self, *func_args, **func_kwargs): for service in args: if not service_list[service]: msg = 'Skipped because the %s service is not available' % ( service) raise testtools.TestCase.skipException(msg) return f(self, *func_args, **func_kwargs) return wrapper return decorator def stresstest(*args, **kwargs): """Add stress test decorator For all functions with this decorator a attr stress will be set automatically. @param class_setup_per: allowed values are application, process, action ``application``: once in the stress job lifetime ``process``: once in the worker process lifetime ``action``: on each action @param allow_inheritance: allows inheritance of this attribute """ def decorator(f): if 'class_setup_per' in kwargs: setattr(f, "st_class_setup_per", kwargs['class_setup_per']) else: setattr(f, "st_class_setup_per", 'process') if 'allow_inheritance' in kwargs: setattr(f, "st_allow_inheritance", kwargs['allow_inheritance']) else: setattr(f, "st_allow_inheritance", False) attr(type='stress')(f) return f return decorator def skip_because(*args, **kwargs): """A decorator useful to skip tests hitting known bugs @param bug: bug number causing the test to skip @param condition: optional condition to be True for the skip to have place @param interface: skip the test if it is the same as self._interface """ def decorator(f): @functools.wraps(f) def wrapper(self, *func_args, **func_kwargs): skip = False if "condition" in kwargs: if kwargs["condition"] is True: skip = True elif "interface" in kwargs: if kwargs["interface"] == self._interface: skip = True else: skip = True if "bug" in kwargs and skip is True: if not kwargs['bug'].isdigit(): raise ValueError('bug must be a valid bug number') msg = "Skipped until Bug: %s is resolved." % kwargs["bug"] raise testtools.TestCase.skipException(msg) return f(self, *func_args, **func_kwargs) return wrapper return decorator def requires_ext(*args, **kwargs): """A decorator to skip tests if an extension is not enabled @param extension @param service """ def decorator(func): @functools.wraps(func) def wrapper(*func_args, **func_kwargs): if not is_extension_enabled(kwargs['extension'], kwargs['service']): msg = "Skipped because %s extension: %s is not enabled" % ( kwargs['service'], kwargs['extension']) raise testtools.TestCase.skipException(msg) return func(*func_args, **func_kwargs) return wrapper return decorator def is_extension_enabled(extension_name, service): """A function that will check the list of enabled extensions from config """ config_dict = { 'compute': CONF.compute_feature_enabled.api_extensions, 'compute_v3': CONF.compute_feature_enabled.api_v3_extensions, 'volume': CONF.volume_feature_enabled.api_extensions, 'network': CONF.network_feature_enabled.api_extensions, 'object': CONF.object_storage_feature_enabled.discoverable_apis, } if config_dict[service][0] == 'all': return True if extension_name in config_dict[service]: return True return False at_exit_set = set() def validate_tearDownClass(): if at_exit_set: LOG.error( "tearDownClass does not call the super's " "tearDownClass in these classes: \n" + str(at_exit_set)) atexit.register(validate_tearDownClass) if sys.version_info >= (2, 7): class BaseDeps(testtools.TestCase, testtools.testcase.WithAttributes, testresources.ResourcedTestCase): pass else: # Define asserts for py26 import unittest2 class BaseDeps(testtools.TestCase, testtools.testcase.WithAttributes, testresources.ResourcedTestCase, unittest2.TestCase): pass class BaseTestCase(BaseDeps): setUpClassCalled = False _service = None network_resources = {} @classmethod def setUpClass(cls): if hasattr(super(BaseTestCase, cls), 'setUpClass'): super(BaseTestCase, cls).setUpClass() cls.setUpClassCalled = True @classmethod def tearDownClass(cls): at_exit_set.discard(cls) if hasattr(super(BaseTestCase, cls), 'tearDownClass'): super(BaseTestCase, cls).tearDownClass() def setUp(self): super(BaseTestCase, self).setUp() if not self.setUpClassCalled: raise RuntimeError("setUpClass does not calls the super's" "setUpClass in the " + self.__class__.__name__) at_exit_set.add(self.__class__) test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) try: test_timeout = int(test_timeout) except ValueError: test_timeout = 0 if test_timeout > 0: self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or os.environ.get('OS_STDOUT_CAPTURE') == '1'): stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or os.environ.get('OS_STDERR_CAPTURE') == '1'): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) if (os.environ.get('OS_LOG_CAPTURE') != 'False' and os.environ.get('OS_LOG_CAPTURE') != '0'): log_format = '%(asctime)-15s %(message)s' self.useFixture(fixtures.LoggerFixture(nuke_handlers=False, format=log_format, level=None)) @classmethod def get_client_manager(cls, interface=None): """ Returns an OpenStack client manager """ cls.isolated_creds = isolated_creds.IsolatedCreds( cls.__name__, network_resources=cls.network_resources) force_tenant_isolation = getattr(cls, 'force_tenant_isolation', None) if CONF.compute.allow_tenant_isolation or force_tenant_isolation: creds = cls.isolated_creds.get_primary_creds() if getattr(cls, '_interface', None): os = clients.Manager(credentials=creds, interface=cls._interface, service=cls._service) elif interface: os = clients.Manager(credentials=creds, interface=interface, service=cls._service) else: os = clients.Manager(credentials=creds, service=cls._service) else: if getattr(cls, '_interface', None): os = clients.Manager(interface=cls._interface, service=cls._service) elif interface: os = clients.Manager(interface=interface, service=cls._service) else: os = clients.Manager(service=cls._service) return os @classmethod def clear_isolated_creds(cls): """ Clears isolated creds if set """ if getattr(cls, 'isolated_creds'): cls.isolated_creds.clear_isolated_creds() @classmethod def _get_identity_admin_client(cls): """ Returns an instance of the Identity Admin API client """ os = clients.AdminManager(interface=cls._interface, service=cls._service) admin_client = os.identity_client return admin_client @classmethod def set_network_resources(self, network=False, router=False, subnet=False, dhcp=False): """Specify which network resources should be created @param network @param router @param subnet @param dhcp """ # network resources should be set only once from callers # in order to ensure that even if it's called multiple times in # a chain of overloaded methods, the attribute is set only # in the leaf class if not self.network_resources: self.network_resources = { 'network': network, 'router': router, 'subnet': subnet, 'dhcp': dhcp} def assertEmpty(self, list, msg=None): self.assertTrue(len(list) == 0, msg) def assertNotEmpty(self, list, msg=None): self.assertTrue(len(list) > 0, msg) class NegativeAutoTest(BaseTestCase): _resources = {} @classmethod def setUpClass(cls): super(NegativeAutoTest, cls).setUpClass() os = cls.get_client_manager() cls.client = os.negative_client os_admin = clients.AdminManager(interface=cls._interface, service=cls._service) cls.admin_client = os_admin.negative_client @staticmethod def load_schema(file): """ Loads a schema from a file on a specified location. :param file: the file name """ #NOTE(mkoderer): must be extended for xml support fn = os.path.join( os.path.abspath(os.path.dirname(os.path.dirname(__file__))), "etc", "schemas", file) LOG.debug("Open schema file: %s" % (fn)) return json.load(open(fn)) @staticmethod def load_tests(*args): """ Wrapper for testscenarios to set the mandatory scenarios variable only in case a real test loader is in place. Will be automatically called in case the variable "load_tests" is set. """ if getattr(args[0], 'suiteClass', None) is not None: loader, standard_tests, pattern = args else: standard_tests, module, loader = args for test in testtools.iterate_tests(standard_tests): schema_file = getattr(test, '_schema_file', None) if schema_file is not None: setattr(test, 'scenarios', NegativeAutoTest.generate_scenario(schema_file)) return testscenarios.load_tests_apply_scenarios(*args) @staticmethod def generate_scenario(description_file): """ Generates the test scenario list for a given description. :param description: A dictionary with the following entries: name (required) name for the api http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE url (required) the url to be appended to the catalog url with '%s' for each resource mentioned resources: (optional) A list of resource names such as "server", "flavor", etc. with an element for each '%s' in the url. This method will call self.get_resource for each element when constructing the positive test case template so negative subclasses are expected to return valid resource ids when appropriate. json-schema (optional) A valid json schema that will be used to create invalid data for the api calls. For "GET" and "HEAD", the data is used to generate query strings appended to the url, otherwise for the body of the http call. """ description = NegativeAutoTest.load_schema(description_file) LOG.debug(description) generator = importutils.import_class( CONF.negative.test_generator)() generator.validate_schema(description) schema = description.get("json-schema", None) resources = description.get("resources", []) scenario_list = [] expected_result = None for resource in resources: if isinstance(resource, dict): expected_result = resource['expected_result'] resource = resource['name'] LOG.debug("Add resource to test %s" % resource) scn_name = "inv_res_%s" % (resource) scenario_list.append((scn_name, {"resource": (resource, str(uuid.uuid4())), "expected_result": expected_result })) if schema is not None: for name, schema, expected_result in generator.generate(schema): if (expected_result is None and "default_result_code" in description): expected_result = description["default_result_code"] scenario_list.append((name, {"schema": schema, "expected_result": expected_result})) LOG.debug(scenario_list) return scenario_list def execute(self, description_file): """ Execute a http call on an api that are expected to result in client errors. First it uses invalid resources that are part of the url, and then invalid data for queries and http request bodies. :param description: A dictionary with the following entries: name (required) name for the api http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE url (required) the url to be appended to the catalog url with '%s' for each resource mentioned resources: (optional) A list of resource names such as "server", "flavor", etc. with an element for each '%s' in the url. This method will call self.get_resource for each element when constructing the positive test case template so negative subclasses are expected to return valid resource ids when appropriate. json-schema (optional) A valid json schema that will be used to create invalid data for the api calls. For "GET" and "HEAD", the data is used to generate query strings appended to the url, otherwise for the body of the http call. """ description = NegativeAutoTest.load_schema(description_file) LOG.info("Executing %s" % description["name"]) LOG.debug(description) method = description["http-method"] url = description["url"] resources = [self.get_resource(r) for r in description.get("resources", [])] if hasattr(self, "resource"): # Note(mkoderer): The resources list already contains an invalid # entry (see get_resource). # We just send a valid json-schema with it valid_schema = None schema = description.get("json-schema", None) if schema: valid_schema = \ valid.ValidTestGenerator().generate_valid(schema) new_url, body = self._http_arguments(valid_schema, url, method) elif hasattr(self, "schema"): new_url, body = self._http_arguments(self.schema, url, method) else: raise Exception("testscenarios are not active. Please make sure " "that your test runner supports the load_tests " "mechanism") if "admin_client" in description and description["admin_client"]: client = self.admin_client else: client = self.client resp, resp_body = client.send_request(method, new_url, resources, body=body) self._check_negative_response(resp.status, resp_body) def _http_arguments(self, json_dict, url, method): LOG.debug("dict: %s url: %s method: %s" % (json_dict, url, method)) if not json_dict: return url, None elif method in ["GET", "HEAD", "PUT", "DELETE"]: return "%s?%s" % (url, urllib.urlencode(json_dict)), None else: return url, json.dumps(json_dict) def _check_negative_response(self, result, body): expected_result = getattr(self, "expected_result", None) self.assertTrue(result >= 400 and result < 500 and result != 413, "Expected client error, got %s:%s" % (result, body)) self.assertTrue(expected_result is None or expected_result == result, "Expected %s, got %s:%s" % (expected_result, result, body)) @classmethod def set_resource(cls, name, resource): """ This function can be used in setUpClass context to register a resoruce for a test. :param name: The name of the kind of resource such as "flavor", "role", etc. :resource: The id of the resource """ cls._resources[name] = resource def get_resource(self, name): """ Return a valid uuid for a type of resource. If a real resource is needed as part of a url then this method should return one. Otherwise it can return None. :param name: The name of the kind of resource such as "flavor", "role", etc. """ if isinstance(name, dict): name = name['name'] if hasattr(self, "resource") and self.resource[0] == name: LOG.debug("Return invalid resource (%s) value: %s" % (self.resource[0], self.resource[1])) return self.resource[1] if name in self._resources: return self._resources[name] return None def SimpleNegativeAutoTest(klass): """ This decorator registers a test function on basis of the class name. """ @attr(type=['negative', 'gate']) def generic_test(self): self.execute(self._schema_file) cn = klass.__name__ cn = cn.replace('JSON', '') cn = cn.replace('Test', '') # NOTE(mkoderer): replaces uppercase chars inside the class name with '_' lower_cn = re.sub('(? 0: # If there is a non-kwarg parameter, assume it's the error # message or reason description and tack it on to the end # of the exception message # Convert all arguments into their string representations... args = ["%s" % arg for arg in args] self._error_string = (self._error_string + "\nDetails: %s" % '\n'.join(args)) def __str__(self): return self._error_string class RestClientException(TempestException, testtools.TestCase.failureException): pass class RFCViolation(RestClientException): message = "RFC Violation" class InvalidConfiguration(TempestException): message = "Invalid Configuration" class InvalidCredentials(TempestException): message = "Invalid Credentials" class InvalidHttpSuccessCode(RestClientException): message = "The success code is different than the expected one" class NotFound(RestClientException): message = "Object not found" class Unauthorized(RestClientException): message = 'Unauthorized' class InvalidServiceTag(RestClientException): message = "Invalid service tag" class TimeoutException(TempestException): message = "Request timed out" class BuildErrorException(TempestException): message = "Server %(server_id)s failed to build and is in ERROR status" class ImageKilledException(TempestException): message = "Image %(image_id)s 'killed' while waiting for '%(status)s'" class AddImageException(TempestException): message = "Image %(image_id)s failed to become ACTIVE in the allotted time" class EC2RegisterImageException(TempestException): message = ("Image %(image_id)s failed to become 'available' " "in the allotted time") class VolumeBuildErrorException(TempestException): message = "Volume %(volume_id)s failed to build and is in ERROR status" class SnapshotBuildErrorException(TempestException): message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status" class VolumeBackupException(TempestException): message = "Volume backup %(backup_id)s failed and is in ERROR status" class StackBuildErrorException(TempestException): message = ("Stack %(stack_identifier)s is in %(stack_status)s status " "due to '%(stack_status_reason)s'") class StackResourceBuildErrorException(TempestException): message = ("Resource %(resource_name)s in stack %(stack_identifier)s is " "in %(resource_status)s status due to " "'%(resource_status_reason)s'") class BadRequest(RestClientException): message = "Bad request" class UnprocessableEntity(RestClientException): message = "Unprocessable entity" class AuthenticationFailure(RestClientException): message = ("Authentication with user %(user)s and password " "%(password)s failed auth using tenant %(tenant)s.") class EndpointNotFound(TempestException): message = "Endpoint not found" class RateLimitExceeded(TempestException): message = "Rate limit exceeded" class OverLimit(TempestException): message = "Quota exceeded" class ServerFault(TempestException): message = "Got server fault" class ImageFault(TempestException): message = "Got image fault" class IdentityError(TempestException): message = "Got identity error" class Conflict(RestClientException): message = "An object with that identifier already exists" class SSHTimeout(TempestException): message = ("Connection to the %(host)s via SSH timed out.\n" "User: %(user)s, Password: %(password)s") class SSHExecCommandFailed(TempestException): """Raised when remotely executed command returns nonzero status.""" message = ("Command '%(command)s', exit status: %(exit_status)d, " "Error:\n%(strerror)s") class ServerUnreachable(TempestException): message = "The server is not reachable via the configured network" class TearDownException(TempestException): message = "%(num)d cleanUp operation failed" class ResponseWithNonEmptyBody(RFCViolation): message = ("RFC Violation! Response with %(status)d HTTP Status Code " "MUST NOT have a body") class ResponseWithEntity(RFCViolation): message = ("RFC Violation! Response with 205 HTTP Status Code " "MUST NOT have an entity") class InvalidHTTPResponseBody(RestClientException): message = "HTTP response body is invalid json or xml" class InvalidHTTPResponseHeader(RestClientException): message = "HTTP response header is invalid" class InvalidContentType(RestClientException): message = "Invalid content type provided" class UnexpectedResponseCode(RestClientException): message = "Unexpected response code received" class InvalidStructure(TempestException): message = "Invalid structure of table with details" tempest-2014.1.dev4108.gf22b6cc/tempest/README.rst0000664000175000017500000000472412332757070021254 0ustar chuckchuck00000000000000============================ Tempest Field Guide Overview ============================ Tempest is designed to be useful for a large number of different environments. This includes being useful for gating commits to OpenStack core projects, being used to validate OpenStack cloud implementations for both correctness, as well as a burn in tool for OpenStack clouds. As such Tempest tests come in many flavors, each with their own rules and guidelines. Below is the proposed Havana restructuring for Tempest to make this clear. | tempest/ | api/ - API tests | cli/ - CLI tests | scenario/ - complex scenario tests | stress/ - stress tests | thirdparty/ - 3rd party api tests Each of these directories contains different types of tests. What belongs in each directory, the rules and examples for good tests, are documented in a README.rst file in the directory. api --- API tests are validation tests for the OpenStack API. They should not use the existing python clients for OpenStack, but should instead use the tempest implementations of clients. This allows us to test both XML and JSON. Having raw clients also lets us pass invalid JSON and XML to the APIs and see the results, something we could not get with the native clients. When it makes sense, API testing should be moved closer to the projects themselves, possibly as functional tests in their unit test frameworks. cli --- CLI tests use the openstack CLI to interact with the OpenStack cloud. CLI testing in unit tests is somewhat difficult because unlike server testing, there is no access to server code to instantiate. Tempest seems like a logical place for this, as it prereqs having a running OpenStack cloud. scenario -------- Scenario tests are complex "through path" tests for OpenStack functionality. They are typically a series of steps where complicated state requiring multiple services is set up exercised, and torn down. Scenario tests can and should use the OpenStack python clients. stress ------ Stress tests are designed to stress an OpenStack environment by running a high workload against it and seeing what breaks. Tools may be provided to help detect breaks (stack traces in the logs). TODO: old stress tests deleted, new_stress that david is working on moves into here. thirdparty ---------- Many openstack components include 3rdparty API support. It is completely legitimate for Tempest to include tests of 3rdparty APIs, but those should be kept separate from the normal OpenStack validation. tempest-2014.1.dev4108.gf22b6cc/tempest/services/0000775000175000017500000000000012332757136021404 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/object_storage/0000775000175000017500000000000012332757136024376 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/object_storage/account_client.py0000664000175000017500000001667012332757070027751 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.common import http from tempest.common import rest_client from tempest import config from tempest import exceptions from xml.etree import ElementTree as etree CONF = config.CONF class AccountClient(rest_client.RestClient): def __init__(self, auth_provider): super(AccountClient, self).__init__(auth_provider) self.service = CONF.object_storage.catalog_type def create_account(self, data=None, params=None, metadata={}, remove_metadata={}, metadata_prefix='X-Account-Meta-', remove_metadata_prefix='X-Remove-Account-Meta-'): """Create an account.""" url = '' if params: url += '?%s' % urllib.urlencode(params) headers = {} for key in metadata: headers[metadata_prefix + key] = metadata[key] for key in remove_metadata: headers[remove_metadata_prefix + key] = remove_metadata[key] resp, body = self.put(url, data, headers) return resp, body def delete_account(self, data=None, params=None): """Delete an account.""" url = '' if params: if 'bulk-delete' in params: url += 'bulk-delete&' url = '?%s%s' % (url, urllib.urlencode(params)) resp, body = self.delete(url, headers={}, body=data) return resp, body def list_account_metadata(self): """ HEAD on the storage URL Returns all account metadata headers """ resp, body = self.head('') return resp, body def create_account_metadata(self, metadata, metadata_prefix='X-Account-Meta-'): """Creates an account metadata entry.""" headers = {} for key in metadata: headers[metadata_prefix + key] = metadata[key] resp, body = self.post('', headers=headers, body=None) return resp, body def delete_account_metadata(self, metadata, metadata_prefix='X-Remove-Account-Meta-'): """ Deletes an account metadata entry. """ headers = {} for item in metadata: headers[metadata_prefix + item] = metadata[item] resp, body = self.post('', headers=headers, body=None) return resp, body def create_and_delete_account_metadata( self, create_metadata=None, delete_metadata=None, create_metadata_prefix='X-Account-Meta-', delete_metadata_prefix='X-Remove-Account-Meta-'): """ Creates and deletes an account metadata entry. """ headers = {} for key in create_metadata: headers[create_metadata_prefix + key] = create_metadata[key] for key in delete_metadata: headers[delete_metadata_prefix + key] = delete_metadata[key] resp, body = self.post('', headers=headers, body=None) return resp, body def list_account_containers(self, params=None): """ GET on the (base) storage URL Given valid X-Auth-Token, returns a list of all containers for the account. Optional Arguments: limit=[integer value N] Limits the number of results to at most N values DEFAULT: 10,000 marker=[string value X] Given string value X, return object names greater in value than the specified marker. DEFAULT: No Marker format=[string value, either 'json' or 'xml'] Specify either json or xml to return the respective serialized response. DEFAULT: Python-List returned in response body """ url = '?%s' % urllib.urlencode(params) if params else '' resp, body = self.get(url, headers={}) if params and params.get('format') == 'json': body = json.loads(body) elif params and params.get('format') == 'xml': body = etree.fromstring(body) else: body = body.strip().splitlines() return resp, body def list_extensions(self): self.skip_path() try: resp, body = self.get('info') finally: self.reset_path() body = json.loads(body) return resp, body class AccountClientCustomizedHeader(rest_client.RestClient): # TODO(andreaf) This class is now redundant, to be removed in next patch def __init__(self, auth_provider): super(AccountClientCustomizedHeader, self).__init__( auth_provider) # Overwrites json-specific header encoding in rest_client.RestClient self.service = CONF.object_storage.catalog_type self.format = 'json' def request(self, method, url, extra_headers=False, headers=None, body=None): """A simple HTTP request interface.""" self.http_obj = http.ClosingHttp() if headers is None: headers = {} elif extra_headers: try: headers.update(self.get_headers()) except (ValueError, TypeError): headers = {} # Authorize the request req_url, req_headers, req_body = self.auth_provider.auth_request( method=method, url=url, headers=headers, body=body, filters=self.filters ) # use original body resp, resp_body = self.http_obj.request(req_url, method, headers=req_headers, body=req_body) self._log_request(method, req_url, resp) if resp.status == 401 or resp.status == 403: raise exceptions.Unauthorized() return resp, resp_body def list_account_containers(self, params=None, metadata=None): """ GET on the (base) storage URL Given a valid X-Auth-Token, returns a list of all containers for the account. Optional Arguments: limit=[integer value N] Limits the number of results to at most N values DEFAULT: 10,000 marker=[string value X] Given string value X, return object names greater in value than the specified marker. DEFAULT: No Marker format=[string value, either 'json' or 'xml'] Specify either json or xml to return the respective serialized response. DEFAULT: Python-List returned in response body """ url = '?format=%s' % self.format if params: url += '&%s' + urllib.urlencode(params) headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.get(url, headers=headers) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/object_storage/container_client.py0000664000175000017500000001511712332757070030272 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.common import rest_client from tempest import config from xml.etree import ElementTree as etree CONF = config.CONF class ContainerClient(rest_client.RestClient): def __init__(self, auth_provider): super(ContainerClient, self).__init__(auth_provider) # Overwrites json-specific header encoding in rest_client.RestClient self.headers = {} self.service = CONF.object_storage.catalog_type self.format = 'json' def create_container( self, container_name, metadata=None, remove_metadata=None, metadata_prefix='X-Container-Meta-', remove_metadata_prefix='X-Remove-Container-Meta-'): """ Creates a container, with optional metadata passed in as a dictionary """ url = str(container_name) headers = {} if metadata is not None: for key in metadata: headers[metadata_prefix + key] = metadata[key] if remove_metadata is not None: for key in remove_metadata: headers[remove_metadata_prefix + key] = remove_metadata[key] resp, body = self.put(url, body=None, headers=headers) return resp, body def delete_container(self, container_name): """Deletes the container (if it's empty).""" url = str(container_name) resp, body = self.delete(url) return resp, body def update_container_metadata( self, container_name, metadata=None, remove_metadata=None, metadata_prefix='X-Container-Meta-', remove_metadata_prefix='X-Remove-Container-Meta-'): """Updates arbitrary metadata on container.""" url = str(container_name) headers = {} if metadata is not None: for key in metadata: headers[metadata_prefix + key] = metadata[key] if remove_metadata is not None: for key in remove_metadata: headers[remove_metadata_prefix + key] = remove_metadata[key] resp, body = self.post(url, body=None, headers=headers) return resp, body def delete_container_metadata(self, container_name, metadata, metadata_prefix='X-Remove-Container-Meta-'): """Deletes arbitrary metadata on container.""" url = str(container_name) headers = {} if metadata is not None: for item in metadata: headers[metadata_prefix + item] = metadata[item] resp, body = self.post(url, body=None, headers=headers) return resp, body def list_container_metadata(self, container_name): """ Retrieves container metadata headers """ url = str(container_name) resp, body = self.head(url) return resp, body def list_all_container_objects(self, container, params=None): """ Returns complete list of all objects in the container, even if item count is beyond 10,000 item listing limit. Does not require any parameters aside from container name. """ # TODO(dwalleck): Rewrite using json format to avoid newlines at end of # obj names. Set limit to API limit - 1 (max returned items = 9999) limit = 9999 if params is not None: if 'limit' in params: limit = params['limit'] if 'marker' in params: limit = params['marker'] resp, objlist = self.list_container_contents( container, params={'limit': limit, 'format': 'json'}) return objlist """tmp = [] for obj in objlist: tmp.append(obj['name']) objlist = tmp if len(objlist) >= limit: # Increment marker marker = objlist[len(objlist) - 1] # Get the next chunk of the list objlist.extend(_list_all_container_objects(container, params={'marker': marker, 'limit': limit})) return objlist else: # Return final, complete list return objlist""" def list_container_contents(self, container, params=None): """ List the objects in a container, given the container name Returns the container object listing as a plain text list, or as xml or json if that option is specified via the 'format' argument. Optional Arguments: limit = integer For an integer value n, limits the number of results to at most n values. marker = 'string' Given a string value x, return object names greater in value than the specified marker. prefix = 'string' For a string value x, causes the results to be limited to names beginning with the substring x. format = 'json' or 'xml' Specify either json or xml to return the respective serialized response. If json, returns a list of json objects if xml, returns a string of xml path = 'string' For a string value x, return the object names nested in the pseudo path (assuming preconditions are met - see below). delimiter = 'character' For a character c, return all the object names nested in the container (without the need for the directory marker objects). """ url = str(container) if params: url += '?' url += '&%s' % urllib.urlencode(params) resp, body = self.get(url, headers={}) if params and params.get('format') == 'json': body = json.loads(body) elif params and params.get('format') == 'xml': body = etree.fromstring(body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/object_storage/__init__.py0000664000175000017500000000000012332757070026472 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/object_storage/object_client.py0000664000175000017500000001767212332757070027566 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from tempest.common import http from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class ObjectClient(rest_client.RestClient): def __init__(self, auth_provider): super(ObjectClient, self).__init__(auth_provider) self.service = CONF.object_storage.catalog_type def create_object(self, container, object_name, data, params=None, metadata=None): """Create storage object.""" headers = self.get_headers() if not data: headers['content-length'] = '0' if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "%s/%s" % (str(container), str(object_name)) if params: url += '?%s' % urllib.urlencode(params) resp, body = self.put(url, data, headers) return resp, body def update_object(self, container, object_name, data): """Upload data to replace current storage object.""" return self.create_object(container, object_name, data) def delete_object(self, container, object_name, params=None): """Delete storage object.""" url = "%s/%s" % (str(container), str(object_name)) if params: url += '?%s' % urllib.urlencode(params) resp, body = self.delete(url, headers={}) return resp, body def update_object_metadata(self, container, object_name, metadata, metadata_prefix='X-Object-Meta-'): """Add, remove, or change X-Object-Meta metadata for storage object.""" headers = {} for key in metadata: headers["%s%s" % (str(metadata_prefix), str(key))] = metadata[key] url = "%s/%s" % (str(container), str(object_name)) resp, body = self.post(url, None, headers=headers) return resp, body def list_object_metadata(self, container, object_name): """List all storage object X-Object-Meta- metadata.""" url = "%s/%s" % (str(container), str(object_name)) resp, body = self.head(url) return resp, body def get_object(self, container, object_name, metadata=None): """Retrieve object's data.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "{0}/{1}".format(container, object_name) resp, body = self.get(url, headers=headers) return resp, body def copy_object_in_same_container(self, container, src_object_name, dest_object_name, metadata=None): """Copy storage object's data to the new object using PUT.""" url = "{0}/{1}".format(container, dest_object_name) headers = {} headers['X-Copy-From'] = "%s/%s" % (str(container), str(src_object_name)) headers['content-length'] = '0' if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.put(url, None, headers=headers) return resp, body def copy_object_across_containers(self, src_container, src_object_name, dst_container, dst_object_name, metadata=None): """Copy storage object's data to the new object using PUT.""" url = "{0}/{1}".format(dst_container, dst_object_name) headers = {} headers['X-Copy-From'] = "%s/%s" % (str(src_container), str(src_object_name)) headers['content-length'] = '0' if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.put(url, None, headers=headers) return resp, body def copy_object_2d_way(self, container, src_object_name, dest_object_name, metadata=None): """Copy storage object's data to the new object using COPY.""" url = "{0}/{1}".format(container, src_object_name) headers = {} headers['Destination'] = "%s/%s" % (str(container), str(dest_object_name)) if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.copy(url, headers=headers) return resp, body def create_object_segments(self, container, object_name, segment, data): """Creates object segments.""" url = "{0}/{1}/{2}".format(container, object_name, segment) resp, body = self.put(url, data) return resp, body class ObjectClientCustomizedHeader(rest_client.RestClient): # TODO(andreaf) This class is now redundant, to be removed in next patch def __init__(self, auth_provider): super(ObjectClientCustomizedHeader, self).__init__( auth_provider) # Overwrites json-specific header encoding in rest_client.RestClient self.service = CONF.object_storage.catalog_type self.format = 'json' def request(self, method, url, extra_headers=False, headers=None, body=None): """A simple HTTP request interface.""" dscv = CONF.identity.disable_ssl_certificate_validation self.http_obj = http.ClosingHttp( disable_ssl_certificate_validation=dscv) if headers is None: headers = {} elif extra_headers: try: headers.update(self.get_headers()) except (ValueError, TypeError): headers = {} # Authorize the request req_url, req_headers, req_body = self.auth_provider.auth_request( method=method, url=url, headers=headers, body=body, filters=self.filters ) # Use original method resp, resp_body = self.http_obj.request(req_url, method, headers=req_headers, body=req_body) self._log_request(method, req_url, resp) if resp.status == 401 or resp.status == 403: raise exceptions.Unauthorized() return resp, resp_body def get_object(self, container, object_name, metadata=None): """Retrieve object's data.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "{0}/{1}".format(container, object_name) resp, body = self.get(url, headers=headers) return resp, body def create_object(self, container, object_name, data, metadata=None): """Create storage object.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] if not data: headers['content-length'] = '0' url = "%s/%s" % (str(container), str(object_name)) resp, body = self.put(url, data, headers=headers) return resp, body def delete_object(self, container, object_name, metadata=None): """Delete storage object.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "%s/%s" % (str(container), str(object_name)) resp, body = self.delete(url, headers=headers) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/orchestration/0000775000175000017500000000000012332757136024270 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/orchestration/json/0000775000175000017500000000000012332757136025241 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/orchestration/json/orchestration_client.py0000664000175000017500000002322112332757070032032 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import re import time import urllib from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class OrchestrationClient(rest_client.RestClient): def __init__(self, auth_provider): super(OrchestrationClient, self).__init__(auth_provider) self.service = CONF.orchestration.catalog_type self.build_interval = CONF.orchestration.build_interval self.build_timeout = CONF.orchestration.build_timeout def list_stacks(self, params=None): """Lists all stacks for a user.""" uri = 'stacks' if params: uri += '?%s' % urllib.urlencode(params) resp, body = self.get(uri) body = json.loads(body) return resp, body['stacks'] def create_stack(self, name, disable_rollback=True, parameters={}, timeout_mins=60, template=None, template_url=None): headers, body = self._prepare_update_create( name, disable_rollback, parameters, timeout_mins, template, template_url) uri = 'stacks' resp, body = self.post(uri, headers=headers, body=body) return resp, body def update_stack(self, stack_identifier, name, disable_rollback=True, parameters={}, timeout_mins=60, template=None, template_url=None): headers, body = self._prepare_update_create( name, disable_rollback, parameters, timeout_mins, template, template_url) uri = "stacks/%s" % stack_identifier resp, body = self.put(uri, headers=headers, body=body) return resp, body def _prepare_update_create(self, name, disable_rollback=True, parameters={}, timeout_mins=60, template=None, template_url=None): post_body = { "stack_name": name, "disable_rollback": disable_rollback, "parameters": parameters, "timeout_mins": timeout_mins, "template": "HeatTemplateFormatVersion: '2012-12-12'\n" } if template: post_body['template'] = template if template_url: post_body['template_url'] = template_url body = json.dumps(post_body) # Password must be provided on stack create so that heat # can perform future operations on behalf of the user headers = self.get_headers() headers['X-Auth-Key'] = self.password headers['X-Auth-User'] = self.user return headers, body def get_stack(self, stack_identifier): """Returns the details of a single stack.""" url = "stacks/%s" % stack_identifier resp, body = self.get(url) body = json.loads(body) return resp, body['stack'] def suspend_stack(self, stack_identifier): """Suspend a stack.""" url = 'stacks/%s/actions' % stack_identifier body = {'suspend': None} resp, body = self.post(url, json.dumps(body)) return resp, body def resume_stack(self, stack_identifier): """Resume a stack.""" url = 'stacks/%s/actions' % stack_identifier body = {'resume': None} resp, body = self.post(url, json.dumps(body)) return resp, body def list_resources(self, stack_identifier): """Returns the details of a single resource.""" url = "stacks/%s/resources" % stack_identifier resp, body = self.get(url) body = json.loads(body) return resp, body['resources'] def get_resource(self, stack_identifier, resource_name): """Returns the details of a single resource.""" url = "stacks/%s/resources/%s" % (stack_identifier, resource_name) resp, body = self.get(url) body = json.loads(body) return resp, body['resource'] def delete_stack(self, stack_identifier): """Deletes the specified Stack.""" return self.delete("stacks/%s" % str(stack_identifier)) def wait_for_resource_status(self, stack_identifier, resource_name, status, failure_pattern='^.*_FAILED$'): """Waits for a Resource to reach a given status.""" start = int(time.time()) fail_regexp = re.compile(failure_pattern) while True: try: resp, body = self.get_resource( stack_identifier, resource_name) except exceptions.NotFound: # ignore this, as the resource may not have # been created yet pass else: resource_name = body['resource_name'] resource_status = body['resource_status'] if resource_status == status: return if fail_regexp.search(resource_status): raise exceptions.StackResourceBuildErrorException( resource_name=resource_name, stack_identifier=stack_identifier, resource_status=resource_status, resource_status_reason=body['resource_status_reason']) if int(time.time()) - start >= self.build_timeout: message = ('Resource %s failed to reach %s status within ' 'the required time (%s s).' % (resource_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) def wait_for_stack_status(self, stack_identifier, status, failure_pattern='^.*_FAILED$'): """Waits for a Stack to reach a given status.""" start = int(time.time()) fail_regexp = re.compile(failure_pattern) while True: resp, body = self.get_stack(stack_identifier) stack_name = body['stack_name'] stack_status = body['stack_status'] if stack_status == status: return body if fail_regexp.search(stack_status): raise exceptions.StackBuildErrorException( stack_identifier=stack_identifier, stack_status=stack_status, stack_status_reason=body['stack_status_reason']) if int(time.time()) - start >= self.build_timeout: message = ('Stack %s failed to reach %s status within ' 'the required time (%s s).' % (stack_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) def show_resource_metadata(self, stack_identifier, resource_name): """Returns the resource's metadata.""" url = ('stacks/{stack_identifier}/resources/{resource_name}' '/metadata'.format(**locals())) resp, body = self.get(url) body = json.loads(body) return resp, body['metadata'] def list_events(self, stack_identifier): """Returns list of all events for a stack.""" url = 'stacks/{stack_identifier}/events'.format(**locals()) resp, body = self.get(url) body = json.loads(body) return resp, body['events'] def list_resource_events(self, stack_identifier, resource_name): """Returns list of all events for a resource from stack.""" url = ('stacks/{stack_identifier}/resources/{resource_name}' '/events'.format(**locals())) resp, body = self.get(url) body = json.loads(body) return resp, body['events'] def show_event(self, stack_identifier, resource_name, event_id): """Returns the details of a single stack's event.""" url = ('stacks/{stack_identifier}/resources/{resource_name}/events' '/{event_id}'.format(**locals())) resp, body = self.get(url) body = json.loads(body) return resp, body['event'] def show_template(self, stack_identifier): """Returns the template for the stack.""" url = ('stacks/{stack_identifier}/template'.format(**locals())) resp, body = self.get(url) body = json.loads(body) return resp, body def _validate_template(self, post_body): """Returns the validation request result.""" post_body = json.dumps(post_body) resp, body = self.post('validate', post_body) body = json.loads(body) return resp, body def validate_template(self, template, parameters={}): """Returns the validation result for a template with parameters.""" post_body = { 'template': template, 'parameters': parameters, } return self._validate_template(post_body) def validate_template_url(self, template_url, parameters={}): """Returns the validation result for a template with parameters.""" post_body = { 'template_url': template_url, 'parameters': parameters, } return self._validate_template(post_body) tempest-2014.1.dev4108.gf22b6cc/tempest/services/orchestration/json/__init__.py0000664000175000017500000000000012332757070027335 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/orchestration/__init__.py0000664000175000017500000000000012332757070026364 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/data_processing/0000775000175000017500000000000012332757136024551 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/data_processing/__init__.py0000664000175000017500000000000012332757070026645 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/data_processing/v1_1/0000775000175000017500000000000012332757136025317 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/data_processing/v1_1/client.py0000664000175000017500000001327612332757070027155 0ustar chuckchuck00000000000000# Copyright (c) 2013 Mirantis 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 json from tempest.common import rest_client from tempest import config CONF = config.CONF class DataProcessingClient(rest_client.RestClient): def __init__(self, auth_provider): super(DataProcessingClient, self).__init__(auth_provider) self.service = CONF.data_processing.catalog_type @classmethod def _request_and_parse(cls, req_fun, uri, res_name, *args, **kwargs): """Make a request using specified req_fun and parse response. It returns pair: resp and parsed resource(s) body. """ resp, body = req_fun(uri, headers={ 'Content-Type': 'application/json' }, *args, **kwargs) body = json.loads(body) return resp, body[res_name] def list_node_group_templates(self): """List all node group templates for a user.""" uri = 'node-group-templates' return self._request_and_parse(self.get, uri, 'node_group_templates') def get_node_group_template(self, tmpl_id): """Returns the details of a single node group template.""" uri = 'node-group-templates/%s' % tmpl_id return self._request_and_parse(self.get, uri, 'node_group_template') def create_node_group_template(self, name, plugin_name, hadoop_version, node_processes, flavor_id, node_configs=None, **kwargs): """Creates node group template with specified params. It supports passing additional params using kwargs and returns created object. """ uri = 'node-group-templates' body = kwargs.copy() body.update({ 'name': name, 'plugin_name': plugin_name, 'hadoop_version': hadoop_version, 'node_processes': node_processes, 'flavor_id': flavor_id, 'node_configs': node_configs or dict(), }) return self._request_and_parse(self.post, uri, 'node_group_template', body=json.dumps(body)) def delete_node_group_template(self, tmpl_id): """Deletes the specified node group template by id.""" uri = 'node-group-templates/%s' % tmpl_id return self.delete(uri) def list_plugins(self): """List all enabled plugins.""" uri = 'plugins' return self._request_and_parse(self.get, uri, 'plugins') def get_plugin(self, plugin_name, plugin_version=None): """Returns the details of a single plugin.""" uri = 'plugins/%s' % plugin_name if plugin_version: uri += '/%s' % plugin_version return self._request_and_parse(self.get, uri, 'plugin') def list_cluster_templates(self): """List all cluster templates for a user.""" uri = 'cluster-templates' return self._request_and_parse(self.get, uri, 'cluster_templates') def get_cluster_template(self, tmpl_id): """Returns the details of a single cluster template.""" uri = 'cluster-templates/%s' % tmpl_id return self._request_and_parse(self.get, uri, 'cluster_template') def create_cluster_template(self, name, plugin_name, hadoop_version, node_groups, cluster_configs=None, **kwargs): """Creates cluster template with specified params. It supports passing additional params using kwargs and returns created object. """ uri = 'cluster-templates' body = kwargs.copy() body.update({ 'name': name, 'plugin_name': plugin_name, 'hadoop_version': hadoop_version, 'node_groups': node_groups, 'cluster_configs': cluster_configs or dict(), }) return self._request_and_parse(self.post, uri, 'cluster_template', body=json.dumps(body)) def delete_cluster_template(self, tmpl_id): """Deletes the specified cluster template by id.""" uri = 'cluster-templates/%s' % tmpl_id return self.delete(uri) def list_data_sources(self): """List all data sources for a user.""" uri = 'data-sources' return self._request_and_parse(self.get, uri, 'data_sources') def get_data_source(self, source_id): """Returns the details of a single data source.""" uri = 'data-sources/%s' % source_id return self._request_and_parse(self.get, uri, 'data_source') def create_data_source(self, name, data_source_type, url, **kwargs): """Creates data source with specified params. It supports passing additional params using kwargs and returns created object. """ uri = 'data-sources' body = kwargs.copy() body.update({ 'name': name, 'type': data_source_type, 'url': url }) return self._request_and_parse(self.post, uri, 'data_source', body=json.dumps(body)) def delete_data_source(self, source_id): """Deletes the specified data source by id.""" uri = 'data-sources/%s' % source_id return self.delete(uri) tempest-2014.1.dev4108.gf22b6cc/tempest/services/data_processing/v1_1/__init__.py0000664000175000017500000000000012332757070027413 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/0000775000175000017500000000000012332757136022466 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/__init__.py0000664000175000017500000000000012332757070024562 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v2/0000775000175000017500000000000012332757136023015 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v2/json/0000775000175000017500000000000012332757136023766 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v2/json/image_client.py0000664000175000017500000001333312332757070026760 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib import jsonschema from tempest.common import glance_http from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class ImageClientV2JSON(rest_client.RestClient): def __init__(self, auth_provider): super(ImageClientV2JSON, self).__init__(auth_provider) self.service = CONF.image.catalog_type self._http = None def _get_http(self): dscv = CONF.identity.disable_ssl_certificate_validation return glance_http.HTTPClient(auth_provider=self.auth_provider, filters=self.filters, insecure=dscv) def _validate_schema(self, body, type='image'): if type in ['image', 'images']: resp, schema = self.get_schema(type) else: raise ValueError("%s is not a valid schema type" % type) jsonschema.validate(body, schema) @property def http(self): if self._http is None: if CONF.service_available.glance: self._http = self._get_http() return self._http def update_image(self, image_id, patch): data = json.dumps(patch) self._validate_schema(data) headers = {"Content-Type": "application/openstack-images-v2.0" "-json-patch"} resp, body = self.patch('v2/images/%s' % image_id, data, headers) return resp, self._parse_resp(body) def create_image(self, name, container_format, disk_format, **kwargs): params = { "name": name, "container_format": container_format, "disk_format": disk_format, } for option in ['visibility']: if option in kwargs: value = kwargs.get(option) if isinstance(value, dict) or isinstance(value, tuple): params.update(value) else: params[option] = value data = json.dumps(params) self._validate_schema(data) resp, body = self.post('v2/images', data) body = json.loads(body) return resp, body def delete_image(self, image_id): url = 'v2/images/%s' % image_id self.delete(url) def image_list(self, params=None): url = 'v2/images' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self._validate_schema(body, type='images') return resp, body['images'] def get_image(self, image_id): url = 'v2/images/%s' % image_id resp, body = self.get(url) body = json.loads(body) return resp, body def is_resource_deleted(self, id): try: self.get_image(id) except exceptions.NotFound: return True return False def store_image(self, image_id, data): url = 'v2/images/%s/file' % image_id headers = {'Content-Type': 'application/octet-stream'} resp, body = self.http.raw_request('PUT', url, headers=headers, body=data) return resp, body def get_image_file(self, image_id): url = 'v2/images/%s/file' % image_id resp, body = self.get(url) return resp, body def add_image_tag(self, image_id, tag): url = 'v2/images/%s/tags/%s' % (image_id, tag) resp, body = self.put(url, body=None) return resp, body def delete_image_tag(self, image_id, tag): url = 'v2/images/%s/tags/%s' % (image_id, tag) resp, _ = self.delete(url) return resp def get_image_membership(self, image_id): url = 'v2/images/%s/members' % image_id resp, body = self.get(url) body = json.loads(body) self.expected_success(200, resp) return resp, body def add_member(self, image_id, member_id): url = 'v2/images/%s/members' % image_id data = json.dumps({'member': member_id}) resp, body = self.post(url, data) body = json.loads(body) self.expected_success(200, resp) return resp, body def update_member_status(self, image_id, member_id, status): """Valid status are: ``pending``, ``accepted``, ``rejected``.""" url = 'v2/images/%s/members/%s' % (image_id, member_id) data = json.dumps({'status': status}) resp, body = self.put(url, data) body = json.loads(body) self.expected_success(200, resp) return resp, body def get_member(self, image_id, member_id): url = 'v2/images/%s/members/%s' % (image_id, member_id) resp, body = self.get(url) self.expected_success(200, resp) return resp, json.loads(body) def remove_member(self, image_id, member_id): url = 'v2/images/%s/members/%s' % (image_id, member_id) resp, _ = self.delete(url) self.expected_success(204, resp) return resp def get_schema(self, schema): url = 'v2/schemas/%s' % schema resp, body = self.get(url) body = json.loads(body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v2/json/__init__.py0000664000175000017500000000000012332757070026062 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v2/__init__.py0000664000175000017500000000000012332757070025111 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v1/0000775000175000017500000000000012332757136023014 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v1/json/0000775000175000017500000000000012332757136023765 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v1/json/image_client.py0000664000175000017500000002454012332757070026761 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import errno import json import os import time import urllib from tempest.common import glance_http from tempest.common import rest_client from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class ImageClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ImageClientJSON, self).__init__(auth_provider) self.service = CONF.image.catalog_type self._http = None def _image_meta_from_headers(self, headers): meta = {'properties': {}} for key, value in headers.iteritems(): if key.startswith('x-image-meta-property-'): _key = key[22:] meta['properties'][_key] = value elif key.startswith('x-image-meta-'): _key = key[13:] meta[_key] = value for key in ['is_public', 'protected', 'deleted']: if key in meta: meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes', '1') for key in ['size', 'min_ram', 'min_disk']: if key in meta: try: meta[key] = int(meta[key]) except ValueError: pass return meta def _image_meta_to_headers(self, fields): headers = {} fields_copy = copy.deepcopy(fields) copy_from = fields_copy.pop('copy_from', None) if copy_from is not None: headers['x-glance-api-copy-from'] = copy_from for key, value in fields_copy.pop('properties', {}).iteritems(): headers['x-image-meta-property-%s' % key] = str(value) for key, value in fields_copy.pop('api', {}).iteritems(): headers['x-glance-api-property-%s' % key] = str(value) for key, value in fields_copy.iteritems(): headers['x-image-meta-%s' % key] = str(value) return headers def _get_file_size(self, obj): """Analyze file-like object and attempt to determine its size. :param obj: file-like object, typically redirected from stdin. :retval The file's size or None if it cannot be determined. """ # For large images, we need to supply the size of the # image file. See LP Bugs #827660 and #845788. if hasattr(obj, 'seek') and hasattr(obj, 'tell'): try: obj.seek(0, os.SEEK_END) obj_size = obj.tell() obj.seek(0) return obj_size except IOError as e: if e.errno == errno.ESPIPE: # Illegal seek. This means the user is trying # to pipe image data to the client, e.g. # echo testdata | bin/glance add blah..., or # that stdin is empty, or that a file-like # object which doesn't support 'seek/tell' has # been supplied. return None else: raise else: # Cannot determine size of input image return None def _get_http(self): dscv = CONF.identity.disable_ssl_certificate_validation return glance_http.HTTPClient(auth_provider=self.auth_provider, filters=self.filters, insecure=dscv) def _create_with_data(self, headers, data): resp, body_iter = self.http.raw_request('POST', '/v1/images', headers=headers, body=data) self._error_checker('POST', '/v1/images', headers, data, resp, body_iter) body = json.loads(''.join([c for c in body_iter])) return resp, body['image'] def _update_with_data(self, image_id, headers, data): url = '/v1/images/%s' % image_id resp, body_iter = self.http.raw_request('PUT', url, headers=headers, body=data) self._error_checker('PUT', url, headers, data, resp, body_iter) body = json.loads(''.join([c for c in body_iter])) return resp, body['image'] @property def http(self): if self._http is None: if CONF.service_available.glance: self._http = self._get_http() return self._http def create_image(self, name, container_format, disk_format, **kwargs): params = { "name": name, "container_format": container_format, "disk_format": disk_format, } headers = {} for option in ['is_public', 'location', 'properties', 'copy_from', 'min_ram']: if option in kwargs: params[option] = kwargs.get(option) headers.update(self._image_meta_to_headers(params)) if 'data' in kwargs: return self._create_with_data(headers, kwargs.get('data')) resp, body = self.post('v1/images', None, headers) body = json.loads(body) return resp, body['image'] def update_image(self, image_id, name=None, container_format=None, data=None, properties=None): params = {} headers = {} if name is not None: params['name'] = name if container_format is not None: params['container_format'] = container_format if properties is not None: params['properties'] = properties headers.update(self._image_meta_to_headers(params)) if data is not None: return self._update_with_data(image_id, headers, data) url = 'v1/images/%s' % image_id resp, body = self.put(url, data, headers) body = json.loads(body) return resp, body['image'] def delete_image(self, image_id): url = 'v1/images/%s' % image_id return self.delete(url) def image_list(self, **kwargs): url = 'v1/images' if len(kwargs) > 0: url += '?%s' % urllib.urlencode(kwargs) resp, body = self.get(url) body = json.loads(body) return resp, body['images'] def image_list_detail(self, properties=dict(), changes_since=None, **kwargs): url = 'v1/images/detail' params = {} for key, value in properties.items(): params['property-%s' % key] = value kwargs.update(params) if changes_since is not None: kwargs['changes-since'] = changes_since if len(kwargs) > 0: url += '?%s' % urllib.urlencode(kwargs) resp, body = self.get(url) body = json.loads(body) return resp, body['images'] def get_image_meta(self, image_id): url = 'v1/images/%s' % image_id resp, __ = self.head(url) body = self._image_meta_from_headers(resp) return resp, body def get_image(self, image_id): url = 'v1/images/%s' % image_id resp, body = self.get(url) return resp, body def is_resource_deleted(self, id): try: self.get_image(id) except exceptions.NotFound: return True return False def get_image_membership(self, image_id): url = 'v1/images/%s/members' % image_id resp, body = self.get(url) body = json.loads(body) return resp, body def get_shared_images(self, member_id): url = 'v1/shared-images/%s' % member_id resp, body = self.get(url) body = json.loads(body) return resp, body def add_member(self, member_id, image_id, can_share=False): url = 'v1/images/%s/members/%s' % (image_id, member_id) body = None if can_share: body = json.dumps({'member': {'can_share': True}}) resp, __ = self.put(url, body) return resp def delete_member(self, member_id, image_id): url = 'v1/images/%s/members/%s' % (image_id, member_id) resp, __ = self.delete(url) return resp def replace_membership_list(self, image_id, member_list): url = 'v1/images/%s/members' % image_id body = json.dumps({'membership': member_list}) resp, data = self.put(url, body) data = json.loads(data) return resp, data # NOTE(afazekas): just for the wait function def _get_image_status(self, image_id): resp, meta = self.get_image_meta(image_id) status = meta['status'] return status # NOTE(afazkas): Wait reinvented again. It is not in the correct layer def wait_for_image_status(self, image_id, status): """Waits for a Image to reach a given status.""" start_time = time.time() old_value = value = self._get_image_status(image_id) while True: dtime = time.time() - start_time time.sleep(self.build_interval) if value != old_value: LOG.info('Value transition from "%s" to "%s"' 'in %d second(s).', old_value, value, dtime) if value == status: return value if value == 'killed': raise exceptions.ImageKilledException(image_id=image_id, status=status) if dtime > self.build_timeout: message = ('Time Limit Exceeded! (%ds)' 'while waiting for %s, ' 'but we got %s.' % (self.build_timeout, status, value)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) old_value = value value = self._get_image_status(image_id) tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v1/json/__init__.py0000664000175000017500000000000012332757070026061 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/image/v1/__init__.py0000664000175000017500000000000012332757070025110 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/0000775000175000017500000000000012332757136023075 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/json/0000775000175000017500000000000012332757136024046 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/json/__init__.py0000664000175000017500000000000012332757070026142 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/json/network_client.py0000664000175000017500000002574212332757070027456 0ustar chuckchuck00000000000000# 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 json from tempest.common import rest_client from tempest.services.network import network_client_base class NetworkClientJSON(network_client_base.NetworkClientBase): """ Tempest REST client for Neutron. Uses v2 of the Neutron API, since the V1 API has been removed from the code base. Implements create, delete, update, list and show for the basic Neutron abstractions (networks, sub-networks, routers, ports and floating IP): Implements add/remove interface to router using subnet ID / port ID It also implements list, show, update and reset for OpenStack Networking quotas """ def get_rest_client(self, auth_provider): return rest_client.RestClient(auth_provider) def deserialize_single(self, body): return json.loads(body) def deserialize_list(self, body): res = json.loads(body) # expecting response in form # {'resources': [ res1, res2] } return res[res.keys()[0]] def serialize(self, data): return json.dumps(data) def serialize_list(self, data, root=None, item=None): return self.serialize(data) def update_quotas(self, tenant_id, **kwargs): put_body = {'quota': kwargs} body = json.dumps(put_body) uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id) resp, body = self.put(uri, body) body = json.loads(body) return resp, body['quota'] def reset_quotas(self, tenant_id): uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id) resp, body = self.delete(uri) return resp, body def create_router(self, name, admin_state_up=True, **kwargs): post_body = {'router': kwargs} post_body['router']['name'] = name post_body['router']['admin_state_up'] = admin_state_up body = json.dumps(post_body) uri = '%s/routers' % (self.uri_prefix) resp, body = self.post(uri, body) body = json.loads(body) return resp, body def _update_router(self, router_id, set_enable_snat, **kwargs): uri = '%s/routers/%s' % (self.uri_prefix, router_id) resp, body = self.get(uri) body = json.loads(body) update_body = {} update_body['name'] = kwargs.get('name', body['router']['name']) update_body['admin_state_up'] = kwargs.get( 'admin_state_up', body['router']['admin_state_up']) cur_gw_info = body['router']['external_gateway_info'] if cur_gw_info and not set_enable_snat: cur_gw_info.pop('enable_snat', None) update_body['external_gateway_info'] = kwargs.get( 'external_gateway_info', body['router']['external_gateway_info']) update_body = dict(router=update_body) update_body = json.dumps(update_body) resp, body = self.put(uri, update_body) body = json.loads(body) return resp, body def update_router(self, router_id, **kwargs): """Update a router leaving enable_snat to its default value.""" # If external_gateway_info contains enable_snat the request will fail # with 404 unless executed with admin client, and therefore we instruct # _update_router to not set this attribute # NOTE(salv-orlando): The above applies as long as Neutron's default # policy is to restrict enable_snat usage to admins only. return self._update_router(router_id, set_enable_snat=False, **kwargs) def update_router_with_snat_gw_info(self, router_id, **kwargs): """Update a router passing also the enable_snat attribute. This method must be execute with admin credentials, otherwise the API call will return a 404 error. """ return self._update_router(router_id, set_enable_snat=True, **kwargs) def add_router_interface_with_subnet_id(self, router_id, subnet_id): uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix, router_id) update_body = {"subnet_id": subnet_id} update_body = json.dumps(update_body) resp, body = self.put(uri, update_body) body = json.loads(body) return resp, body def add_router_interface_with_port_id(self, router_id, port_id): uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix, router_id) update_body = {"port_id": port_id} update_body = json.dumps(update_body) resp, body = self.put(uri, update_body) body = json.loads(body) return resp, body def remove_router_interface_with_subnet_id(self, router_id, subnet_id): uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix, router_id) update_body = {"subnet_id": subnet_id} update_body = json.dumps(update_body) resp, body = self.put(uri, update_body) body = json.loads(body) return resp, body def remove_router_interface_with_port_id(self, router_id, port_id): uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix, router_id) update_body = {"port_id": port_id} update_body = json.dumps(update_body) resp, body = self.put(uri, update_body) body = json.loads(body) return resp, body def associate_health_monitor_with_pool(self, health_monitor_id, pool_id): post_body = { "health_monitor": { "id": health_monitor_id, } } body = json.dumps(post_body) uri = '%s/lb/pools/%s/health_monitors' % (self.uri_prefix, pool_id) resp, body = self.post(uri, body) body = json.loads(body) return resp, body def disassociate_health_monitor_with_pool(self, health_monitor_id, pool_id): uri = '%s/lb/pools/%s/health_monitors/%s' % (self.uri_prefix, pool_id, health_monitor_id) resp, body = self.delete(uri) return resp, body def list_router_interfaces(self, uuid): uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid) resp, body = self.get(uri) body = json.loads(body) return resp, body def update_agent(self, agent_id, agent_info): """ :param agent_info: Agent update information. E.g {"admin_state_up": True} """ uri = '%s/agents/%s' % (self.uri_prefix, agent_id) agent = {"agent": agent_info} body = json.dumps(agent) resp, body = self.put(uri, body) body = json.loads(body) return resp, body def list_pools_hosted_by_one_lbaas_agent(self, agent_id): uri = '%s/agents/%s/loadbalancer-pools' % (self.uri_prefix, agent_id) resp, body = self.get(uri) body = json.loads(body) return resp, body def show_lbaas_agent_hosting_pool(self, pool_id): uri = ('%s/lb/pools/%s/loadbalancer-agent' % (self.uri_prefix, pool_id)) resp, body = self.get(uri) body = json.loads(body) return resp, body def list_routers_on_l3_agent(self, agent_id): uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id) resp, body = self.get(uri) body = json.loads(body) return resp, body def list_l3_agents_hosting_router(self, router_id): uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id) resp, body = self.get(uri) body = json.loads(body) return resp, body def add_router_to_l3_agent(self, agent_id, router_id): uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id) post_body = {"router_id": router_id} body = json.dumps(post_body) resp, body = self.post(uri, body) body = json.loads(body) return resp, body def remove_router_from_l3_agent(self, agent_id, router_id): uri = '%s/agents/%s/l3-routers/%s' % ( self.uri_prefix, agent_id, router_id) resp, body = self.delete(uri) return resp, body def list_dhcp_agent_hosting_network(self, network_id): uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id) resp, body = self.get(uri) body = json.loads(body) return resp, body def list_networks_hosted_by_one_dhcp_agent(self, agent_id): uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id) resp, body = self.get(uri) body = json.loads(body) return resp, body def remove_network_from_dhcp_agent(self, agent_id, network_id): uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id, network_id) resp, body = self.delete(uri) return resp, body def create_ikepolicy(self, name, **kwargs): post_body = { "ikepolicy": { "name": name, } } for key, val in kwargs.items(): post_body['ikepolicy'][key] = val body = json.dumps(post_body) uri = '%s/vpn/ikepolicies' % (self.uri_prefix) resp, body = self.post(uri, body) body = json.loads(body) return resp, body def update_extra_routes(self, router_id, nexthop, destination): uri = '%s/routers/%s' % (self.uri_prefix, router_id) put_body = { 'router': { 'routes': [{'nexthop': nexthop, "destination": destination}] } } body = json.dumps(put_body) resp, body = self.put(uri, body) body = json.loads(body) return resp, body def delete_extra_routes(self, router_id): uri = '%s/routers/%s' % (self.uri_prefix, router_id) null_routes = None put_body = { 'router': { 'routes': null_routes } } body = json.dumps(put_body) resp, body = self.put(uri, body) body = json.loads(body) return resp, body def list_lb_pool_stats(self, pool_id): uri = '%s/lb/pools/%s/stats' % (self.uri_prefix, pool_id) resp, body = self.get(uri) body = json.loads(body) return resp, body def add_dhcp_agent_to_network(self, agent_id, network_id): post_body = {'network_id': network_id} body = json.dumps(post_body) uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id) resp, body = self.post(uri, body) body = json.loads(body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/__init__.py0000664000175000017500000000000012332757070025171 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/xml/0000775000175000017500000000000012332757136023675 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/xml/__init__.py0000664000175000017500000000000012332757070025771 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/xml/network_client.py0000664000175000017500000002644512332757070027306 0ustar chuckchuck00000000000000# 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 lxml import etree import xml.etree.ElementTree as ET from tempest.common import rest_client from tempest.common import xml_utils as common from tempest.services.network import network_client_base as client_base class NetworkClientXML(client_base.NetworkClientBase): TYPE = "xml" # list of plurals used for xml serialization PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools', 'fixed_ips', 'extensions', 'extra_dhcp_opts', 'pools', 'health_monitors', 'vips', 'members'] def get_rest_client(self, auth_provider): rc = rest_client.RestClient(auth_provider) rc.TYPE = self.TYPE return rc def deserialize_list(self, body): return common.parse_array(etree.fromstring(body), self.PLURALS) def deserialize_single(self, body): return _root_tag_fetcher_and_xml_to_json_parse(body) def serialize(self, body): #TODO(enikanorov): implement better json to xml conversion # expecting the dict with single key root = body.keys()[0] post_body = common.Element(root) post_body.add_attr('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance') elements = set() for name, attr in body[root].items(): elt = self._get_element(name, attr) post_body.append(elt) if ":" in name: elements.add(name.split(":")[0]) if elements: self._add_namespaces(post_body, elements) return str(common.Document(post_body)) def serialize_list(self, body, root_name=None, item_name=None): # expecting dict in form # body = {'resources': [res_dict1, res_dict2, ...] post_body = common.Element(root_name) post_body.add_attr('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance') for item in body[body.keys()[0]]: elt = common.Element(item_name) for name, attr in item.items(): elt_content = self._get_element(name, attr) elt.append(elt_content) post_body.append(elt) return str(common.Document(post_body)) def _get_element(self, name, value): if value is None: xml_elem = common.Element(name) xml_elem.add_attr("xsi:nil", "true") return xml_elem elif isinstance(value, dict): dict_element = common.Element(name) for key, value in value.iteritems(): elem = self._get_element(key, value) dict_element.append(elem) return dict_element elif isinstance(value, list): list_element = common.Element(name) for element in value: elem = self._get_element(name[:-1], element) list_element.append(elem) return list_element else: return common.Element(name, value) def _add_namespaces(self, root, elements): for element in elements: root.add_attr('xmlns:%s' % element, common.NEUTRON_NAMESPACES[element]) def associate_health_monitor_with_pool(self, health_monitor_id, pool_id): uri = '%s/lb/pools/%s/health_monitors' % (self.uri_prefix, pool_id) post_body = common.Element("health_monitor") p1 = common.Element("id", health_monitor_id,) post_body.append(p1) resp, body = self.post(uri, str(common.Document(post_body))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def disassociate_health_monitor_with_pool(self, health_monitor_id, pool_id): uri = '%s/lb/pools/%s/health_monitors/%s' % (self.uri_prefix, pool_id, health_monitor_id) return self.delete(uri) def show_extension_details(self, ext_alias): uri = '%s/extensions/%s' % (self.uri_prefix, str(ext_alias)) resp, body = self.get(uri) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def create_router(self, name, **kwargs): uri = '%s/routers' % (self.uri_prefix) router = common.Element("router") router.append(common.Element("name", name)) common.deep_dict_to_xml(router, kwargs) resp, body = self.post(uri, str(common.Document(router))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def update_router(self, router_id, **kwargs): uri = '%s/routers/%s' % (self.uri_prefix, router_id) router = common.Element("router") for element, content in kwargs.iteritems(): router.append(common.Element(element, content)) resp, body = self.put(uri, str(common.Document(router))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def add_router_interface_with_subnet_id(self, router_id, subnet_id): uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix, router_id) subnet = common.Element("subnet_id", subnet_id) resp, body = self.put(uri, str(common.Document(subnet))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def add_router_interface_with_port_id(self, router_id, port_id): uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix, router_id) port = common.Element("port_id", port_id) resp, body = self.put(uri, str(common.Document(port))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def remove_router_interface_with_subnet_id(self, router_id, subnet_id): uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix, router_id) subnet = common.Element("subnet_id", subnet_id) resp, body = self.put(uri, str(common.Document(subnet))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def remove_router_interface_with_port_id(self, router_id, port_id): uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix, router_id) port = common.Element("port_id", port_id) resp, body = self.put(uri, str(common.Document(port))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def list_router_interfaces(self, uuid): uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid) resp, body = self.get(uri) ports = common.parse_array(etree.fromstring(body), self.PLURALS) ports = {"ports": ports} return resp, ports def update_agent(self, agent_id, agent_info): uri = '%s/agents/%s' % (self.uri_prefix, agent_id) agent = common.Element('agent') for (key, value) in agent_info.items(): p = common.Element(key, value) agent.append(p) resp, body = self.put(uri, str(common.Document(agent))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def list_pools_hosted_by_one_lbaas_agent(self, agent_id): uri = '%s/agents/%s/loadbalancer-pools' % (self.uri_prefix, agent_id) resp, body = self.get(uri) pools = common.parse_array(etree.fromstring(body)) body = {'pools': pools} return resp, body def show_lbaas_agent_hosting_pool(self, pool_id): uri = ('%s/lb/pools/%s/loadbalancer-agent' % (self.uri_prefix, pool_id)) resp, body = self.get(uri) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def list_routers_on_l3_agent(self, agent_id): uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id) resp, body = self.get(uri) routers = common.parse_array(etree.fromstring(body)) body = {'routers': routers} return resp, body def list_l3_agents_hosting_router(self, router_id): uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id) resp, body = self.get(uri) agents = common.parse_array(etree.fromstring(body)) body = {'agents': agents} return resp, body def add_router_to_l3_agent(self, agent_id, router_id): uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id) router = (common.Element("router_id", router_id)) resp, body = self.post(uri, str(common.Document(router))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def remove_router_from_l3_agent(self, agent_id, router_id): uri = '%s/agents/%s/l3-routers/%s' % ( self.uri_prefix, agent_id, router_id) resp, body = self.delete(uri) return resp, body def list_dhcp_agent_hosting_network(self, network_id): uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id) resp, body = self.get(uri) agents = common.parse_array(etree.fromstring(body)) body = {'agents': agents} return resp, body def list_networks_hosted_by_one_dhcp_agent(self, agent_id): uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id) resp, body = self.get(uri) networks = common.parse_array(etree.fromstring(body)) body = {'networks': networks} return resp, body def remove_network_from_dhcp_agent(self, agent_id, network_id): uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id, network_id) resp, body = self.delete(uri) return resp, body def list_lb_pool_stats(self, pool_id): uri = '%s/lb/pools/%s/stats' % (self.uri_prefix, pool_id) resp, body = self.get(uri) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def add_dhcp_agent_to_network(self, agent_id, network_id): uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id) network = common.Element("network_id", network_id) resp, body = self.post(uri, str(common.Document(network))) body = _root_tag_fetcher_and_xml_to_json_parse(body) return resp, body def _root_tag_fetcher_and_xml_to_json_parse(xml_returned_body): body = ET.fromstring(xml_returned_body) root_tag = body.tag if root_tag.startswith("{"): ns, root_tag = root_tag.split("}", 1) body = common.xml_to_json(etree.fromstring(xml_returned_body), NetworkClientXML.PLURALS) nil = '{http://www.w3.org/2001/XMLSchema-instance}nil' for key, val in body.iteritems(): if isinstance(val, dict): if (nil in val and val[nil] == 'true'): body[key] = None body = {root_tag: body} return body tempest-2014.1.dev4108.gf22b6cc/tempest/services/network/network_client_base.py0000664000175000017500000001706512332757070027476 0ustar chuckchuck00000000000000# 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 time import urllib from tempest import config from tempest import exceptions CONF = config.CONF # the following map is used to construct proper URI # for the given neutron resource service_resource_prefix_map = { 'networks': '', 'subnets': '', 'ports': '', 'pools': 'lb', 'vips': 'lb', 'health_monitors': 'lb', 'members': 'lb', 'vpnservices': 'vpn', 'ikepolicies': 'vpn', 'metering_labels': 'metering', 'metering_label_rules': 'metering', 'firewall_rules': 'fw', 'firewall_policies': 'fw', 'firewalls': 'fw' } # The following list represents resource names that do not require # changing underscore to a hyphen hyphen_exceptions = ["health_monitors", "firewall_rules", "firewall_policies"] # map from resource name to a plural name # needed only for those which can't be constructed as name + 's' resource_plural_map = { 'security_groups': 'security_groups', 'security_group_rules': 'security_group_rules', 'ikepolicy': 'ikepolicies', 'quotas': 'quotas', 'firewall_policy': 'firewall_policies' } class NetworkClientBase(object): def __init__(self, auth_provider): self.rest_client = self.get_rest_client( auth_provider) self.rest_client.service = CONF.network.catalog_type self.version = '2.0' self.uri_prefix = "v%s" % (self.version) self.build_timeout = CONF.network.build_timeout self.build_interval = CONF.network.build_interval def get_rest_client(self, auth_provider): raise NotImplementedError def post(self, uri, body, headers=None): return self.rest_client.post(uri, body, headers) def put(self, uri, body, headers=None): return self.rest_client.put(uri, body, headers) def get(self, uri, headers=None): return self.rest_client.get(uri, headers) def delete(self, uri, headers=None): return self.rest_client.delete(uri, headers) def deserialize_list(self, body): raise NotImplementedError def deserialize_single(self, body): raise NotImplementedError def get_uri(self, plural_name): # get service prefix from resource name service_prefix = service_resource_prefix_map.get( plural_name) if plural_name not in hyphen_exceptions: plural_name = plural_name.replace("_", "-") if service_prefix: uri = '%s/%s/%s' % (self.uri_prefix, service_prefix, plural_name) else: uri = '%s/%s' % (self.uri_prefix, plural_name) return uri def pluralize(self, resource_name): # get plural from map or just add 's' return resource_plural_map.get(resource_name, resource_name + 's') def _lister(self, plural_name): def _list(**filters): uri = self.get_uri(plural_name) if filters: uri += '?' + urllib.urlencode(filters, doseq=1) resp, body = self.get(uri) result = {plural_name: self.deserialize_list(body)} return resp, result return _list def _deleter(self, resource_name): def _delete(resource_id): plural = self.pluralize(resource_name) uri = '%s/%s' % (self.get_uri(plural), resource_id) return self.delete(uri) return _delete def _shower(self, resource_name): def _show(resource_id, **fields): # fields is a dict which key is 'fields' and value is a # list of field's name. An example: # {'fields': ['id', 'name']} plural = self.pluralize(resource_name) uri = '%s/%s' % (self.get_uri(plural), resource_id) if fields: uri += '?' + urllib.urlencode(fields, doseq=1) resp, body = self.get(uri) body = self.deserialize_single(body) return resp, body return _show def _creater(self, resource_name): def _create(**kwargs): plural = self.pluralize(resource_name) uri = self.get_uri(plural) post_data = self.serialize({resource_name: kwargs}) resp, body = self.post(uri, post_data) body = self.deserialize_single(body) return resp, body return _create def _updater(self, resource_name): def _update(res_id, **kwargs): plural = self.pluralize(resource_name) uri = '%s/%s' % (self.get_uri(plural), res_id) post_data = self.serialize({resource_name: kwargs}) resp, body = self.put(uri, post_data) body = self.deserialize_single(body) return resp, body return _update def __getattr__(self, name): method_prefixes = ["list_", "delete_", "show_", "create_", "update_"] method_functors = [self._lister, self._deleter, self._shower, self._creater, self._updater] for index, prefix in enumerate(method_prefixes): prefix_len = len(prefix) if name[:prefix_len] == prefix: return method_functors[index](name[prefix_len:]) raise AttributeError(name) # Common methods that are hard to automate def create_bulk_network(self, count, names): network_list = list() for i in range(count): network_list.append({'name': names[i]}) post_data = {'networks': network_list} body = self.serialize_list(post_data, "networks", "network") uri = self.get_uri("networks") resp, body = self.post(uri, body) body = {'networks': self.deserialize_list(body)} return resp, body def create_bulk_subnet(self, subnet_list): post_data = {'subnets': subnet_list} body = self.serialize_list(post_data, 'subnets', 'subnet') uri = self.get_uri('subnets') resp, body = self.post(uri, body) body = {'subnets': self.deserialize_list(body)} return resp, body def create_bulk_port(self, port_list): post_data = {'ports': port_list} body = self.serialize_list(post_data, 'ports', 'port') uri = self.get_uri('ports') resp, body = self.post(uri, body) body = {'ports': self.deserialize_list(body)} return resp, body def wait_for_resource_deletion(self, resource_type, id): """Waits for a resource to be deleted.""" start_time = int(time.time()) while True: if self.is_resource_deleted(resource_type, id): return if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def is_resource_deleted(self, resource_type, id): method = 'show_' + resource_type try: getattr(self, method)(id) except AttributeError: raise Exception("Unknown resource type %s " % resource_type) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/baremetal/0000775000175000017500000000000012332757136023340 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/baremetal/__init__.py0000664000175000017500000000000012332757070025434 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/baremetal/v1/0000775000175000017500000000000012332757136023666 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/baremetal/v1/client_json.py0000664000175000017500000000170312332757070026545 0ustar chuckchuck00000000000000# 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 json from tempest.services.baremetal.v1 import base_v1 class BaremetalClientJSON(base_v1.BaremetalClientV1): """Tempest REST client for Ironic JSON API v1.""" def __init__(self, auth_provider): super(BaremetalClientJSON, self).__init__(auth_provider) self.serialize = lambda obj_type, obj_body: json.dumps(obj_body) self.deserialize = json.loads tempest-2014.1.dev4108.gf22b6cc/tempest/services/baremetal/v1/__init__.py0000664000175000017500000000000012332757070025762 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/baremetal/v1/base_v1.py0000664000175000017500000001623412332757070025563 0ustar chuckchuck00000000000000# 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 tempest.services.baremetal import base class BaremetalClientV1(base.BaremetalClient): """ Base Tempest REST client for Ironic API v1. Specific implementations must implement serialize and deserialize methods in order to send requests to Ironic. """ def __init__(self, auth_provider): super(BaremetalClientV1, self).__init__(auth_provider) self.version = '1' self.uri_prefix = 'v%s' % self.version @base.handle_errors def list_nodes(self): """List all existing nodes.""" return self._list_request('nodes') @base.handle_errors def list_chassis(self): """List all existing chassis.""" return self._list_request('chassis') @base.handle_errors def list_ports(self, **kwargs): """List all existing ports.""" return self._list_request('ports', **kwargs) @base.handle_errors def list_nodestates(self, uuid): """List all existing states.""" return self._list_request('/nodes/%s/states' % uuid) @base.handle_errors def list_ports_detail(self): """Details list all existing ports.""" return self._list_request('/ports/detail') @base.handle_errors def list_drivers(self): """List all existing drivers.""" return self._list_request('drivers') @base.handle_errors def show_node(self, uuid): """ Gets a specific node. :param uuid: Unique identifier of the node in UUID format. :return: Serialized node as a dictionary. """ return self._show_request('nodes', uuid) @base.handle_errors def show_chassis(self, uuid): """ Gets a specific chassis. :param uuid: Unique identifier of the chassis in UUID format. :return: Serialized chassis as a dictionary. """ return self._show_request('chassis', uuid) @base.handle_errors def show_port(self, uuid): """ Gets a specific port. :param uuid: Unique identifier of the port in UUID format. :return: Serialized port as a dictionary. """ return self._show_request('ports', uuid) @base.handle_errors def create_node(self, chassis_id, **kwargs): """ Create a baremetal node with the specified parameters. :param cpu_arch: CPU architecture of the node. Default: x86_64. :param cpu_num: Number of CPUs. Default: 8. :param storage: Disk size. Default: 1024. :param memory: Available RAM. Default: 4096. :param driver: Driver name. Default: "fake" :return: A tuple with the server response and the created node. """ node = {'chassis_uuid': chassis_id, 'properties': {'cpu_arch': kwargs.get('cpu_arch', 'x86_64'), 'cpu_num': kwargs.get('cpu_num', 8), 'storage': kwargs.get('storage', 1024), 'memory': kwargs.get('memory', 4096)}, 'driver': kwargs.get('driver', 'fake')} return self._create_request('nodes', 'node', node) @base.handle_errors def create_chassis(self, **kwargs): """ Create a chassis with the specified parameters. :param description: The description of the chassis. Default: test-chassis :return: A tuple with the server response and the created chassis. """ chassis = {'description': kwargs.get('description', 'test-chassis')} return self._create_request('chassis', 'chassis', chassis) @base.handle_errors def create_port(self, node_id, **kwargs): """ Create a port with the specified parameters. :param node_id: The ID of the node which owns the port. :param address: MAC address of the port. :param extra: Meta data of the port. Default: {'foo': 'bar'}. :param uuid: UUID of the port. :return: A tuple with the server response and the created port. """ port = {'extra': kwargs.get('extra', {'foo': 'bar'}), 'uuid': kwargs['uuid']} if node_id is not None: port['node_uuid'] = node_id if kwargs['address'] is not None: port['address'] = kwargs['address'] return self._create_request('ports', 'port', port) @base.handle_errors def delete_node(self, uuid): """ Deletes a node having the specified UUID. :param uuid: The unique identifier of the node. :return: A tuple with the server response and the response body. """ return self._delete_request('nodes', uuid) @base.handle_errors def delete_chassis(self, uuid): """ Deletes a chassis having the specified UUID. :param uuid: The unique identifier of the chassis. :return: A tuple with the server response and the response body. """ return self._delete_request('chassis', uuid) @base.handle_errors def delete_port(self, uuid): """ Deletes a port having the specified UUID. :param uuid: The unique identifier of the port. :return: A tuple with the server response and the response body. """ return self._delete_request('ports', uuid) @base.handle_errors def update_node(self, uuid, **kwargs): """ Update the specified node. :param uuid: The unique identifier of the node. :return: A tuple with the server response and the updated node. """ node_attributes = ('properties/cpu_arch', 'properties/cpu_num', 'properties/storage', 'properties/memory', 'driver') patch = self._make_patch(node_attributes, **kwargs) return self._patch_request('nodes', uuid, patch) @base.handle_errors def update_chassis(self, uuid, **kwargs): """ Update the specified chassis. :param uuid: The unique identifier of the chassis. :return: A tuple with the server response and the updated chassis. """ chassis_attributes = ('description',) patch = self._make_patch(chassis_attributes, **kwargs) return self._patch_request('chassis', uuid, patch) @base.handle_errors def update_port(self, uuid, patch): """ Update the specified port. :param uuid: The unique identifier of the port. :param patch: List of dicts representing json patches. :return: A tuple with the server response and the updated port. """ return self._patch_request('ports', uuid, patch) tempest-2014.1.dev4108.gf22b6cc/tempest/services/baremetal/base.py0000664000175000017500000001436412332757070024631 0ustar chuckchuck00000000000000# 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 functools import json import urllib import six from tempest.common import rest_client from tempest import config CONF = config.CONF def handle_errors(f): """A decorator that allows to ignore certain types of errors.""" @functools.wraps(f) def wrapper(*args, **kwargs): param_name = 'ignore_errors' ignored_errors = kwargs.get(param_name, tuple()) if param_name in kwargs: del kwargs[param_name] try: return f(*args, **kwargs) except ignored_errors: # Silently ignore errors pass return wrapper class BaremetalClient(rest_client.RestClient): """ Base Tempest REST client for Ironic API. """ def __init__(self, auth_provider): super(BaremetalClient, self).__init__(auth_provider) self.service = CONF.baremetal.catalog_type self.uri_prefix = '' def serialize(self, object_type, object_dict): """Serialize an Ironic object.""" raise NotImplementedError def deserialize(self, object_str): """Deserialize an Ironic object.""" raise NotImplementedError def _get_uri(self, resource_name, uuid=None, permanent=False): """ Get URI for a specific resource or object. :param resource_name: The name of the REST resource, e.g., 'nodes'. :param uuid: The unique identifier of an object in UUID format. :return: Relative URI for the resource or object. """ prefix = self.uri_prefix if not permanent else '' return '{pref}/{res}{uuid}'.format(pref=prefix, res=resource_name, uuid='/%s' % uuid if uuid else '') def _make_patch(self, allowed_attributes, **kw): """ Create a JSON patch according to RFC 6902. :param allowed_attributes: An iterable object that contains a set of allowed attributes for an object. :param **kw: Attributes and new values for them. :return: A JSON path that sets values of the specified attributes to the new ones. """ def get_change(kw, path='/'): for name, value in six.iteritems(kw): if isinstance(value, dict): for ch in get_change(value, path + '%s/' % name): yield ch else: yield {'path': path + name, 'value': value, 'op': 'replace'} patch = [ch for ch in get_change(kw) if ch['path'].lstrip('/') in allowed_attributes] return patch def _list_request(self, resource, permanent=False, **kwargs): """ Get the list of objects of the specified type. :param resource: The name of the REST resource, e.g., 'nodes'. "param **kw: Parameters for the request. :return: A tuple with the server response and deserialized JSON list of objects """ uri = self._get_uri(resource, permanent=permanent) if kwargs: uri += "?%s" % urllib.urlencode(kwargs) resp, body = self.get(uri) return resp, self.deserialize(body) def _show_request(self, resource, uuid, permanent=False): """ Gets a specific object of the specified type. :param uuid: Unique identifier of the object in UUID format. :return: Serialized object as a dictionary. """ uri = self._get_uri(resource, uuid=uuid, permanent=permanent) resp, body = self.get(uri) return resp, self.deserialize(body) def _create_request(self, resource, object_type, object_dict): """ Create an object of the specified type. :param resource: The name of the REST resource, e.g., 'nodes'. :param object_dict: A Python dict that represents an object of the specified type. :return: A tuple with the server response and the deserialized created object. """ body = self.serialize(object_type, object_dict) uri = self._get_uri(resource) resp, body = self.post(uri, body=body) return resp, self.deserialize(body) def _delete_request(self, resource, uuid): """ Delete specified object. :param resource: The name of the REST resource, e.g., 'nodes'. :param uuid: The unique identifier of an object in UUID format. :return: A tuple with the server response and the response body. """ uri = self._get_uri(resource, uuid) resp, body = self.delete(uri) return resp, body def _patch_request(self, resource, uuid, patch_object): """ Update specified object with JSON-patch. :param resource: The name of the REST resource, e.g., 'nodes'. :param uuid: The unique identifier of an object in UUID format. :return: A tuple with the server response and the serialized patched object. """ uri = self._get_uri(resource, uuid) patch_body = json.dumps(patch_object) resp, body = self.patch(uri, body=patch_body) return resp, self.deserialize(body) @handle_errors def get_api_description(self): """Retrieves all versions of the Ironic API.""" return self._list_request('', permanent=True) @handle_errors def get_version_description(self, version='v1'): """ Retrieves the desctription of the API. :param version: The version of the API. Default: 'v1'. :return: Serialized description of API resources. """ return self._list_request(version, permanent=True) tempest-2014.1.dev4108.gf22b6cc/tempest/services/database/0000775000175000017500000000000012332757136023150 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/database/json/0000775000175000017500000000000012332757136024121 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/database/json/__init__.py0000664000175000017500000000000012332757070026215 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/database/json/flavors_client.py0000664000175000017500000000247112332757070027506 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from tempest.common import rest_client from tempest import config CONF = config.CONF class DatabaseFlavorsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(DatabaseFlavorsClientJSON, self).__init__(auth_provider) self.service = CONF.database.catalog_type def list_db_flavors(self, params=None): url = 'flavors' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) return resp, self._parse_resp(body) def get_db_flavor_details(self, db_flavor_id): resp, body = self.get("flavors/%s" % str(db_flavor_id)) return resp, self._parse_resp(body) tempest-2014.1.dev4108.gf22b6cc/tempest/services/database/__init__.py0000664000175000017500000000000012332757070025244 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/0000775000175000017500000000000012332757136022713 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/0000775000175000017500000000000012332757136023664 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/snapshots_client.py0000664000175000017500000001617512332757070027625 0ustar chuckchuck00000000000000# 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 json import time import urllib from tempest.common import rest_client from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class SnapshotsClientJSON(rest_client.RestClient): """Client class to send CRUD Volume API requests.""" def __init__(self, auth_provider): super(SnapshotsClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def list_snapshots(self, params=None): """List all the snapshot.""" url = 'snapshots' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['snapshots'] def list_snapshots_with_detail(self, params=None): """List the details of all snapshots.""" url = 'snapshots/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['snapshots'] def get_snapshot(self, snapshot_id): """Returns the details of a single snapshot.""" url = "snapshots/%s" % str(snapshot_id) resp, body = self.get(url) body = json.loads(body) return resp, body['snapshot'] def create_snapshot(self, volume_id, **kwargs): """ Creates a new snapshot. volume_id(Required): id of the volume. force: Create a snapshot even if the volume attached (Default=False) display_name: Optional snapshot Name. display_description: User friendly snapshot description. """ post_body = {'volume_id': volume_id} post_body.update(kwargs) post_body = json.dumps({'snapshot': post_body}) resp, body = self.post('snapshots', post_body) body = json.loads(body) return resp, body['snapshot'] def update_snapshot(self, snapshot_id, **kwargs): """Updates a snapshot.""" put_body = json.dumps({'snapshot': kwargs}) resp, body = self.put('snapshots/%s' % snapshot_id, put_body) body = json.loads(body) return resp, body['snapshot'] # NOTE(afazekas): just for the wait function def _get_snapshot_status(self, snapshot_id): resp, body = self.get_snapshot(snapshot_id) status = body['status'] # NOTE(afazekas): snapshot can reach an "error" # state in a "normal" lifecycle if (status == 'error'): raise exceptions.SnapshotBuildErrorException( snapshot_id=snapshot_id) return status # NOTE(afazkas): Wait reinvented again. It is not in the correct layer def wait_for_snapshot_status(self, snapshot_id, status): """Waits for a Snapshot to reach a given status.""" start_time = time.time() old_value = value = self._get_snapshot_status(snapshot_id) while True: dtime = time.time() - start_time time.sleep(self.build_interval) if value != old_value: LOG.info('Value transition from "%s" to "%s"' 'in %d second(s).', old_value, value, dtime) if (value == status): return value if dtime > self.build_timeout: message = ('Time Limit Exceeded! (%ds)' 'while waiting for %s, ' 'but we got %s.' % (self.build_timeout, status, value)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) old_value = value value = self._get_snapshot_status(snapshot_id) def delete_snapshot(self, snapshot_id): """Delete Snapshot.""" return self.delete("snapshots/%s" % str(snapshot_id)) def is_resource_deleted(self, id): try: self.get_snapshot(id) except exceptions.NotFound: return True return False def reset_snapshot_status(self, snapshot_id, status): """Reset the specified snapshot's status.""" post_body = json.dumps({'os-reset_status': {"status": status}}) resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body) return resp, body def update_snapshot_status(self, snapshot_id, status, progress): """Update the specified snapshot's status.""" post_body = { 'status': status, 'progress': progress } post_body = json.dumps({'os-update_snapshot_status': post_body}) url = 'snapshots/%s/action' % str(snapshot_id) resp, body = self.post(url, post_body) return resp, body def create_snapshot_metadata(self, snapshot_id, metadata): """Create metadata for the snapshot.""" put_body = json.dumps({'metadata': metadata}) url = "snapshots/%s/metadata" % str(snapshot_id) resp, body = self.post(url, put_body) body = json.loads(body) return resp, body['metadata'] def get_snapshot_metadata(self, snapshot_id): """Get metadata of the snapshot.""" url = "snapshots/%s/metadata" % str(snapshot_id) resp, body = self.get(url) body = json.loads(body) return resp, body['metadata'] def update_snapshot_metadata(self, snapshot_id, metadata): """Update metadata for the snapshot.""" put_body = json.dumps({'metadata': metadata}) url = "snapshots/%s/metadata" % str(snapshot_id) resp, body = self.put(url, put_body) body = json.loads(body) return resp, body['metadata'] def update_snapshot_metadata_item(self, snapshot_id, id, meta_item): """Update metadata item for the snapshot.""" put_body = json.dumps({'meta': meta_item}) url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id)) resp, body = self.put(url, put_body) body = json.loads(body) return resp, body['meta'] def delete_snapshot_metadata_item(self, snapshot_id, id): """Delete metadata item for the snapshot.""" url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id)) resp, body = self.delete(url) return resp, body def force_delete_snapshot(self, snapshot_id): """Force Delete Snapshot.""" post_body = json.dumps({'os-force_delete': {}}) resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/volumes_client.py0000664000175000017500000002563012332757070027271 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time import urllib from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class VolumesClientJSON(rest_client.RestClient): """ Client class to send CRUD Volume API requests to a Cinder endpoint """ def __init__(self, auth_provider): super(VolumesClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def get_attachment_from_volume(self, volume): """Return the element 'attachment' from input volumes.""" return volume['attachments'][0] def list_volumes(self, params=None): """List all the volumes created.""" url = 'volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def list_volumes_with_detail(self, params=None): """List the details of all volumes.""" url = 'volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['volume'] def create_volume(self, size=None, **kwargs): """ Creates a new Volume. size: Size of volume in GB. Following optional keyword arguments are accepted: display_name: Optional Volume Name. metadata: A dictionary of values to be used as metadata. volume_type: Optional Name of volume_type for the volume snapshot_id: When specified the volume is created from this snapshot imageRef: When specified the volume is created from this image """ # for bug #1293885: # If no size specified, read volume size from CONF if size is None: size = CONF.volume.volume_size post_body = {'size': size} post_body.update(kwargs) post_body = json.dumps({'volume': post_body}) resp, body = self.post('volumes', post_body) body = json.loads(body) return resp, body['volume'] def update_volume(self, volume_id, **kwargs): """Updates the Specified Volume.""" put_body = json.dumps({'volume': kwargs}) resp, body = self.put('volumes/%s' % volume_id, put_body) body = json.loads(body) return resp, body['volume'] def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("volumes/%s" % str(volume_id)) def upload_volume(self, volume_id, image_name, disk_format): """Uploads a volume in Glance.""" post_body = { 'image_name': image_name, 'disk_format': disk_format } post_body = json.dumps({'os-volume_upload_image': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) body = json.loads(body) return resp, body['os-volume_upload_image'] def attach_volume(self, volume_id, instance_uuid, mountpoint): """Attaches a volume to a given instance on a given mountpoint.""" post_body = { 'instance_uuid': instance_uuid, 'mountpoint': mountpoint, } post_body = json.dumps({'os-attach': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def detach_volume(self, volume_id): """Detaches a volume from an instance.""" post_body = {} post_body = json.dumps({'os-detach': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def reserve_volume(self, volume_id): """Reserves a volume.""" post_body = {} post_body = json.dumps({'os-reserve': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def unreserve_volume(self, volume_id): """Restore a reserved volume .""" post_body = {} post_body = json.dumps({'os-unreserve': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_name = body['display_name'] volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = ('Volume %s failed to reach %s status within ' 'the required time (%s s).' % (volume_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False def extend_volume(self, volume_id, extend_size): """Extend a volume.""" post_body = { 'new_size': extend_size } post_body = json.dumps({'os-extend': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def reset_volume_status(self, volume_id, status): """Reset the Specified Volume's Status.""" post_body = json.dumps({'os-reset_status': {"status": status}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def volume_begin_detaching(self, volume_id): """Volume Begin Detaching.""" post_body = json.dumps({'os-begin_detaching': {}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def volume_roll_detaching(self, volume_id): """Volume Roll Detaching.""" post_body = json.dumps({'os-roll_detaching': {}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def create_volume_transfer(self, vol_id, display_name=None): """Create a volume transfer.""" post_body = { 'volume_id': vol_id } if display_name: post_body['name'] = display_name post_body = json.dumps({'transfer': post_body}) resp, body = self.post('os-volume-transfer', post_body) body = json.loads(body) return resp, body['transfer'] def get_volume_transfer(self, transfer_id): """Returns the details of a volume transfer.""" url = "os-volume-transfer/%s" % str(transfer_id) resp, body = self.get(url) body = json.loads(body) return resp, body['transfer'] def list_volume_transfers(self, params=None): """List all the volume transfers created.""" url = 'os-volume-transfer' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['transfers'] def delete_volume_transfer(self, transfer_id): """Delete a volume transfer.""" return self.delete("os-volume-transfer/%s" % str(transfer_id)) def accept_volume_transfer(self, transfer_id, transfer_auth_key): """Accept a volume transfer.""" post_body = { 'auth_key': transfer_auth_key, } url = 'os-volume-transfer/%s/accept' % transfer_id post_body = json.dumps({'accept': post_body}) resp, body = self.post(url, post_body) body = json.loads(body) return resp, body['transfer'] def update_volume_readonly(self, volume_id, readonly): """Update the Specified Volume readonly.""" post_body = { 'readonly': readonly } post_body = json.dumps({'os-update_readonly_flag': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def force_delete_volume(self, volume_id): """Force Delete Volume.""" post_body = json.dumps({'os-force_delete': {}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def create_volume_metadata(self, volume_id, metadata): """Create metadata for the volume.""" put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % str(volume_id) resp, body = self.post(url, put_body) body = json.loads(body) return resp, body['metadata'] def get_volume_metadata(self, volume_id): """Get metadata of the volume.""" url = "volumes/%s/metadata" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['metadata'] def update_volume_metadata(self, volume_id, metadata): """Update metadata for the volume.""" put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % str(volume_id) resp, body = self.put(url, put_body) body = json.loads(body) return resp, body['metadata'] def update_volume_metadata_item(self, volume_id, id, meta_item): """Update metadata item for the volume.""" put_body = json.dumps({'meta': meta_item}) url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) resp, body = self.put(url, put_body) body = json.loads(body) return resp, body['meta'] def delete_volume_metadata_item(self, volume_id, id): """Delete metadata item for the volume.""" url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) resp, body = self.delete(url) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/backups_client.py0000664000175000017500000000702612332757070027226 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class BackupsClientJSON(rest_client.RestClient): """ Client class to send CRUD Volume backup API requests to a Cinder endpoint """ def __init__(self, auth_provider): super(BackupsClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def create_backup(self, volume_id, container=None, name=None, description=None): """Creates a backup of volume.""" post_body = {'volume_id': volume_id} if container: post_body['container'] = container if name: post_body['name'] = name if description: post_body['description'] = description post_body = json.dumps({'backup': post_body}) resp, body = self.post('backups', post_body) body = json.loads(body) return resp, body['backup'] def restore_backup(self, backup_id, volume_id=None): """Restore volume from backup.""" post_body = {'volume_id': volume_id} post_body = json.dumps({'restore': post_body}) resp, body = self.post('backups/%s/restore' % (backup_id), post_body) body = json.loads(body) return resp, body['restore'] def delete_backup(self, backup_id): """Delete a backup of volume.""" resp, body = self.delete('backups/%s' % (str(backup_id))) return resp, body def get_backup(self, backup_id): """Returns the details of a single backup.""" url = "backups/%s" % str(backup_id) resp, body = self.get(url) body = json.loads(body) return resp, body['backup'] def list_backups_with_detail(self): """Information for all the tenant's backups.""" url = "backups/detail" resp, body = self.get(url) body = json.loads(body) return resp, body['backups'] def wait_for_backup_status(self, backup_id, status): """Waits for a Backup to reach a given status.""" resp, body = self.get_backup(backup_id) backup_status = body['status'] start = int(time.time()) while backup_status != status: time.sleep(self.build_interval) resp, body = self.get_backup(backup_id) backup_status = body['status'] if backup_status == 'error': raise exceptions.VolumeBackupException(backup_id=backup_id) if int(time.time()) - start >= self.build_timeout: message = ('Volume backup %s failed to reach %s status within ' 'the required time (%s s).' % (backup_id, status, self.build_timeout)) raise exceptions.TimeoutException(message) tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/__init__.py0000664000175000017500000000000012332757070025760 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/admin/0000775000175000017500000000000012332757136024754 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/admin/volume_services_client.py0000664000175000017500000000227212332757070032076 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.common import rest_client from tempest import config CONF = config.CONF class VolumesServicesClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(VolumesServicesClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type def list_services(self, params=None): url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['services'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/admin/__init__.py0000664000175000017500000000000012332757070027050 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/admin/volume_hosts_client.py0000664000175000017500000000263412332757070031415 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.common import rest_client from tempest import config CONF = config.CONF class VolumeHostsClientJSON(rest_client.RestClient): """ Client class to send CRUD Volume Hosts API requests to a Cinder endpoint """ def __init__(self, auth_provider): super(VolumeHostsClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def list_hosts(self, params=None): """Lists all hosts.""" url = 'os-hosts' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['hosts'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/admin/volume_quotas_client.py0000664000175000017500000000501512332757070031565 0ustar chuckchuck00000000000000# Copyright (C) 2014 eNovance SAS # # Author: Sylvain Baubeau # # 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 urllib from tempest.openstack.common import jsonutils from tempest.common import rest_client from tempest import config CONF = config.CONF class VolumeQuotasClientJSON(rest_client.RestClient): """ Client class to send CRUD Volume Quotas API requests to a Cinder endpoint """ TYPE = "json" def __init__(self, auth_provider): super(VolumeQuotasClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def get_default_quota_set(self, tenant_id): """List the default volume quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % tenant_id resp, body = self.get(url) return resp, self._parse_resp(body) def get_quota_set(self, tenant_id, params=None): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) return resp, self._parse_resp(body) def get_quota_usage(self, tenant_id): """List the quota set for a tenant.""" resp, body = self.get_quota_set(tenant_id, params={'usage': True}) return resp, body def update_quota_set(self, tenant_id, gigabytes=None, volumes=None, snapshots=None): post_body = {} if gigabytes is not None: post_body['gigabytes'] = gigabytes if volumes is not None: post_body['volumes'] = volumes if snapshots is not None: post_body['snapshots'] = snapshots post_body = jsonutils.dumps({'quota_set': post_body}) resp, body = self.put('os-quota-sets/%s' % tenant_id, post_body) return resp, self._parse_resp(body) tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/admin/volume_types_client.py0000664000175000017500000001310312332757070031412 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.common import rest_client from tempest import config CONF = config.CONF class VolumeTypesClientJSON(rest_client.RestClient): """ Client class to send CRUD Volume Types API requests to a Cinder endpoint """ def __init__(self, auth_provider): super(VolumeTypesClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def list_volume_types(self, params=None): """List all the volume_types created.""" url = 'types' if params is not None: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volume_types'] def get_volume_type(self, volume_id): """Returns the details of a single volume_type.""" url = "types/%s" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['volume_type'] def create_volume_type(self, name, **kwargs): """ Creates a new Volume_type. name(Required): Name of volume_type. Following optional keyword arguments are accepted: extra_specs: A dictionary of values to be used as extra_specs. """ post_body = { 'name': name, 'extra_specs': kwargs.get('extra_specs'), } post_body = json.dumps({'volume_type': post_body}) resp, body = self.post('types', post_body) body = json.loads(body) return resp, body['volume_type'] def delete_volume_type(self, volume_id): """Deletes the Specified Volume_type.""" return self.delete("types/%s" % str(volume_id)) def list_volume_types_extra_specs(self, vol_type_id, params=None): """List all the volume_types extra specs created.""" url = 'types/%s/extra_specs' % str(vol_type_id) if params is not None: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['extra_specs'] def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name): """Returns the details of a single volume_type extra spec.""" url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) resp, body = self.get(url) body = json.loads(body) return resp, body def create_volume_type_extra_specs(self, vol_type_id, extra_spec): """ Creates a new Volume_type extra spec. vol_type_id: Id of volume_type. extra_specs: A dictionary of values to be used as extra_specs. """ url = "types/%s/extra_specs" % str(vol_type_id) post_body = json.dumps({'extra_specs': extra_spec}) resp, body = self.post(url, post_body) body = json.loads(body) return resp, body['extra_specs'] def delete_volume_type_extra_specs(self, vol_id, extra_spec_name): """Deletes the Specified Volume_type extra spec.""" return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)), str(extra_spec_name))) def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name, extra_spec): """ Update a volume_type extra spec. vol_type_id: Id of volume_type. extra_spec_name: Name of the extra spec to be updated. extra_spec: A dictionary of with key as extra_spec_name and the updated value. """ url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) put_body = json.dumps(extra_spec) resp, body = self.put(url, put_body) body = json.loads(body) return resp, body def get_encryption_type(self, vol_type_id): """ Get the volume encryption type for the specified volume type. vol_type_id: Id of volume_type. """ url = "/types/%s/encryption" % str(vol_type_id) resp, body = self.get(url) body = json.loads(body) return resp, body def create_encryption_type(self, vol_type_id, **kwargs): """ Create a new encryption type for the specified volume type. vol_type_id: Id of volume_type. provider: Class providing encryption support. cipher: Encryption algorithm/mode to use. key_size: Size of the encryption key, in bits. control_location: Notional service where encryption is performed. """ url = "/types/%s/encryption" % str(vol_type_id) post_body = {} post_body.update(kwargs) post_body = json.dumps({'encryption': post_body}) resp, body = self.post(url, post_body) body = json.loads(body) return resp, body['encryption'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/json/extensions_client.py0000664000175000017500000000212512332757070027770 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.common import rest_client from tempest import config CONF = config.CONF class ExtensionsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ExtensionsClientJSON, self).__init__(auth_provider) self.service = CONF.volume.catalog_type def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = json.loads(body) return resp, body['extensions'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/__init__.py0000664000175000017500000000000012332757070025007 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/0000775000175000017500000000000012332757136023242 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/json/0000775000175000017500000000000012332757136024213 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/json/volumes_client.py0000664000175000017500000002562712332757070027626 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time import urllib from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class VolumesV2ClientJSON(rest_client.RestClient): """ Client class to send CRUD Volume V2 API requests to a Cinder endpoint """ def __init__(self, auth_provider): super(VolumesV2ClientJSON, self).__init__(auth_provider) self.api_version = "v2" self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def get_attachment_from_volume(self, volume): """Return the element 'attachment' from input volumes.""" return volume['attachments'][0] def list_volumes(self, params=None): """List all the volumes created.""" url = 'volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def list_volumes_with_detail(self, params=None): """List the details of all volumes.""" url = 'volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['volume'] def create_volume(self, size=None, **kwargs): """ Creates a new Volume. size: Size of volume in GB. Following optional keyword arguments are accepted: name: Optional Volume Name. metadata: A dictionary of values to be used as metadata. volume_type: Optional Name of volume_type for the volume snapshot_id: When specified the volume is created from this snapshot imageRef: When specified the volume is created from this image """ # for bug #1293885: # If no size specified, read volume size from CONF if size is None: size = CONF.volume.volume_size post_body = {'size': size} post_body.update(kwargs) post_body = json.dumps({'volume': post_body}) resp, body = self.post('volumes', post_body) body = json.loads(body) return resp, body['volume'] def update_volume(self, volume_id, **kwargs): """Updates the Specified Volume.""" put_body = json.dumps({'volume': kwargs}) resp, body = self.put('volumes/%s' % volume_id, put_body) body = json.loads(body) return resp, body['volume'] def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("volumes/%s" % str(volume_id)) def upload_volume(self, volume_id, image_name, disk_format): """Uploads a volume in Glance.""" post_body = { 'image_name': image_name, 'disk_format': disk_format } post_body = json.dumps({'os-volume_upload_image': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) body = json.loads(body) return resp, body['os-volume_upload_image'] def attach_volume(self, volume_id, instance_uuid, mountpoint): """Attaches a volume to a given instance on a given mountpoint.""" post_body = { 'instance_uuid': instance_uuid, 'mountpoint': mountpoint, } post_body = json.dumps({'os-attach': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def detach_volume(self, volume_id): """Detaches a volume from an instance.""" post_body = {} post_body = json.dumps({'os-detach': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def reserve_volume(self, volume_id): """Reserves a volume.""" post_body = {} post_body = json.dumps({'os-reserve': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def unreserve_volume(self, volume_id): """Restore a reserved volume .""" post_body = {} post_body = json.dumps({'os-unreserve': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_name = body['name'] volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = ('Volume %s failed to reach %s status within ' 'the required time (%s s).' % (volume_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False def extend_volume(self, volume_id, extend_size): """Extend a volume.""" post_body = { 'new_size': extend_size } post_body = json.dumps({'os-extend': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def reset_volume_status(self, volume_id, status): """Reset the Specified Volume's Status.""" post_body = json.dumps({'os-reset_status': {"status": status}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def volume_begin_detaching(self, volume_id): """Volume Begin Detaching.""" post_body = json.dumps({'os-begin_detaching': {}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def volume_roll_detaching(self, volume_id): """Volume Roll Detaching.""" post_body = json.dumps({'os-roll_detaching': {}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def create_volume_transfer(self, vol_id, name=None): """Create a volume transfer.""" post_body = { 'volume_id': vol_id } if name: post_body['name'] = name post_body = json.dumps({'transfer': post_body}) resp, body = self.post('os-volume-transfer', post_body) body = json.loads(body) return resp, body['transfer'] def get_volume_transfer(self, transfer_id): """Returns the details of a volume transfer.""" url = "os-volume-transfer/%s" % str(transfer_id) resp, body = self.get(url) body = json.loads(body) return resp, body['transfer'] def list_volume_transfers(self, params=None): """List all the volume transfers created.""" url = 'os-volume-transfer' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['transfers'] def delete_volume_transfer(self, transfer_id): """Delete a volume transfer.""" return self.delete("os-volume-transfer/%s" % str(transfer_id)) def accept_volume_transfer(self, transfer_id, transfer_auth_key): """Accept a volume transfer.""" post_body = { 'auth_key': transfer_auth_key, } url = 'os-volume-transfer/%s/accept' % transfer_id post_body = json.dumps({'accept': post_body}) resp, body = self.post(url, post_body) body = json.loads(body) return resp, body['transfer'] def update_volume_readonly(self, volume_id, readonly): """Update the Specified Volume readonly.""" post_body = { 'readonly': readonly } post_body = json.dumps({'os-update_readonly_flag': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body) return resp, body def force_delete_volume(self, volume_id): """Force Delete Volume.""" post_body = json.dumps({'os-force_delete': {}}) resp, body = self.post('volumes/%s/action' % volume_id, post_body) return resp, body def create_volume_metadata(self, volume_id, metadata): """Create metadata for the volume.""" put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % str(volume_id) resp, body = self.post(url, put_body) body = json.loads(body) return resp, body['metadata'] def get_volume_metadata(self, volume_id): """Get metadata of the volume.""" url = "volumes/%s/metadata" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['metadata'] def update_volume_metadata(self, volume_id, metadata): """Update metadata for the volume.""" put_body = json.dumps({'metadata': metadata}) url = "volumes/%s/metadata" % str(volume_id) resp, body = self.put(url, put_body) body = json.loads(body) return resp, body['metadata'] def update_volume_metadata_item(self, volume_id, id, meta_item): """Update metadata item for the volume.""" put_body = json.dumps({'meta': meta_item}) url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) resp, body = self.put(url, put_body) body = json.loads(body) return resp, body['meta'] def delete_volume_metadata_item(self, volume_id, id): """Delete metadata item for the volume.""" url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) resp, body = self.delete(url) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/json/__init__.py0000664000175000017500000000000012332757070026307 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/__init__.py0000664000175000017500000000000012332757070025336 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/xml/0000775000175000017500000000000012332757136024042 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/xml/volumes_client.py0000664000175000017500000003710212332757070027444 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config from tempest import exceptions CONF = config.CONF class VolumesV2ClientXML(rest_client.RestClient): """ Client class to send CRUD Volume API requests to a Cinder endpoint """ TYPE = "xml" def __init__(self, auth_provider): super(VolumesV2ClientXML, self).__init__(auth_provider) self.api_version = "v2" self.service = CONF.volume.catalog_type self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout def _parse_volume(self, body): vol = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) if tag == 'metadata': vol['metadata'] = dict((meta.get('key'), meta.text) for meta in child.getchildren()) else: vol[tag] = common.xml_to_json(child) return vol def get_attachment_from_volume(self, volume): """Return the element 'attachment' from input volumes.""" return volume['attachments']['attachment'] def _check_if_bootable(self, volume): """ Check if the volume is bootable, also change the value of 'bootable' from string to boolean. """ # NOTE(jdg): Version 1 of Cinder API uses lc strings # We should consider being explicit in this check to # avoid introducing bugs like: LP #1227837 if volume['bootable'].lower() == 'true': volume['bootable'] = True elif volume['bootable'].lower() == 'false': volume['bootable'] = False else: raise ValueError( 'bootable flag is supposed to be either True or False,' 'it is %s' % volume['bootable']) return volume def list_volumes(self, params=None): """List all the volumes created.""" url = 'volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] return resp, volumes def list_volumes_with_detail(self, params=None): """List all the details of volumes.""" url = 'volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] for v in volumes: v = self._check_if_bootable(v) return resp, volumes def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % str(volume_id) resp, body = self.get(url) body = self._parse_volume(etree.fromstring(body)) body = self._check_if_bootable(body) return resp, body def create_volume(self, size=None, **kwargs): """Creates a new Volume. :param size: Size of volume in GB. :param name: Optional Volume Name. :param metadata: An optional dictionary of values for metadata. :param volume_type: Optional Name of volume_type for the volume :param snapshot_id: When specified the volume is created from this snapshot :param imageRef: When specified the volume is created from this image """ # for bug #1293885: # If no size specified, read volume size from CONF if size is None: size = CONF.volume.volume_size # NOTE(afazekas): it should use a volume namespace volume = common.Element("volume", xmlns=common.XMLNS_11, size=size) if 'metadata' in kwargs: _metadata = common.Element('metadata') volume.append(_metadata) for key, value in kwargs['metadata'].items(): meta = common.Element('meta') meta.add_attr('key', key) meta.append(common.Text(value)) _metadata.append(meta) attr_to_add = kwargs.copy() del attr_to_add['metadata'] else: attr_to_add = kwargs for key, value in attr_to_add.items(): volume.add_attr(key, value) resp, body = self.post('volumes', str(common.Document(volume))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def update_volume(self, volume_id, **kwargs): """Updates the Specified Volume.""" put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs) resp, body = self.put('volumes/%s' % volume_id, str(common.Document(put_body))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("volumes/%s" % str(volume_id)) def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = 'Volume %s failed to reach %s status within '\ 'the required time (%s s).' % (volume_id, status, self.build_timeout) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False def attach_volume(self, volume_id, instance_uuid, mountpoint): """Attaches a volume to a given instance on a given mountpoint.""" post_body = common.Element("os-attach", instance_uuid=instance_uuid, mountpoint=mountpoint ) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def detach_volume(self, volume_id): """Detaches a volume from an instance.""" post_body = common.Element("os-detach") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def upload_volume(self, volume_id, image_name, disk_format): """Uploads a volume in Glance.""" post_body = common.Element("os-volume_upload_image", image_name=image_name, disk_format=disk_format) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def extend_volume(self, volume_id, extend_size): """Extend a volume.""" post_body = common.Element("os-extend", new_size=extend_size) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def reset_volume_status(self, volume_id, status): """Reset the Specified Volume's Status.""" post_body = common.Element("os-reset_status", status=status ) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def volume_begin_detaching(self, volume_id): """Volume Begin Detaching.""" post_body = common.Element("os-begin_detaching") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def volume_roll_detaching(self, volume_id): """Volume Roll Detaching.""" post_body = common.Element("os-roll_detaching") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def reserve_volume(self, volume_id): """Reserves a volume.""" post_body = common.Element("os-reserve") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def unreserve_volume(self, volume_id): """Restore a reserved volume .""" post_body = common.Element("os-unreserve") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def create_volume_transfer(self, vol_id, name=None): """Create a volume transfer.""" post_body = common.Element("transfer", volume_id=vol_id) if name: post_body.add_attr('name', name) resp, body = self.post('os-volume-transfer', str(common.Document(post_body))) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def get_volume_transfer(self, transfer_id): """Returns the details of a volume transfer.""" url = "os-volume-transfer/%s" % str(transfer_id) resp, body = self.get(url) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def list_volume_transfers(self, params=None): """List all the volume transfers created.""" url = 'os-volume-transfer' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume_transfer(vol) for vol in list(body)] return resp, volumes def _parse_volume_transfer(self, body): vol = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): tag = tag.split("}", 1) vol[tag] = common.xml_to_json(child) return vol def delete_volume_transfer(self, transfer_id): """Delete a volume transfer.""" return self.delete("os-volume-transfer/%s" % str(transfer_id)) def accept_volume_transfer(self, transfer_id, transfer_auth_key): """Accept a volume transfer.""" post_body = common.Element("accept", auth_key=transfer_auth_key) url = 'os-volume-transfer/%s/accept' % transfer_id resp, body = self.post(url, str(common.Document(post_body))) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def update_volume_readonly(self, volume_id, readonly): """Update the Specified Volume readonly.""" post_body = common.Element("os-update_readonly_flag", readonly=readonly) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def force_delete_volume(self, volume_id): """Force Delete Volume.""" post_body = common.Element("os-force_delete") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def _metadata_body(self, meta): post_body = common.Element('metadata') for k, v in meta.items(): data = common.Element('meta', key=k) data.append(common.Text(v)) post_body.append(data) return post_body def _parse_key_value(self, node): """Parse value data into {'key': 'value'}.""" data = {} for node in node.getchildren(): data[node.get('key')] = node.text return data def create_volume_metadata(self, volume_id, metadata): """Create metadata for the volume.""" post_body = self._metadata_body(metadata) resp, body = self.post('volumes/%s/metadata' % volume_id, str(common.Document(post_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def get_volume_metadata(self, volume_id): """Get metadata of the volume.""" url = "volumes/%s/metadata" % str(volume_id) resp, body = self.get(url) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_volume_metadata(self, volume_id, metadata): """Update metadata for the volume.""" put_body = self._metadata_body(metadata) url = "volumes/%s/metadata" % str(volume_id) resp, body = self.put(url, str(common.Document(put_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_volume_metadata_item(self, volume_id, id, meta_item): """Update metadata item for the volume.""" for k, v in meta_item.items(): put_body = common.Element('meta', key=k) put_body.append(common.Text(v)) url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) resp, body = self.put(url, str(common.Document(put_body))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def delete_volume_metadata_item(self, volume_id, id): """Delete metadata item for the volume.""" url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) return self.delete(url) tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/v2/xml/__init__.py0000664000175000017500000000000012332757070026136 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/0000775000175000017500000000000012332757136023513 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/snapshots_client.py0000664000175000017500000002134512332757070027447 0ustar chuckchuck00000000000000# 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 time import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class SnapshotsClientXML(rest_client.RestClient): """Client class to send CRUD Volume API requests.""" TYPE = "xml" def __init__(self, auth_provider): super(SnapshotsClientXML, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def list_snapshots(self, params=None): """List all snapshot.""" url = 'snapshots' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) snapshots = [] for snap in body: snapshots.append(common.xml_to_json(snap)) return resp, snapshots def list_snapshots_with_detail(self, params=None): """List all the details of snapshot.""" url = 'snapshots/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) snapshots = [] for snap in body: snapshots.append(common.xml_to_json(snap)) return resp, snapshots def get_snapshot(self, snapshot_id): """Returns the details of a single snapshot.""" url = "snapshots/%s" % str(snapshot_id) resp, body = self.get(url) body = etree.fromstring(body) return resp, common.xml_to_json(body) def create_snapshot(self, volume_id, **kwargs): """Creates a new snapshot. volume_id(Required): id of the volume. force: Create a snapshot even if the volume attached (Default=False) display_name: Optional snapshot Name. display_description: User friendly snapshot description. """ # NOTE(afazekas): it should use the volume namespace snapshot = common.Element("snapshot", xmlns=common.XMLNS_11, volume_id=volume_id) for key, value in kwargs.items(): snapshot.add_attr(key, value) resp, body = self.post('snapshots', str(common.Document(snapshot))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def update_snapshot(self, snapshot_id, **kwargs): """Updates a snapshot.""" put_body = common.Element("snapshot", xmlns=common.XMLNS_11, **kwargs) resp, body = self.put('snapshots/%s' % snapshot_id, str(common.Document(put_body))) body = common.xml_to_json(etree.fromstring(body)) return resp, body # NOTE(afazekas): just for the wait function def _get_snapshot_status(self, snapshot_id): resp, body = self.get_snapshot(snapshot_id) status = body['status'] # NOTE(afazekas): snapshot can reach an "error" # state in a "normal" lifecycle if (status == 'error'): raise exceptions.SnapshotBuildErrorException( snapshot_id=snapshot_id) return status # NOTE(afazkas): Wait reinvented again. It is not in the correct layer def wait_for_snapshot_status(self, snapshot_id, status): """Waits for a Snapshot to reach a given status.""" start_time = time.time() old_value = value = self._get_snapshot_status(snapshot_id) while True: dtime = time.time() - start_time time.sleep(self.build_interval) if value != old_value: LOG.info('Value transition from "%s" to "%s"' 'in %d second(s).', old_value, value, dtime) if (value == status): return value if dtime > self.build_timeout: message = ('Time Limit Exceeded! (%ds)' 'while waiting for %s, ' 'but we got %s.' % (self.build_timeout, status, value)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) old_value = value value = self._get_snapshot_status(snapshot_id) def delete_snapshot(self, snapshot_id): """Delete Snapshot.""" return self.delete("snapshots/%s" % str(snapshot_id)) def is_resource_deleted(self, id): try: self.get_snapshot(id) except exceptions.NotFound: return True return False def reset_snapshot_status(self, snapshot_id, status): """Reset the specified snapshot's status.""" post_body = common.Element("os-reset_status", status=status) url = 'snapshots/%s/action' % str(snapshot_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def update_snapshot_status(self, snapshot_id, status, progress): """Update the specified snapshot's status.""" post_body = common.Element("os-update_snapshot_status", status=status, progress=progress ) url = 'snapshots/%s/action' % str(snapshot_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def _metadata_body(self, meta): post_body = common.Element('metadata') for k, v in meta.items(): data = common.Element('meta', key=k) data.append(common.Text(v)) post_body.append(data) return post_body def _parse_key_value(self, node): """Parse value data into {'key': 'value'}.""" data = {} for node in node.getchildren(): data[node.get('key')] = node.text return data def create_snapshot_metadata(self, snapshot_id, metadata): """Create metadata for the snapshot.""" post_body = self._metadata_body(metadata) resp, body = self.post('snapshots/%s/metadata' % snapshot_id, str(common.Document(post_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def get_snapshot_metadata(self, snapshot_id): """Get metadata of the snapshot.""" url = "snapshots/%s/metadata" % str(snapshot_id) resp, body = self.get(url) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_snapshot_metadata(self, snapshot_id, metadata): """Update metadata for the snapshot.""" put_body = self._metadata_body(metadata) url = "snapshots/%s/metadata" % str(snapshot_id) resp, body = self.put(url, str(common.Document(put_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_snapshot_metadata_item(self, snapshot_id, id, meta_item): """Update metadata item for the snapshot.""" for k, v in meta_item.items(): put_body = common.Element('meta', key=k) put_body.append(common.Text(v)) url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id)) resp, body = self.put(url, str(common.Document(put_body))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def delete_snapshot_metadata_item(self, snapshot_id, id): """Delete metadata item for the snapshot.""" url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id)) return self.delete(url) def force_delete_snapshot(self, snapshot_id): """Force Delete Snapshot.""" post_body = common.Element("os-force_delete") url = 'snapshots/%s/action' % str(snapshot_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/volumes_client.py0000664000175000017500000003741512332757070027124 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time import urllib from lxml import etree from xml.sax import saxutils from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config from tempest import exceptions CONF = config.CONF class VolumesClientXML(rest_client.RestClient): """ Client class to send CRUD Volume API requests to a Cinder endpoint """ TYPE = "xml" def __init__(self, auth_provider): super(VolumesClientXML, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout def _parse_volume(self, body): vol = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) if tag == 'metadata': vol['metadata'] = dict((meta.get('key'), meta.text) for meta in child.getchildren()) else: vol[tag] = common.xml_to_json(child) return vol def get_attachment_from_volume(self, volume): """Return the element 'attachment' from input volumes.""" return volume['attachments']['attachment'] def _check_if_bootable(self, volume): """ Check if the volume is bootable, also change the value of 'bootable' from string to boolean. """ # NOTE(jdg): Version 1 of Cinder API uses lc strings # We should consider being explicit in this check to # avoid introducing bugs like: LP #1227837 if volume['bootable'].lower() == 'true': volume['bootable'] = True elif volume['bootable'].lower() == 'false': volume['bootable'] = False else: raise ValueError( 'bootable flag is supposed to be either True or False,' 'it is %s' % volume['bootable']) return volume def list_volumes(self, params=None): """List all the volumes created.""" url = 'volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] for v in volumes: v = self._check_if_bootable(v) return resp, volumes def list_volumes_with_detail(self, params=None): """List all the details of volumes.""" url = 'volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] for v in volumes: v = self._check_if_bootable(v) return resp, volumes def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % str(volume_id) resp, body = self.get(url) body = self._parse_volume(etree.fromstring(body)) body = self._check_if_bootable(body) return resp, body def create_volume(self, size=None, **kwargs): """Creates a new Volume. :param size: Size of volume in GB. :param display_name: Optional Volume Name. :param metadata: An optional dictionary of values for metadata. :param volume_type: Optional Name of volume_type for the volume :param snapshot_id: When specified the volume is created from this snapshot :param imageRef: When specified the volume is created from this image """ # for bug #1293885: # If no size specified, read volume size from CONF if size is None: size = CONF.volume.volume_size # NOTE(afazekas): it should use a volume namespace volume = common.Element("volume", xmlns=common.XMLNS_11, size=size) if 'metadata' in kwargs: _metadata = common.Element('metadata') volume.append(_metadata) for key, value in kwargs['metadata'].items(): meta = common.Element('meta') meta.add_attr('key', key) meta.append(common.Text(value)) _metadata.append(meta) attr_to_add = kwargs.copy() del attr_to_add['metadata'] else: attr_to_add = kwargs for key, value in attr_to_add.items(): volume.add_attr(key, value) resp, body = self.post('volumes', str(common.Document(volume))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def update_volume(self, volume_id, **kwargs): """Updates the Specified Volume.""" put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs) resp, body = self.put('volumes/%s' % volume_id, str(common.Document(put_body))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("volumes/%s" % str(volume_id)) def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = 'Volume %s failed to reach %s status within '\ 'the required time (%s s).' % (volume_id, status, self.build_timeout) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False def attach_volume(self, volume_id, instance_uuid, mountpoint): """Attaches a volume to a given instance on a given mountpoint.""" post_body = common.Element("os-attach", instance_uuid=instance_uuid, mountpoint=mountpoint ) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def detach_volume(self, volume_id): """Detaches a volume from an instance.""" post_body = common.Element("os-detach") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def upload_volume(self, volume_id, image_name, disk_format): """Uploads a volume in Glance.""" post_body = common.Element("os-volume_upload_image", image_name=image_name, disk_format=disk_format) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def extend_volume(self, volume_id, extend_size): """Extend a volume.""" post_body = common.Element("os-extend", new_size=extend_size) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def reset_volume_status(self, volume_id, status): """Reset the Specified Volume's Status.""" post_body = common.Element("os-reset_status", status=status ) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def volume_begin_detaching(self, volume_id): """Volume Begin Detaching.""" post_body = common.Element("os-begin_detaching") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def volume_roll_detaching(self, volume_id): """Volume Roll Detaching.""" post_body = common.Element("os-roll_detaching") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def reserve_volume(self, volume_id): """Reserves a volume.""" post_body = common.Element("os-reserve") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def unreserve_volume(self, volume_id): """Restore a reserved volume .""" post_body = common.Element("os-unreserve") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def create_volume_transfer(self, vol_id, display_name=None): """Create a volume transfer.""" post_body = common.Element("transfer", volume_id=vol_id) if display_name: post_body.add_attr('name', display_name) resp, body = self.post('os-volume-transfer', str(common.Document(post_body))) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def get_volume_transfer(self, transfer_id): """Returns the details of a volume transfer.""" url = "os-volume-transfer/%s" % str(transfer_id) resp, body = self.get(url) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def list_volume_transfers(self, params=None): """List all the volume transfers created.""" url = 'os-volume-transfer' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume_transfer(vol) for vol in list(body)] return resp, volumes def _parse_volume_transfer(self, body): vol = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): tag = tag.split("}", 1) vol[tag] = common.xml_to_json(child) return vol def delete_volume_transfer(self, transfer_id): """Delete a volume transfer.""" return self.delete("os-volume-transfer/%s" % str(transfer_id)) def accept_volume_transfer(self, transfer_id, transfer_auth_key): """Accept a volume transfer.""" post_body = common.Element("accept", auth_key=transfer_auth_key) url = 'os-volume-transfer/%s/accept' % transfer_id resp, body = self.post(url, str(common.Document(post_body))) volume = common.xml_to_json(etree.fromstring(body)) return resp, volume def update_volume_readonly(self, volume_id, readonly): """Update the Specified Volume readonly.""" post_body = common.Element("os-update_readonly_flag", readonly=readonly) url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def force_delete_volume(self, volume_id): """Force Delete Volume.""" post_body = common.Element("os-force_delete") url = 'volumes/%s/action' % str(volume_id) resp, body = self.post(url, str(common.Document(post_body))) if body: body = common.xml_to_json(etree.fromstring(body)) return resp, body def _metadata_body(self, meta): post_body = common.Element('metadata') for k, v in meta.items(): data = common.Element('meta', key=k) # Escape value to allow for special XML chars data.append(common.Text(saxutils.escape(v))) post_body.append(data) return post_body def _parse_key_value(self, node): """Parse value data into {'key': 'value'}.""" data = {} for node in node.getchildren(): data[node.get('key')] = node.text return data def create_volume_metadata(self, volume_id, metadata): """Create metadata for the volume.""" post_body = self._metadata_body(metadata) resp, body = self.post('volumes/%s/metadata' % volume_id, str(common.Document(post_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def get_volume_metadata(self, volume_id): """Get metadata of the volume.""" url = "volumes/%s/metadata" % str(volume_id) resp, body = self.get(url) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_volume_metadata(self, volume_id, metadata): """Update metadata for the volume.""" put_body = self._metadata_body(metadata) url = "volumes/%s/metadata" % str(volume_id) resp, body = self.put(url, str(common.Document(put_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_volume_metadata_item(self, volume_id, id, meta_item): """Update metadata item for the volume.""" for k, v in meta_item.items(): put_body = common.Element('meta', key=k) put_body.append(common.Text(v)) url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) resp, body = self.put(url, str(common.Document(put_body))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def delete_volume_metadata_item(self, volume_id, id): """Delete metadata item for the volume.""" url = "volumes/%s/metadata/%s" % (str(volume_id), str(id)) return self.delete(url) tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/backups_client.py0000664000175000017500000000166612332757070027061 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.services.volume.json import backups_client class BackupsClientXML(backups_client.BackupsClientJSON): """ Client class to send CRUD Volume Backup API requests to a Cinder endpoint """ TYPE = "xml" #TODO(gfidente): XML client isn't yet implemented because of bug 1270589 pass tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/__init__.py0000664000175000017500000000000012332757070025607 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/admin/0000775000175000017500000000000012332757136024603 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/admin/volume_services_client.py0000664000175000017500000000247212332757070031727 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class VolumesServicesClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(VolumesServicesClientXML, self).__init__(auth_provider) self.service = CONF.volume.catalog_type def list_services(self, params=None): url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) node = etree.fromstring(body) body = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/admin/__init__.py0000664000175000017500000000000012332757070026677 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/admin/volume_hosts_client.py0000664000175000017500000000452112332757070031241 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config CONF = config.CONF class VolumeHostsClientXML(rest_client.RestClient): """ Client class to send CRUD Volume Hosts API requests to a Cinder endpoint """ TYPE = "xml" def __init__(self, auth_provider): super(VolumeHostsClientXML, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout def _parse_array(self, node): """ This method is to parse the "list" response body Eg: This method will append the details of specified tag, here it is "host" Return value would be list of hosts as below [{'service-status': 'available', 'service': 'cinder-scheduler'}, {'service-status': 'available', 'service': 'cinder-volume'}] """ array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[0] == "host": array.append(common.xml_to_json(child)) return array def list_hosts(self, params=None): """List all the hosts.""" url = 'os-hosts' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = self._parse_array(etree.fromstring(body)) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/admin/volume_quotas_client.py0000664000175000017500000000431012332757070031411 0ustar chuckchuck00000000000000# Copyright (C) 2014 eNovance SAS # # Author: Sylvain Baubeau # # 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 ast import literal_eval from lxml import etree from tempest.common import xml_utils as xml from tempest import config from tempest.services.volume.json.admin import volume_quotas_client CONF = config.CONF class VolumeQuotasClientXML(volume_quotas_client.VolumeQuotasClientJSON): """ Client class to send CRUD Volume Quotas API requests to a Cinder endpoint """ TYPE = "xml" def _format_quota(self, q): quota = {} for k, v in q.items(): try: v = literal_eval(v) except (ValueError, SyntaxError): pass quota[k] = v return quota def get_quota_usage(self, tenant_id): """List the quota set for a tenant.""" resp, body = self.get_quota_set(tenant_id, params={'usage': True}) return resp, self._format_quota(body) def update_quota_set(self, tenant_id, gigabytes=None, volumes=None, snapshots=None): post_body = {} element = xml.Element("quota_set") if gigabytes is not None: post_body['gigabytes'] = gigabytes if volumes is not None: post_body['volumes'] = volumes if snapshots is not None: post_body['snapshots'] = snapshots xml.deep_dict_to_xml(element, post_body) resp, body = self.put('os-quota-sets/%s' % tenant_id, str(xml.Document(element))) body = xml.xml_to_json(etree.fromstring(body)) return resp, self._format_quota(body) tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/admin/volume_types_client.py0000664000175000017500000001651612332757070031254 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config from tempest import exceptions CONF = config.CONF class VolumeTypesClientXML(rest_client.RestClient): """ Client class to send CRUD Volume Types API requests to a Cinder endpoint """ TYPE = "xml" def __init__(self, auth_provider): super(VolumeTypesClientXML, self).__init__(auth_provider) self.service = CONF.volume.catalog_type self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout def _parse_volume_type(self, body): vol_type = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) if tag == 'extra_specs': vol_type['extra_specs'] = dict((meta.get('key'), meta.text) for meta in list(child)) else: vol_type[tag] = common.xml_to_json(child) return vol_type def _parse_volume_type_extra_specs(self, body): extra_spec = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) else: extra_spec[tag] = common.xml_to_json(child) return extra_spec def list_volume_types(self, params=None): """List all the volume_types created.""" url = 'types' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volume_types = [] if body is not None: volume_types += [self._parse_volume_type(vol) for vol in list(body)] return resp, volume_types def get_volume_type(self, type_id): """Returns the details of a single volume_type.""" url = "types/%s" % str(type_id) resp, body = self.get(url) body = etree.fromstring(body) return resp, self._parse_volume_type(body) def create_volume_type(self, name, **kwargs): """ Creates a new Volume_type. name(Required): Name of volume_type. Following optional keyword arguments are accepted: extra_specs: A dictionary of values to be used as extra_specs. """ vol_type = common.Element("volume_type", xmlns=common.XMLNS_11) if name: vol_type.add_attr('name', name) extra_specs = kwargs.get('extra_specs') if extra_specs: _extra_specs = common.Element('extra_specs') vol_type.append(_extra_specs) for key, value in extra_specs.items(): spec = common.Element('extra_spec') spec.add_attr('key', key) spec.append(common.Text(value)) _extra_specs.append(spec) resp, body = self.post('types', str(common.Document(vol_type))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def delete_volume_type(self, type_id): """Deletes the Specified Volume_type.""" return self.delete("types/%s" % str(type_id)) def list_volume_types_extra_specs(self, vol_type_id, params=None): """List all the volume_types extra specs created.""" url = 'types/%s/extra_specs' % str(vol_type_id) if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) extra_specs = [] if body is not None: extra_specs += [self._parse_volume_type_extra_specs(spec) for spec in list(body)] return resp, extra_specs def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name): """Returns the details of a single volume_type extra spec.""" url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) resp, body = self.get(url) body = etree.fromstring(body) return resp, self._parse_volume_type_extra_specs(body) def create_volume_type_extra_specs(self, vol_type_id, extra_spec): """ Creates a new Volume_type extra spec. vol_type_id: Id of volume_type. extra_specs: A dictionary of values to be used as extra_specs. """ url = "types/%s/extra_specs" % str(vol_type_id) extra_specs = common.Element("extra_specs", xmlns=common.XMLNS_11) if extra_spec: if isinstance(extra_spec, list): extra_specs.append(extra_spec) else: for key, value in extra_spec.items(): spec = common.Element('extra_spec') spec.add_attr('key', key) spec.append(common.Text(value)) extra_specs.append(spec) else: extra_specs = None resp, body = self.post(url, str(common.Document(extra_specs))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def delete_volume_type_extra_specs(self, vol_id, extra_spec_name): """Deletes the Specified Volume_type extra spec.""" return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)), str(extra_spec_name))) def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name, extra_spec): """ Update a volume_type extra spec. vol_type_id: Id of volume_type. extra_spec_name: Name of the extra spec to be updated. extra_spec: A dictionary of with key as extra_spec_name and the updated value. """ url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) extra_specs = common.Element("extra_specs", xmlns=common.XMLNS_11) if extra_spec is not None: for key, value in extra_spec.items(): spec = common.Element('extra_spec') spec.add_attr('key', key) spec.append(common.Text(value)) extra_specs.append(spec) resp, body = self.put(url, str(common.Document(extra_specs))) body = common.xml_to_json(etree.fromstring(body)) return resp, body def is_resource_deleted(self, id): try: self.get_volume_type(id) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/volume/xml/extensions_client.py0000664000175000017500000000250312332757070027617 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config CONF = config.CONF class ExtensionsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(ExtensionsClientXML, self).__init__(auth_provider) self.service = CONF.volume.catalog_type def _parse_array(self, node): array = [] for child in node: array.append(common.xml_to_json(child)) return array def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = self._parse_array(etree.fromstring(body)) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/0000775000175000017500000000000012332757136023235 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/0000775000175000017500000000000012332757136023565 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/json/0000775000175000017500000000000012332757136024536 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/json/endpoints_client.py0000664000175000017500000000630412332757070030451 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.common import rest_client from tempest import config CONF = config.CONF class EndPointClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(EndPointClientJSON, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def list_endpoints(self): """GET endpoints.""" resp, body = self.get('endpoints') body = json.loads(body) return resp, body['endpoints'] def create_endpoint(self, service_id, interface, url, **kwargs): """Create endpoint. Normally this function wouldn't allow setting values that are not allowed for 'enabled'. Use `force_enabled` to set a non-boolean. """ region = kwargs.get('region', None) if 'force_enabled' in kwargs: enabled = kwargs.get('force_enabled', None) else: enabled = kwargs.get('enabled', None) post_body = { 'service_id': service_id, 'interface': interface, 'url': url, 'region': region, 'enabled': enabled } post_body = json.dumps({'endpoint': post_body}) resp, body = self.post('endpoints', post_body) body = json.loads(body) return resp, body['endpoint'] def update_endpoint(self, endpoint_id, service_id=None, interface=None, url=None, region=None, enabled=None, **kwargs): """Updates an endpoint with given parameters. Normally this function wouldn't allow setting values that are not allowed for 'enabled'. Use `force_enabled` to set a non-boolean. """ post_body = {} if service_id is not None: post_body['service_id'] = service_id if interface is not None: post_body['interface'] = interface if url is not None: post_body['url'] = url if region is not None: post_body['region'] = region if 'force_enabled' in kwargs: post_body['enabled'] = kwargs['force_enabled'] elif enabled is not None: post_body['enabled'] = enabled post_body = json.dumps({'endpoint': post_body}) resp, body = self.patch('endpoints/%s' % endpoint_id, post_body) body = json.loads(body) return resp, body['endpoint'] def delete_endpoint(self, endpoint_id): """Delete endpoint.""" resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id) return resp_header, resp_body tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/json/credentials_client.py0000664000175000017500000000641612332757070030747 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.common import rest_client from tempest import config CONF = config.CONF class CredentialsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(CredentialsClientJSON, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def create_credential(self, access_key, secret_key, user_id, project_id): """Creates a credential.""" blob = "{\"access\": \"%s\", \"secret\": \"%s\"}" % ( access_key, secret_key) post_body = { "blob": blob, "project_id": project_id, "type": "ec2", "user_id": user_id } post_body = json.dumps({'credential': post_body}) resp, body = self.post('credentials', post_body) body = json.loads(body) body['credential']['blob'] = json.loads(body['credential']['blob']) return resp, body['credential'] def update_credential(self, credential_id, **kwargs): """Updates a credential.""" resp, body = self.get_credential(credential_id) cred_type = kwargs.get('type', body['type']) access_key = kwargs.get('access_key', body['blob']['access']) secret_key = kwargs.get('secret_key', body['blob']['secret']) project_id = kwargs.get('project_id', body['project_id']) user_id = kwargs.get('user_id', body['user_id']) blob = "{\"access\": \"%s\", \"secret\": \"%s\"}" % ( access_key, secret_key) post_body = { "blob": blob, "project_id": project_id, "type": cred_type, "user_id": user_id } post_body = json.dumps({'credential': post_body}) resp, body = self.patch('credentials/%s' % credential_id, post_body) body = json.loads(body) body['credential']['blob'] = json.loads(body['credential']['blob']) return resp, body['credential'] def get_credential(self, credential_id): """To GET Details of a credential.""" resp, body = self.get('credentials/%s' % credential_id) body = json.loads(body) body['credential']['blob'] = json.loads(body['credential']['blob']) return resp, body['credential'] def list_credentials(self): """Lists out all the available credentials.""" resp, body = self.get('credentials') body = json.loads(body) return resp, body['credentials'] def delete_credential(self, credential_id): """Deletes a credential.""" resp, body = self.delete('credentials/%s' % credential_id) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/json/__init__.py0000664000175000017500000000000012332757070026632 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/json/service_client.py0000664000175000017500000000455012332757070030107 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.common import rest_client from tempest import config CONF = config.CONF class ServiceClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ServiceClientJSON, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def update_service(self, service_id, **kwargs): """Updates a service.""" resp, body = self.get_service(service_id) name = kwargs.get('name', body['name']) type = kwargs.get('type', body['type']) desc = kwargs.get('description', body['description']) patch_body = { 'description': desc, 'type': type, 'name': name } patch_body = json.dumps({'service': patch_body}) resp, body = self.patch('services/%s' % service_id, patch_body) body = json.loads(body) return resp, body['service'] def get_service(self, service_id): """Get Service.""" url = 'services/%s' % service_id resp, body = self.get(url) body = json.loads(body) return resp, body['service'] def create_service(self, serv_type, name=None, description=None, enabled=True): body_dict = { "name": name, 'type': serv_type, 'enabled': enabled, "description": description, } body = json.dumps({'service': body_dict}) resp, body = self.post("services", body) body = json.loads(body) return resp, body["service"] def delete_service(self, serv_id): url = "services/" + serv_id resp, body = self.delete(url) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/json/identity_client.py0000664000175000017500000005066312332757070030306 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class IdentityV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(IdentityV3ClientJSON, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def create_user(self, user_name, **kwargs): """Creates a user.""" password = kwargs.get('password', None) email = kwargs.get('email', None) en = kwargs.get('enabled', True) project_id = kwargs.get('project_id', None) description = kwargs.get('description', None) domain_id = kwargs.get('domain_id', 'default') post_body = { 'project_id': project_id, 'description': description, 'domain_id': domain_id, 'email': email, 'enabled': en, 'name': user_name, 'password': password } post_body = json.dumps({'user': post_body}) resp, body = self.post('users', post_body) body = json.loads(body) return resp, body['user'] def update_user(self, user_id, name, **kwargs): """Updates a user.""" resp, body = self.get_user(user_id) email = kwargs.get('email', body['email']) en = kwargs.get('enabled', body['enabled']) project_id = kwargs.get('project_id', body['project_id']) description = kwargs.get('description', body['description']) domain_id = kwargs.get('domain_id', body['domain_id']) post_body = { 'name': name, 'email': email, 'enabled': en, 'project_id': project_id, 'id': user_id, 'domain_id': domain_id, 'description': description } post_body = json.dumps({'user': post_body}) resp, body = self.patch('users/%s' % user_id, post_body) body = json.loads(body) return resp, body['user'] def list_user_projects(self, user_id): """Lists the projects on which a user has roles assigned.""" resp, body = self.get('users/%s/projects' % user_id) body = json.loads(body) return resp, body['projects'] def get_users(self): """Get the list of users.""" resp, body = self.get("users") body = json.loads(body) return resp, body['users'] def get_user(self, user_id): """GET a user.""" resp, body = self.get("users/%s" % user_id) body = json.loads(body) return resp, body['user'] def delete_user(self, user_id): """Deletes a User.""" resp, body = self.delete("users/%s" % user_id) return resp, body def create_project(self, name, **kwargs): """Creates a project.""" description = kwargs.get('description', None) en = kwargs.get('enabled', True) domain_id = kwargs.get('domain_id', 'default') post_body = { 'description': description, 'domain_id': domain_id, 'enabled': en, 'name': name } post_body = json.dumps({'project': post_body}) resp, body = self.post('projects', post_body) body = json.loads(body) return resp, body['project'] def list_projects(self): resp, body = self.get("projects") body = json.loads(body) return resp, body['projects'] def update_project(self, project_id, **kwargs): resp, body = self.get_project(project_id) name = kwargs.get('name', body['name']) desc = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) domain_id = kwargs.get('domain_id', body['domain_id']) post_body = { 'id': project_id, 'name': name, 'description': desc, 'enabled': en, 'domain_id': domain_id, } post_body = json.dumps({'project': post_body}) resp, body = self.patch('projects/%s' % project_id, post_body) body = json.loads(body) return resp, body['project'] def get_project(self, project_id): """GET a Project.""" resp, body = self.get("projects/%s" % project_id) body = json.loads(body) return resp, body['project'] def delete_project(self, project_id): """Delete a project.""" resp, body = self.delete('projects/%s' % str(project_id)) return resp, body def create_role(self, name): """Create a Role.""" post_body = { 'name': name } post_body = json.dumps({'role': post_body}) resp, body = self.post('roles', post_body) body = json.loads(body) return resp, body['role'] def get_role(self, role_id): """GET a Role.""" resp, body = self.get('roles/%s' % str(role_id)) body = json.loads(body) return resp, body['role'] def list_roles(self): """Get the list of Roles.""" resp, body = self.get("roles") body = json.loads(body) return resp, body['roles'] def update_role(self, name, role_id): """Create a Role.""" post_body = { 'name': name } post_body = json.dumps({'role': post_body}) resp, body = self.patch('roles/%s' % str(role_id), post_body) body = json.loads(body) return resp, body['role'] def delete_role(self, role_id): """Delete a role.""" resp, body = self.delete('roles/%s' % str(role_id)) return resp, body def assign_user_role(self, project_id, user_id, role_id): """Add roles to a user on a project.""" resp, body = self.put('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id), None) return resp, body def create_domain(self, name, **kwargs): """Creates a domain.""" description = kwargs.get('description', None) en = kwargs.get('enabled', True) post_body = { 'description': description, 'enabled': en, 'name': name } post_body = json.dumps({'domain': post_body}) resp, body = self.post('domains', post_body) body = json.loads(body) return resp, body['domain'] def delete_domain(self, domain_id): """Delete a domain.""" resp, body = self.delete('domains/%s' % str(domain_id)) return resp, body def list_domains(self): """List Domains.""" resp, body = self.get('domains') body = json.loads(body) return resp, body['domains'] def update_domain(self, domain_id, **kwargs): """Updates a domain.""" resp, body = self.get_domain(domain_id) description = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) name = kwargs.get('name', body['name']) post_body = { 'description': description, 'enabled': en, 'name': name } post_body = json.dumps({'domain': post_body}) resp, body = self.patch('domains/%s' % domain_id, post_body) body = json.loads(body) return resp, body['domain'] def get_domain(self, domain_id): """Get Domain details.""" resp, body = self.get('domains/%s' % domain_id) body = json.loads(body) return resp, body['domain'] def get_token(self, resp_token): """Get token details.""" headers = {'X-Subject-Token': resp_token} resp, body = self.get("auth/tokens", headers=headers) body = json.loads(body) return resp, body['token'] def delete_token(self, resp_token): """Deletes token.""" headers = {'X-Subject-Token': resp_token} resp, body = self.delete("auth/tokens", headers=headers) return resp, body def create_group(self, name, **kwargs): """Creates a group.""" description = kwargs.get('description', None) domain_id = kwargs.get('domain_id', 'default') project_id = kwargs.get('project_id', None) post_body = { 'description': description, 'domain_id': domain_id, 'project_id': project_id, 'name': name } post_body = json.dumps({'group': post_body}) resp, body = self.post('groups', post_body) body = json.loads(body) return resp, body['group'] def get_group(self, group_id): """Get group details.""" resp, body = self.get('groups/%s' % group_id) body = json.loads(body) return resp, body['group'] def update_group(self, group_id, **kwargs): """Updates a group.""" resp, body = self.get_group(group_id) name = kwargs.get('name', body['name']) description = kwargs.get('description', body['description']) post_body = { 'name': name, 'description': description } post_body = json.dumps({'group': post_body}) resp, body = self.patch('groups/%s' % group_id, post_body) body = json.loads(body) return resp, body['group'] def delete_group(self, group_id): """Delete a group.""" resp, body = self.delete('groups/%s' % str(group_id)) return resp, body def add_group_user(self, group_id, user_id): """Add user into group.""" resp, body = self.put('groups/%s/users/%s' % (group_id, user_id), None) return resp, body def list_group_users(self, group_id): """List users in group.""" resp, body = self.get('groups/%s/users' % group_id) body = json.loads(body) return resp, body['users'] def list_user_groups(self, user_id): """Lists groups which a user belongs to.""" resp, body = self.get('users/%s/groups' % user_id) body = json.loads(body) return resp, body['groups'] def delete_group_user(self, group_id, user_id): """Delete user in group.""" resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id)) return resp, body def assign_user_role_on_project(self, project_id, user_id, role_id): """Add roles to a user on a project.""" resp, body = self.put('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id), None) return resp, body def assign_user_role_on_domain(self, domain_id, user_id, role_id): """Add roles to a user on a domain.""" resp, body = self.put('domains/%s/users/%s/roles/%s' % (domain_id, user_id, role_id), None) return resp, body def list_user_roles_on_project(self, project_id, user_id): """list roles of a user on a project.""" resp, body = self.get('projects/%s/users/%s/roles' % (project_id, user_id)) body = json.loads(body) return resp, body['roles'] def list_user_roles_on_domain(self, domain_id, user_id): """list roles of a user on a domain.""" resp, body = self.get('domains/%s/users/%s/roles' % (domain_id, user_id)) body = json.loads(body) return resp, body['roles'] def revoke_role_from_user_on_project(self, project_id, user_id, role_id): """Delete role of a user on a project.""" resp, body = self.delete('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id)) return resp, body def revoke_role_from_user_on_domain(self, domain_id, user_id, role_id): """Delete role of a user on a domain.""" resp, body = self.delete('domains/%s/users/%s/roles/%s' % (domain_id, user_id, role_id)) return resp, body def assign_group_role_on_project(self, project_id, group_id, role_id): """Add roles to a user on a project.""" resp, body = self.put('projects/%s/groups/%s/roles/%s' % (project_id, group_id, role_id), None) return resp, body def assign_group_role_on_domain(self, domain_id, group_id, role_id): """Add roles to a user on a domain.""" resp, body = self.put('domains/%s/groups/%s/roles/%s' % (domain_id, group_id, role_id), None) return resp, body def list_group_roles_on_project(self, project_id, group_id): """list roles of a user on a project.""" resp, body = self.get('projects/%s/groups/%s/roles' % (project_id, group_id)) body = json.loads(body) return resp, body['roles'] def list_group_roles_on_domain(self, domain_id, group_id): """list roles of a user on a domain.""" resp, body = self.get('domains/%s/groups/%s/roles' % (domain_id, group_id)) body = json.loads(body) return resp, body['roles'] def revoke_role_from_group_on_project(self, project_id, group_id, role_id): """Delete role of a user on a project.""" resp, body = self.delete('projects/%s/groups/%s/roles/%s' % (project_id, group_id, role_id)) return resp, body def revoke_role_from_group_on_domain(self, domain_id, group_id, role_id): """Delete role of a user on a domain.""" resp, body = self.delete('domains/%s/groups/%s/roles/%s' % (domain_id, group_id, role_id)) return resp, body def create_trust(self, trustor_user_id, trustee_user_id, project_id, role_names, impersonation, expires_at): """Creates a trust.""" roles = [{'name': n} for n in role_names] post_body = { 'trustor_user_id': trustor_user_id, 'trustee_user_id': trustee_user_id, 'project_id': project_id, 'impersonation': impersonation, 'roles': roles, 'expires_at': expires_at } post_body = json.dumps({'trust': post_body}) resp, body = self.post('OS-TRUST/trusts', post_body) body = json.loads(body) return resp, body['trust'] def delete_trust(self, trust_id): """Deletes a trust.""" resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id) return resp, body def get_trusts(self, trustor_user_id=None, trustee_user_id=None): """GET trusts.""" if trustor_user_id: resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s" % trustor_user_id) elif trustee_user_id: resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s" % trustee_user_id) else: resp, body = self.get("OS-TRUST/trusts") body = json.loads(body) return resp, body['trusts'] def get_trust(self, trust_id): """GET trust.""" resp, body = self.get("OS-TRUST/trusts/%s" % trust_id) body = json.loads(body) return resp, body['trust'] def get_trust_roles(self, trust_id): """GET roles delegated by a trust.""" resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id) body = json.loads(body) return resp, body['roles'] def get_trust_role(self, trust_id, role_id): """GET role delegated by a trust.""" resp, body = self.get("OS-TRUST/trusts/%s/roles/%s" % (trust_id, role_id)) body = json.loads(body) return resp, body['role'] def check_trust_role(self, trust_id, role_id): """HEAD Check if role is delegated by a trust.""" resp, body = self.head("OS-TRUST/trusts/%s/roles/%s" % (trust_id, role_id)) return resp, body class V3TokenClientJSON(rest_client.RestClient): def __init__(self): super(V3TokenClientJSON, self).__init__(None) auth_url = CONF.identity.uri_v3 if not auth_url and CONF.identity_feature_enabled.api_v3: raise exceptions.InvalidConfiguration('you must specify a v3 uri ' 'if using the v3 identity ' 'api') if 'auth/tokens' not in auth_url: auth_url = auth_url.rstrip('/') + '/auth/tokens' self.auth_url = auth_url def auth(self, user=None, password=None, tenant=None, user_type='id', domain=None, token=None): """ :param user: user id or name, as specified in user_type :param domain: the user and tenant domain :param token: a token to re-scope. Accepts different combinations of credentials. Restrictions: - tenant and domain are only name (no id) - user domain and tenant domain are assumed identical - domain scope is not supported here Sample sample valid combinations: - token - token, tenant, domain - user_id, password - username, password, domain - username, password, tenant, domain Validation is left to the server side. """ creds = { 'auth': { 'identity': { 'methods': [], } } } id_obj = creds['auth']['identity'] if token: id_obj['methods'].append('token') id_obj['token'] = { 'id': token } if user and password: id_obj['methods'].append('password') id_obj['password'] = { 'user': { 'password': password, } } if user_type == 'id': id_obj['password']['user']['id'] = user else: id_obj['password']['user']['name'] = user if domain is not None: _domain = dict(name=domain) id_obj['password']['user']['domain'] = _domain if tenant is not None: _domain = dict(name=domain) project = dict(name=tenant, domain=_domain) scope = dict(project=project) creds['auth']['scope'] = scope body = json.dumps(creds) resp, body = self.post(self.auth_url, body=body) return resp, body def request(self, method, url, extra_headers=False, headers=None, body=None): """A simple HTTP request interface.""" if headers is None: # Always accept 'json', for xml token client too. # Because XML response is not easily # converted to the corresponding JSON one headers = self.get_headers(accept_type="json") elif extra_headers: try: headers.update(self.get_headers(accept_type="json")) except (ValueError, TypeError): headers = self.get_headers(accept_type="json") resp, resp_body = self.http_obj.request(url, method, headers=headers, body=body) self._log_request(method, url, resp) if resp.status in [401, 403]: resp_body = json.loads(resp_body) raise exceptions.Unauthorized(resp_body['error']['message']) elif resp.status not in [200, 201, 204]: raise exceptions.IdentityError( 'Unexpected status code {0}'.format(resp.status)) return resp, json.loads(resp_body) def get_token(self, user, password, tenant, domain='Default', auth_data=False): """ :param user: username Returns (token id, token data) for supplied credentials """ resp, body = self.auth(user, password, tenant, user_type='name', domain=domain) token = resp.get('x-subject-token') if auth_data: return token, body['token'] else: return token tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/json/policy_client.py0000664000175000017500000000441712332757070027750 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.common import rest_client from tempest import config CONF = config.CONF class PolicyClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(PolicyClientJSON, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def create_policy(self, blob, type): """Creates a Policy.""" post_body = { "blob": blob, "type": type } post_body = json.dumps({'policy': post_body}) resp, body = self.post('policies', post_body) body = json.loads(body) return resp, body['policy'] def list_policies(self): """Lists the policies.""" resp, body = self.get('policies') body = json.loads(body) return resp, body['policies'] def get_policy(self, policy_id): """Lists out the given policy.""" url = 'policies/%s' % policy_id resp, body = self.get(url) body = json.loads(body) return resp, body['policy'] def update_policy(self, policy_id, **kwargs): """Updates a policy.""" resp, body = self.get_policy(policy_id) type = kwargs.get('type') post_body = { 'type': type } post_body = json.dumps({'policy': post_body}) url = 'policies/%s' % policy_id resp, body = self.patch(url, post_body) body = json.loads(body) return resp, body['policy'] def delete_policy(self, policy_id): """Deletes the policy.""" url = "policies/%s" % policy_id return self.delete(url) tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/__init__.py0000664000175000017500000000000012332757070025661 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/xml/0000775000175000017500000000000012332757136024365 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/xml/endpoints_client.py0000664000175000017500000001146212332757070030301 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import http from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config CONF = config.CONF XMLNS = "http://docs.openstack.org/identity/api/v3" class EndPointClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(EndPointClientXML, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def _parse_array(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "endpoint": array.append(common.xml_to_json(child)) return array def _parse_body(self, body): json = common.xml_to_json(body) return json def request(self, method, url, extra_headers=False, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class RestClient.""" if extra_headers: try: headers.update(self.get_headers()) except (ValueError, TypeError): headers = self.get_headers() dscv = CONF.identity.disable_ssl_certificate_validation self.http_obj = http.ClosingHttp( disable_ssl_certificate_validation=dscv) return super(EndPointClientXML, self).request(method, url, extra_headers, headers=headers, body=body) def list_endpoints(self): """Get the list of endpoints.""" resp, body = self.get("endpoints") body = self._parse_array(etree.fromstring(body)) return resp, body def create_endpoint(self, service_id, interface, url, **kwargs): """Create endpoint. Normally this function wouldn't allow setting values that are not allowed for 'enabled'. Use `force_enabled` to set a non-boolean. """ region = kwargs.get('region', None) if 'force_enabled' in kwargs: enabled = kwargs['force_enabled'] else: enabled = kwargs.get('enabled', None) if enabled is not None: enabled = str(enabled).lower() create_endpoint = common.Element("endpoint", xmlns=XMLNS, service_id=service_id, interface=interface, url=url, region=region, enabled=enabled) resp, body = self.post('endpoints', str(common.Document(create_endpoint))) body = self._parse_body(etree.fromstring(body)) return resp, body def update_endpoint(self, endpoint_id, service_id=None, interface=None, url=None, region=None, enabled=None, **kwargs): """Updates an endpoint with given parameters. Normally this function wouldn't allow setting values that are not allowed for 'enabled'. Use `force_enabled` to set a non-boolean. """ doc = common.Document() endpoint = common.Element("endpoint") doc.append(endpoint) if service_id: endpoint.add_attr("service_id", service_id) if interface: endpoint.add_attr("interface", interface) if url: endpoint.add_attr("url", url) if region: endpoint.add_attr("region", region) if 'force_enabled' in kwargs: endpoint.add_attr("enabled", kwargs['force_enabled']) elif enabled is not None: endpoint.add_attr("enabled", str(enabled).lower()) resp, body = self.patch('endpoints/%s' % str(endpoint_id), str(doc)) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_endpoint(self, endpoint_id): """Delete endpoint.""" resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id) return resp_header, resp_body tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/xml/credentials_client.py0000664000175000017500000001022612332757070030570 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config CONF = config.CONF XMLNS = "http://docs.openstack.org/identity/api/v3" class CredentialsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(CredentialsClientXML, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def _parse_body(self, body): data = common.xml_to_json(body) return data def _parse_creds(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "credential": array.append(common.xml_to_json(child)) return array def create_credential(self, access_key, secret_key, user_id, project_id): """Creates a credential.""" cred_type = 'ec2' access = ""access": "%s"" % access_key secret = ""secret": "%s"" % secret_key blob = common.Element('blob', xmlns=XMLNS) blob.append(common.Text("{%s , %s}" % (access, secret))) credential = common.Element('credential', project_id=project_id, type=cred_type, user_id=user_id) credential.append(blob) resp, body = self.post('credentials', str(common.Document(credential))) body = self._parse_body(etree.fromstring(body)) body['blob'] = json.loads(body['blob']) return resp, body def update_credential(self, credential_id, **kwargs): """Updates a credential.""" resp, body = self.get_credential(credential_id) cred_type = kwargs.get('type', body['type']) access_key = kwargs.get('access_key', body['blob']['access']) secret_key = kwargs.get('secret_key', body['blob']['secret']) project_id = kwargs.get('project_id', body['project_id']) user_id = kwargs.get('user_id', body['user_id']) access = ""access": "%s"" % access_key secret = ""secret": "%s"" % secret_key blob = common.Element('blob', xmlns=XMLNS) blob.append(common.Text("{%s , %s}" % (access, secret))) credential = common.Element('credential', project_id=project_id, type=cred_type, user_id=user_id) credential.append(blob) resp, body = self.patch('credentials/%s' % credential_id, str(common.Document(credential))) body = self._parse_body(etree.fromstring(body)) body['blob'] = json.loads(body['blob']) return resp, body def get_credential(self, credential_id): """To GET Details of a credential.""" resp, body = self.get('credentials/%s' % credential_id) body = self._parse_body(etree.fromstring(body)) body['blob'] = json.loads(body['blob']) return resp, body def list_credentials(self): """Lists out all the available credentials.""" resp, body = self.get('credentials') body = self._parse_creds(etree.fromstring(body)) return resp, body def delete_credential(self, credential_id): """Deletes a credential.""" resp, body = self.delete('credentials/%s' % credential_id) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/xml/__init__.py0000664000175000017500000000000012332757070026461 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/xml/service_client.py0000664000175000017500000000554512332757070027743 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config CONF = config.CONF XMLNS = "http://docs.openstack.org/identity/api/v3" class ServiceClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(ServiceClientXML, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def _parse_body(self, body): data = common.xml_to_json(body) return data def update_service(self, service_id, **kwargs): """Updates a service_id.""" resp, body = self.get_service(service_id) name = kwargs.get('name', body['name']) description = kwargs.get('description', body['description']) type = kwargs.get('type', body['type']) update_service = common.Element("service", xmlns=XMLNS, id=service_id, name=name, description=description, type=type) resp, body = self.patch('services/%s' % service_id, str(common.Document(update_service))) body = self._parse_body(etree.fromstring(body)) return resp, body def get_service(self, service_id): """Get Service.""" url = 'services/%s' % service_id resp, body = self.get(url) body = self._parse_body(etree.fromstring(body)) return resp, body def create_service(self, serv_type, name=None, description=None): post_body = common.Element("service", xmlns=XMLNS, name=name, description=description, type=serv_type) resp, body = self.post("services", str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_service(self, serv_id): url = "services/" + serv_id resp, body = self.delete(url) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/xml/identity_client.py0000664000175000017500000005360712332757070030136 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config from tempest import exceptions CONF = config.CONF XMLNS = "http://docs.openstack.org/identity/api/v3" class IdentityV3ClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(IdentityV3ClientXML, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def _parse_projects(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "project": array.append(common.xml_to_json(child)) return array def _parse_domains(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "domain": array.append(common.xml_to_json(child)) return array def _parse_groups(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "group": array.append(common.xml_to_json(child)) return array def _parse_group_users(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "user": array.append(common.xml_to_json(child)) return array def _parse_roles(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "role": array.append(common.xml_to_json(child)) return array def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(common.xml_to_json(child)) return array def _parse_body(self, body): _json = common.xml_to_json(body) return _json def create_user(self, user_name, **kwargs): """Creates a user.""" password = kwargs.get('password', None) email = kwargs.get('email', None) en = kwargs.get('enabled', 'true') project_id = kwargs.get('project_id', None) description = kwargs.get('description', None) domain_id = kwargs.get('domain_id', 'default') post_body = common.Element("user", xmlns=XMLNS, name=user_name, password=password, description=description, email=email, enabled=str(en).lower(), project_id=project_id, domain_id=domain_id) resp, body = self.post('users', str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def update_user(self, user_id, name, **kwargs): """Updates a user.""" resp, body = self.get_user(user_id) email = kwargs.get('email', body['email']) en = kwargs.get('enabled', body['enabled']) project_id = kwargs.get('project_id', body['project_id']) description = kwargs.get('description', body['description']) domain_id = kwargs.get('domain_id', body['domain_id']) update_user = common.Element("user", xmlns=XMLNS, name=name, email=email, project_id=project_id, domain_id=domain_id, description=description, enabled=str(en).lower()) resp, body = self.patch('users/%s' % user_id, str(common.Document(update_user))) body = self._parse_body(etree.fromstring(body)) return resp, body def list_user_projects(self, user_id): """Lists the projects on which a user has roles assigned.""" resp, body = self.get('users/%s/projects' % user_id) body = self._parse_projects(etree.fromstring(body)) return resp, body def get_users(self): """Get the list of users.""" resp, body = self.get("users") body = self._parse_array(etree.fromstring(body)) return resp, body def get_user(self, user_id): """GET a user.""" resp, body = self.get("users/%s" % user_id) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_user(self, user_id): """Deletes a User.""" resp, body = self.delete("users/%s" % user_id) return resp, body def create_project(self, name, **kwargs): """Creates a project.""" description = kwargs.get('description', None) en = kwargs.get('enabled', 'true') domain_id = kwargs.get('domain_id', 'default') post_body = common.Element("project", xmlns=XMLNS, description=description, domain_id=domain_id, enabled=str(en).lower(), name=name) resp, body = self.post('projects', str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def list_projects(self): """Get the list of projects.""" resp, body = self.get("projects") body = self._parse_projects(etree.fromstring(body)) return resp, body def update_project(self, project_id, **kwargs): """Updates a Project.""" resp, body = self.get_project(project_id) name = kwargs.get('name', body['name']) desc = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) domain_id = kwargs.get('domain_id', body['domain_id']) post_body = common.Element("project", xmlns=XMLNS, name=name, description=desc, enabled=str(en).lower(), domain_id=domain_id) resp, body = self.patch('projects/%s' % project_id, str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def get_project(self, project_id): """GET a Project.""" resp, body = self.get("projects/%s" % project_id) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_project(self, project_id): """Delete a project.""" resp, body = self.delete('projects/%s' % str(project_id)) return resp, body def create_role(self, name): """Create a Role.""" post_body = common.Element("role", xmlns=XMLNS, name=name) resp, body = self.post('roles', str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def get_role(self, role_id): """GET a Role.""" resp, body = self.get('roles/%s' % str(role_id)) body = self._parse_body(etree.fromstring(body)) return resp, body def list_roles(self): """Get the list of Roles.""" resp, body = self.get("roles") body = self._parse_roles(etree.fromstring(body)) return resp, body def update_role(self, name, role_id): """Updates a Role.""" post_body = common.Element("role", xmlns=XMLNS, name=name) resp, body = self.patch('roles/%s' % str(role_id), str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_role(self, role_id): """Delete a role.""" resp, body = self.delete('roles/%s' % str(role_id)) return resp, body def assign_user_role(self, project_id, user_id, role_id): """Add roles to a user on a tenant.""" resp, body = self.put('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id), '') return resp, body def create_domain(self, name, **kwargs): """Creates a domain.""" description = kwargs.get('description', None) en = kwargs.get('enabled', True) post_body = common.Element("domain", xmlns=XMLNS, name=name, description=description, enabled=str(en).lower()) resp, body = self.post('domains', str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def list_domains(self): """Get the list of domains.""" resp, body = self.get("domains") body = self._parse_domains(etree.fromstring(body)) return resp, body def delete_domain(self, domain_id): """Delete a domain.""" resp, body = self.delete('domains/%s' % domain_id) return resp, body def update_domain(self, domain_id, **kwargs): """Updates a domain.""" resp, body = self.get_domain(domain_id) description = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) name = kwargs.get('name', body['name']) post_body = common.Element("domain", xmlns=XMLNS, name=name, description=description, enabled=str(en).lower()) resp, body = self.patch('domains/%s' % domain_id, str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def get_domain(self, domain_id): """Get Domain details.""" resp, body = self.get('domains/%s' % domain_id) body = self._parse_body(etree.fromstring(body)) return resp, body def get_token(self, resp_token): """GET a Token Details.""" headers = {'Content-Type': 'application/xml', 'Accept': 'application/xml', 'X-Subject-Token': resp_token} resp, body = self.get("auth/tokens", headers=headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_token(self, resp_token): """Delete a Given Token.""" headers = {'X-Subject-Token': resp_token} resp, body = self.delete("auth/tokens", headers=headers) return resp, body def create_group(self, name, **kwargs): """Creates a group.""" description = kwargs.get('description', None) domain_id = kwargs.get('domain_id', 'default') project_id = kwargs.get('project_id', None) post_body = common.Element("group", xmlns=XMLNS, name=name, description=description, domain_id=domain_id, project_id=project_id) resp, body = self.post('groups', str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def get_group(self, group_id): """Get group details.""" resp, body = self.get('groups/%s' % group_id) body = self._parse_body(etree.fromstring(body)) return resp, body def update_group(self, group_id, **kwargs): """Updates a group.""" resp, body = self.get_group(group_id) name = kwargs.get('name', body['name']) description = kwargs.get('description', body['description']) post_body = common.Element("group", xmlns=XMLNS, name=name, description=description) resp, body = self.patch('groups/%s' % group_id, str(common.Document(post_body))) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_group(self, group_id): """Delete a group.""" resp, body = self.delete('groups/%s' % group_id) return resp, body def add_group_user(self, group_id, user_id): """Add user into group.""" resp, body = self.put('groups/%s/users/%s' % (group_id, user_id), '') return resp, body def list_group_users(self, group_id): """List users in group.""" resp, body = self.get('groups/%s/users' % group_id) body = self._parse_group_users(etree.fromstring(body)) return resp, body def list_user_groups(self, user_id): """Lists the groups which a user belongs to.""" resp, body = self.get('users/%s/groups' % user_id) body = self._parse_groups(etree.fromstring(body)) return resp, body def delete_group_user(self, group_id, user_id): """Delete user in group.""" resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id)) return resp, body def assign_user_role_on_project(self, project_id, user_id, role_id): """Add roles to a user on a project.""" resp, body = self.put('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id), '') return resp, body def assign_user_role_on_domain(self, domain_id, user_id, role_id): """Add roles to a user on a domain.""" resp, body = self.put('domains/%s/users/%s/roles/%s' % (domain_id, user_id, role_id), '') return resp, body def list_user_roles_on_project(self, project_id, user_id): """list roles of a user on a project.""" resp, body = self.get('projects/%s/users/%s/roles' % (project_id, user_id)) body = self._parse_roles(etree.fromstring(body)) return resp, body def list_user_roles_on_domain(self, domain_id, user_id): """list roles of a user on a domain.""" resp, body = self.get('domains/%s/users/%s/roles' % (domain_id, user_id)) body = self._parse_roles(etree.fromstring(body)) return resp, body def revoke_role_from_user_on_project(self, project_id, user_id, role_id): """Delete role of a user on a project.""" resp, body = self.delete('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id)) return resp, body def revoke_role_from_user_on_domain(self, domain_id, user_id, role_id): """Delete role of a user on a domain.""" resp, body = self.delete('domains/%s/users/%s/roles/%s' % (domain_id, user_id, role_id)) return resp, body def assign_group_role_on_project(self, project_id, group_id, role_id): """Add roles to a user on a project.""" resp, body = self.put('projects/%s/groups/%s/roles/%s' % (project_id, group_id, role_id), '') return resp, body def assign_group_role_on_domain(self, domain_id, group_id, role_id): """Add roles to a user on a domain.""" resp, body = self.put('domains/%s/groups/%s/roles/%s' % (domain_id, group_id, role_id), '') return resp, body def list_group_roles_on_project(self, project_id, group_id): """list roles of a user on a project.""" resp, body = self.get('projects/%s/groups/%s/roles' % (project_id, group_id)) body = self._parse_roles(etree.fromstring(body)) return resp, body def list_group_roles_on_domain(self, domain_id, group_id): """list roles of a user on a domain.""" resp, body = self.get('domains/%s/groups/%s/roles' % (domain_id, group_id)) body = self._parse_roles(etree.fromstring(body)) return resp, body def revoke_role_from_group_on_project(self, project_id, group_id, role_id): """Delete role of a user on a project.""" resp, body = self.delete('projects/%s/groups/%s/roles/%s' % (project_id, group_id, role_id)) return resp, body def revoke_role_from_group_on_domain(self, domain_id, group_id, role_id): """Delete role of a user on a domain.""" resp, body = self.delete('domains/%s/groups/%s/roles/%s' % (domain_id, group_id, role_id)) return resp, body class V3TokenClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self): super(V3TokenClientXML, self).__init__(None) auth_url = CONF.identity.uri_v3 if not auth_url and CONF.identity_feature_enabled.api_v3: raise exceptions.InvalidConfiguration('you must specify a v3 uri ' 'if using the v3 identity ' 'api') if 'auth/tokens' not in auth_url: auth_url = auth_url.rstrip('/') + '/auth/tokens' self.auth_url = auth_url def auth(self, user=None, password=None, tenant=None, user_type='id', domain=None, token=None): """ :param user: user id or name, as specified in user_type :param domain: the user and tenant domain :param token: a token to re-scope. Accepts different combinations of credentials. Restrictions: - tenant and domain are only name (no id) - user domain and tenant domain are assumed identical - domain scope is not supported here Sample sample valid combinations: - token - token, tenant, domain - user_id, password - username, password, domain - username, password, tenant, domain Validation is left to the server side. """ methods = common.Element('methods') identity = common.Element('identity') if token: method = common.Element('method') method.append(common.Text('token')) methods.append(method) token = common.Element('token', id=token) identity.append(token) if user and password: if user_type == 'id': _user = common.Element('user', id=user, password=password) else: _user = common.Element('user', name=user, password=password) if domain is not None: _domain = common.Element('domain', name=domain) _user.append(_domain) password = common.Element('password') password.append(_user) method = common.Element('method') method.append(common.Text('password')) methods.append(method) identity.append(password) identity.append(methods) auth = common.Element('auth') auth.append(identity) if tenant is not None: project = common.Element('project', name=tenant) _domain = common.Element('domain', name=domain) project.append(_domain) scope = common.Element('scope') scope.append(project) auth.append(scope) resp, body = self.post(self.auth_url, body=str(common.Document(auth))) return resp, body def request(self, method, url, extra_headers=False, headers=None, body=None): """A simple HTTP request interface.""" if headers is None: # Always accept 'json', for xml token client too. # Because XML response is not easily # converted to the corresponding JSON one headers = self.get_headers(accept_type="json") elif extra_headers: try: headers.update(self.get_headers(accept_type="json")) except (ValueError, TypeError): headers = self.get_headers(accept_type="json") resp, resp_body = self.http_obj.request(url, method, headers=headers, body=body) self._log_request(method, url, resp) if resp.status in [401, 403]: resp_body = json.loads(resp_body) raise exceptions.Unauthorized(resp_body['error']['message']) elif resp.status not in [200, 201, 204]: raise exceptions.IdentityError( 'Unexpected status code {0}'.format(resp.status)) return resp, json.loads(resp_body) def get_token(self, user, password, tenant, domain='Default', auth_data=False): """ :param user: username Returns (token id, token data) for supplied credentials """ resp, body = self.auth(user, password, tenant, user_type='name', domain=domain) token = resp.get('x-subject-token') if auth_data: return token, body['token'] else: return token tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/v3/xml/policy_client.py0000664000175000017500000000713712332757070027601 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import http from tempest.common import rest_client from tempest.common import xml_utils as common from tempest import config CONF = config.CONF XMLNS = "http://docs.openstack.org/identity/api/v3" class PolicyClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(PolicyClientXML, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' self.api_version = "v3" def _parse_array(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "policy": array.append(common.xml_to_json(child)) return array def _parse_body(self, body): json = common.xml_to_json(body) return json def request(self, method, url, extra_headers=False, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class RestClient.""" if extra_headers: try: headers.update(self.get_headers()) except (ValueError, TypeError): headers = self.get_headers() dscv = CONF.identity.disable_ssl_certificate_validation self.http_obj = http.ClosingHttp( disable_ssl_certificate_validation=dscv) return super(PolicyClientXML, self).request(method, url, extra_headers, headers=headers, body=body) def create_policy(self, blob, type): """Creates a Policy.""" create_policy = common.Element("policy", xmlns=XMLNS, blob=blob, type=type) resp, body = self.post('policies', str(common.Document(create_policy))) body = self._parse_body(etree.fromstring(body)) return resp, body def list_policies(self): """Lists the policies.""" resp, body = self.get('policies') body = self._parse_array(etree.fromstring(body)) return resp, body def get_policy(self, policy_id): """Lists out the given policy.""" url = 'policies/%s' % policy_id resp, body = self.get(url) body = self._parse_body(etree.fromstring(body)) return resp, body def update_policy(self, policy_id, **kwargs): """Updates a policy.""" resp, body = self.get_policy(policy_id) type = kwargs.get('type') update_policy = common.Element("policy", xmlns=XMLNS, type=type) url = 'policies/%s' % policy_id resp, body = self.patch(url, str(common.Document(update_policy))) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_policy(self, policy_id): """Deletes the policy.""" url = "policies/%s" % policy_id return self.delete(url) tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/json/0000775000175000017500000000000012332757136024206 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/json/__init__.py0000664000175000017500000000000012332757070026302 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/json/identity_client.py0000664000175000017500000002517712332757070027760 0ustar chuckchuck00000000000000# 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 json from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class IdentityClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(IdentityClientJSON, self).__init__(auth_provider) self.service = CONF.identity.catalog_type self.endpoint_url = 'adminURL' # Needed for xml service client self.list_tags = ["roles", "tenants", "users", "services"] def has_admin_extensions(self): """ Returns True if the KSADM Admin Extensions are supported False otherwise """ if hasattr(self, '_has_admin_extensions'): return self._has_admin_extensions resp, body = self.list_roles() self._has_admin_extensions = ('status' in resp and resp.status != 503) return self._has_admin_extensions def create_role(self, name): """Create a role.""" post_body = { 'name': name, } post_body = json.dumps({'role': post_body}) resp, body = self.post('OS-KSADM/roles', post_body) return resp, self._parse_resp(body) def get_role(self, role_id): """Get a role by its id.""" resp, body = self.get('OS-KSADM/roles/%s' % role_id) body = json.loads(body) return resp, body['role'] def create_tenant(self, name, **kwargs): """ Create a tenant name (required): New tenant name description: Description of new tenant (default is none) enabled : Initial tenant status (default is true) """ post_body = { 'name': name, 'description': kwargs.get('description', ''), 'enabled': kwargs.get('enabled', True), } post_body = json.dumps({'tenant': post_body}) resp, body = self.post('tenants', post_body) return resp, self._parse_resp(body) def delete_role(self, role_id): """Delete a role.""" return self.delete('OS-KSADM/roles/%s' % str(role_id)) def list_user_roles(self, tenant_id, user_id): """Returns a list of roles assigned to a user for a tenant.""" url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id) resp, body = self.get(url) return resp, self._parse_resp(body) def assign_user_role(self, tenant_id, user_id, role_id): """Add roles to a user on a tenant.""" resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' % (tenant_id, user_id, role_id), "") return resp, self._parse_resp(body) def remove_user_role(self, tenant_id, user_id, role_id): """Removes a role assignment for a user on a tenant.""" return self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' % (tenant_id, user_id, role_id)) def delete_tenant(self, tenant_id): """Delete a tenant.""" return self.delete('tenants/%s' % str(tenant_id)) def get_tenant(self, tenant_id): """Get tenant details.""" resp, body = self.get('tenants/%s' % str(tenant_id)) return resp, self._parse_resp(body) def list_roles(self): """Returns roles.""" resp, body = self.get('OS-KSADM/roles') return resp, self._parse_resp(body) def list_tenants(self): """Returns tenants.""" resp, body = self.get('tenants') body = json.loads(body) return resp, body['tenants'] def get_tenant_by_name(self, tenant_name): resp, tenants = self.list_tenants() for tenant in tenants: if tenant['name'] == tenant_name: return tenant raise exceptions.NotFound('No such tenant') def update_tenant(self, tenant_id, **kwargs): """Updates a tenant.""" resp, body = self.get_tenant(tenant_id) name = kwargs.get('name', body['name']) desc = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) post_body = { 'id': tenant_id, 'name': name, 'description': desc, 'enabled': en, } post_body = json.dumps({'tenant': post_body}) resp, body = self.post('tenants/%s' % tenant_id, post_body) return resp, self._parse_resp(body) def create_user(self, name, password, tenant_id, email, **kwargs): """Create a user.""" post_body = { 'name': name, 'password': password, 'email': email } if tenant_id is not None: post_body['tenantId'] = tenant_id if kwargs.get('enabled') is not None: post_body['enabled'] = kwargs.get('enabled') post_body = json.dumps({'user': post_body}) resp, body = self.post('users', post_body) return resp, self._parse_resp(body) def update_user(self, user_id, **kwargs): """Updates a user.""" put_body = json.dumps({'user': kwargs}) resp, body = self.put('users/%s' % user_id, put_body) return resp, self._parse_resp(body) def get_user(self, user_id): """GET a user.""" resp, body = self.get("users/%s" % user_id) return resp, self._parse_resp(body) def delete_user(self, user_id): """Delete a user.""" return self.delete("users/%s" % user_id) def get_users(self): """Get the list of users.""" resp, body = self.get("users") return resp, self._parse_resp(body) def enable_disable_user(self, user_id, enabled): """Enables or disables a user.""" put_body = { 'enabled': enabled } put_body = json.dumps({'user': put_body}) resp, body = self.put('users/%s/enabled' % user_id, put_body) return resp, self._parse_resp(body) def get_token(self, token_id): """Get token details.""" resp, body = self.get("tokens/%s" % token_id) return resp, self._parse_resp(body) def delete_token(self, token_id): """Delete a token.""" return self.delete("tokens/%s" % token_id) def list_users_for_tenant(self, tenant_id): """List users for a Tenant.""" resp, body = self.get('/tenants/%s/users' % tenant_id) return resp, self._parse_resp(body) def get_user_by_username(self, tenant_id, username): resp, users = self.list_users_for_tenant(tenant_id) for user in users: if user['name'] == username: return user raise exceptions.NotFound('No such user') def create_service(self, name, type, **kwargs): """Create a service.""" post_body = { 'name': name, 'type': type, 'description': kwargs.get('description') } post_body = json.dumps({'OS-KSADM:service': post_body}) resp, body = self.post('/OS-KSADM/services', post_body) return resp, self._parse_resp(body) def get_service(self, service_id): """Get Service.""" url = '/OS-KSADM/services/%s' % service_id resp, body = self.get(url) return resp, self._parse_resp(body) def list_services(self): """List Service - Returns Services.""" resp, body = self.get('/OS-KSADM/services') return resp, self._parse_resp(body) def delete_service(self, service_id): """Delete Service.""" url = '/OS-KSADM/services/%s' % service_id return self.delete(url) class TokenClientJSON(IdentityClientJSON): def __init__(self): super(TokenClientJSON, self).__init__(None) auth_url = CONF.identity.uri # Normalize URI to ensure /tokens is in it. if 'tokens' not in auth_url: auth_url = auth_url.rstrip('/') + '/tokens' self.auth_url = auth_url def auth(self, user, password, tenant=None): creds = { 'auth': { 'passwordCredentials': { 'username': user, 'password': password, }, } } if tenant: creds['auth']['tenantName'] = tenant body = json.dumps(creds) resp, body = self.post(self.auth_url, body=body) return resp, body['access'] def auth_token(self, token_id, tenant=None): creds = { 'auth': { 'token': { 'id': token_id, }, } } if tenant: creds['auth']['tenantName'] = tenant body = json.dumps(creds) resp, body = self.post(self.auth_url, body=body) return resp, body['access'] def request(self, method, url, extra_headers=False, headers=None, body=None): """A simple HTTP request interface.""" if headers is None: # Always accept 'json', for TokenClientXML too. # Because XML response is not easily # converted to the corresponding JSON one headers = self.get_headers(accept_type="json") elif extra_headers: try: headers.update(self.get_headers(accept_type="json")) except (ValueError, TypeError): headers = self.get_headers(accept_type="json") resp, resp_body = self.http_obj.request(url, method, headers=headers, body=body) self._log_request(method, url, resp) if resp.status in [401, 403]: resp_body = json.loads(resp_body) raise exceptions.Unauthorized(resp_body['error']['message']) elif resp.status not in [200, 201]: raise exceptions.IdentityError( 'Unexpected status code {0}'.format(resp.status)) if isinstance(resp_body, str): resp_body = json.loads(resp_body) return resp, resp_body def get_token(self, user, password, tenant, auth_data=False): """ Returns (token id, token data) for supplied credentials """ resp, body = self.auth(user, password, tenant) if auth_data: return body['token']['id'], body else: return body['token']['id'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/__init__.py0000664000175000017500000000000012332757070025331 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/xml/0000775000175000017500000000000012332757136024035 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/xml/__init__.py0000664000175000017500000000000012332757070026131 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/identity/xml/identity_client.py0000664000175000017500000001347112332757070027601 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common import xml_utils as xml from tempest import config from tempest.services.identity.json import identity_client CONF = config.CONF XMLNS = "http://docs.openstack.org/identity/api/v2.0" class IdentityClientXML(identity_client.IdentityClientJSON): TYPE = "xml" def create_role(self, name): """Create a role.""" create_role = xml.Element("role", xmlns=XMLNS, name=name) resp, body = self.post('OS-KSADM/roles', str(xml.Document(create_role))) return resp, self._parse_resp(body) def get_role(self, role_id): """Get a role by its id.""" resp, body = self.get('OS-KSADM/roles/%s' % role_id) return resp, self._parse_resp(body) def create_tenant(self, name, **kwargs): """ Create a tenant name (required): New tenant name description: Description of new tenant (default is none) enabled : Initial tenant status (default is true) """ en = kwargs.get('enabled', 'true') create_tenant = xml.Element("tenant", xmlns=XMLNS, name=name, description=kwargs.get('description', ''), enabled=str(en).lower()) resp, body = self.post('tenants', str(xml.Document(create_tenant))) return resp, self._parse_resp(body) def list_tenants(self): """Returns tenants.""" resp, body = self.get('tenants') return resp, self._parse_resp(body) def update_tenant(self, tenant_id, **kwargs): """Updates a tenant.""" resp, body = self.get_tenant(tenant_id) name = kwargs.get('name', body['name']) desc = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) update_tenant = xml.Element("tenant", xmlns=XMLNS, id=tenant_id, name=name, description=desc, enabled=str(en).lower()) resp, body = self.post('tenants/%s' % tenant_id, str(xml.Document(update_tenant))) return resp, self._parse_resp(body) def create_user(self, name, password, tenant_id, email, **kwargs): """Create a user.""" create_user = xml.Element("user", xmlns=XMLNS, name=name, password=password, email=email) if tenant_id: create_user.add_attr('tenantId', tenant_id) if 'enabled' in kwargs: create_user.add_attr('enabled', str(kwargs['enabled']).lower()) resp, body = self.post('users', str(xml.Document(create_user))) return resp, self._parse_resp(body) def update_user(self, user_id, **kwargs): """Updates a user.""" if 'enabled' in kwargs: kwargs['enabled'] = str(kwargs['enabled']).lower() update_user = xml.Element("user", xmlns=XMLNS, **kwargs) resp, body = self.put('users/%s' % user_id, str(xml.Document(update_user))) return resp, self._parse_resp(body) def enable_disable_user(self, user_id, enabled): """Enables or disables a user.""" enable_user = xml.Element("user", enabled=str(enabled).lower()) resp, body = self.put('users/%s/enabled' % user_id, str(xml.Document(enable_user))) return resp, self._parse_resp(body) def create_service(self, name, service_type, **kwargs): """Create a service.""" OS_KSADM = "http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0" create_service = xml.Element("service", xmlns=OS_KSADM, name=name, type=service_type, description=kwargs.get('description')) resp, body = self.post('OS-KSADM/services', str(xml.Document(create_service))) return resp, self._parse_resp(body) class TokenClientXML(identity_client.TokenClientJSON): TYPE = "xml" def auth(self, user, password, tenant=None): passwordCreds = xml.Element('passwordCredentials', username=user, password=password) auth_kwargs = {} if tenant: auth_kwargs['tenantName'] = tenant auth = xml.Element('auth', **auth_kwargs) auth.append(passwordCreds) resp, body = self.post(self.auth_url, body=str(xml.Document(auth))) return resp, body['access'] def auth_token(self, token_id, tenant=None): tokenCreds = xml.Element('token', id=token_id) auth_kwargs = {} if tenant: auth_kwargs['tenantName'] = tenant auth = xml.Element('auth', **auth_kwargs) auth.append(tokenCreds) resp, body = self.post(self.auth_url, body=str(xml.Document(auth))) return resp, body['access'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/__init__.py0000664000175000017500000000212312332757070023510 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base Service class, which acts as a descriptor for an OpenStack service in the test environment """ class Service(object): def __init__(self, config): """ Initializes the service. :param config: `tempest.config.Config` object """ self.config = config def get_client(self): """ Returns a client object that may be used to query the service API. """ raise NotImplementedError tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/0000775000175000017500000000000012332757136023416 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/json/0000775000175000017500000000000012332757136024367 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/json/telemetry_client.py0000664000175000017500000000330112332757070030303 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common import rest_client from tempest.openstack.common import jsonutils as json import tempest.services.telemetry.telemetry_client_base as client class TelemetryClientJSON(client.TelemetryClientBase): def get_rest_client(self, auth_provider): return rest_client.RestClient(auth_provider) def deserialize(self, body): return json.loads(body.replace("\n", "")) def serialize(self, body): return json.dumps(body) def add_sample(self, sample_list, meter_name, meter_unit, volume, sample_type, resource_id, **kwargs): sample = {"counter_name": meter_name, "counter_unit": meter_unit, "counter_volume": volume, "counter_type": sample_type, "resource_id": resource_id} for key in kwargs: sample[key] = kwargs[key] sample_list.append(self.serialize(sample)) return sample_list def create_sample(self, meter_name, sample_list): uri = "%s/meters/%s" % (self.uri_prefix, meter_name) return self.post(uri, str(sample_list)) tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/json/__init__.py0000664000175000017500000000000012332757070026463 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/__init__.py0000664000175000017500000000000012332757070025512 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/telemetry_client_base.py0000664000175000017500000001044112332757070030327 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc import six import urllib from tempest import config CONF = config.CONF @six.add_metaclass(abc.ABCMeta) class TelemetryClientBase(object): """ Tempest REST client for Ceilometer V2 API. Implements the following basic Ceilometer abstractions: resources meters alarms queries statistics """ def __init__(self, auth_provider): self.rest_client = self.get_rest_client(auth_provider) self.rest_client.service = CONF.telemetry.catalog_type self.version = '2' self.uri_prefix = "v%s" % self.version @abc.abstractmethod def get_rest_client(self, auth_provider): """ :param config: :param username: :param password: :param auth_url: :param tenant_name: :return: RestClient """ @abc.abstractmethod def deserialize(self, body): """ :param body: :return: Deserialize body """ @abc.abstractmethod def serialize(self, body): """ :param body: :return: Serialize body """ def post(self, uri, body): body = self.serialize(body) resp, body = self.rest_client.post(uri, body) body = self.deserialize(body) return resp, body def put(self, uri, body): body = self.serialize(body) resp, body = self.rest_client.put(uri, body) body = self.deserialize(body) return resp, body def get(self, uri): resp, body = self.rest_client.get(uri) body = self.deserialize(body) return resp, body def delete(self, uri): resp, body = self.rest_client.delete(uri) if body: body = self.deserialize(body) return resp, body def helper_list(self, uri, query=None, period=None): uri_dict = {} if query: uri_dict = {'q.field': query[0], 'q.op': query[1], 'q.value': query[2]} if period: uri_dict['period'] = period if uri_dict: uri += "?%s" % urllib.urlencode(uri_dict) return self.get(uri) def list_resources(self): uri = '%s/resources' % self.uri_prefix return self.get(uri) def list_meters(self): uri = '%s/meters' % self.uri_prefix return self.get(uri) def list_alarms(self): uri = '%s/alarms' % self.uri_prefix return self.get(uri) def list_statistics(self, meter, period=None, query=None): uri = "%s/meters/%s/statistics" % (self.uri_prefix, meter) return self.helper_list(uri, query, period) def list_samples(self, meter_id, query=None): uri = '%s/meters/%s' % (self.uri_prefix, meter_id) return self.helper_list(uri, query) def get_resource(self, resource_id): uri = '%s/resources/%s' % (self.uri_prefix, resource_id) return self.get(uri) def get_alarm(self, alarm_id): uri = '%s/alarms/%s' % (self.uri_prefix, alarm_id) return self.get(uri) def delete_alarm(self, alarm_id): uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id) return self.delete(uri) def create_alarm(self, **kwargs): uri = "%s/alarms" % self.uri_prefix return self.post(uri, kwargs) def update_alarm(self, alarm_id, **kwargs): uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id) return self.put(uri, kwargs) def alarm_get_state(self, alarm_id): uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id) return self.get(uri) def alarm_set_state(self, alarm_id, state): uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id) return self.put(uri, state) tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/xml/0000775000175000017500000000000012332757136024216 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/xml/telemetry_client.py0000664000175000017500000000252212332757070030136 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils as common import tempest.services.telemetry.telemetry_client_base as client class TelemetryClientXML(client.TelemetryClientBase): TYPE = "xml" def get_rest_client(self, auth_provider): rc = rest_client.RestClient(auth_provider) rc.TYPE = self.TYPE return rc def _parse_array(self, body): array = [] for child in body.getchildren(): array.append(common.xml_to_json(child)) return array def serialize(self, body): return str(common.Document(body)) def deserialize(self, body): return self._parse_array(etree.fromstring(body)) tempest-2014.1.dev4108.gf22b6cc/tempest/services/telemetry/xml/__init__.py0000664000175000017500000000000012332757070026312 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/queuing/0000775000175000017500000000000012332757136023061 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/queuing/json/0000775000175000017500000000000012332757136024032 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/queuing/json/queuing_client.py0000664000175000017500000000352612332757070027422 0ustar chuckchuck00000000000000# Copyright (c) 2014 Rackspace, 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 json from tempest.common import rest_client from tempest import config CONF = config.CONF class QueuingClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(QueuingClientJSON, self).__init__(auth_provider) self.service = CONF.queuing.catalog_type self.version = '1' self.uri_prefix = 'v{0}'.format(self.version) def list_queues(self): uri = '{0}/queues'.format(self.uri_prefix) resp, body = self.get(uri) body = json.loads(body) return resp, body def create_queue(self, queue_name): uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name) resp, body = self.put(uri, body=None) return resp, body def get_queue(self, queue_name): uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name) resp, body = self.get(uri) body = json.loads(body) return resp, body def head_queue(self, queue_name): uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name) resp, body = self.head(uri) body = json.loads(body) return resp, body def delete_queue(self, queue_name): uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name) resp = self.delete(uri) return resp tempest-2014.1.dev4108.gf22b6cc/tempest/services/queuing/json/__init__.py0000664000175000017500000000000012332757070026126 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/queuing/__init__.py0000664000175000017500000000000012332757070025155 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/0000775000175000017500000000000012332757136023060 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/0000775000175000017500000000000012332757136023410 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/0000775000175000017500000000000012332757136024361 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/hosts_client.py0000664000175000017500000000561012332757070027430 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 json import urllib from tempest.api_schema.compute import hosts as schema from tempest.api_schema.compute.v3 import hosts as v3_schema from tempest.common import rest_client from tempest import config CONF = config.CONF class HostsV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(HostsV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_hosts(self, params=None): """Lists all hosts.""" url = 'os-hosts' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_hosts, resp, body) return resp, body['hosts'] def show_host_detail(self, hostname): """Show detail information for the host.""" resp, body = self.get("os-hosts/%s" % str(hostname)) body = json.loads(body) self.validate_response(schema.show_host_detail, resp, body) return resp, body['host'] def update_host(self, hostname, **kwargs): """Update a host.""" request_body = { 'status': None, 'maintenance_mode': None, } request_body.update(**kwargs) request_body = json.dumps({'host': request_body}) resp, body = self.put("os-hosts/%s" % str(hostname), request_body) body = json.loads(body) self.validate_response(v3_schema.update_host, resp, body) return resp, body def startup_host(self, hostname): """Startup a host.""" resp, body = self.get("os-hosts/%s/startup" % str(hostname)) body = json.loads(body) self.validate_response(v3_schema.startup_host, resp, body) return resp, body['host'] def shutdown_host(self, hostname): """Shutdown a host.""" resp, body = self.get("os-hosts/%s/shutdown" % str(hostname)) body = json.loads(body) self.validate_response(v3_schema.shutdown_host, resp, body) return resp, body['host'] def reboot_host(self, hostname): """reboot a host.""" resp, body = self.get("os-hosts/%s/reboot" % str(hostname)) body = json.loads(body) self.validate_response(v3_schema.reboot_host, resp, body) return resp, body['host'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/aggregates_client.py0000664000175000017500000001020112332757070030371 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import aggregates as schema from tempest.api_schema.compute.v3 import aggregates as v3_schema from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class AggregatesV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(AggregatesV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_aggregates(self): """Get aggregate list.""" resp, body = self.get("os-aggregates") body = json.loads(body) self.validate_response(schema.list_aggregates, resp, body) return resp, body['aggregates'] def get_aggregate(self, aggregate_id): """Get details of the given aggregate.""" resp, body = self.get("os-aggregates/%s" % str(aggregate_id)) body = json.loads(body) self.validate_response(schema.get_aggregate, resp, body) return resp, body['aggregate'] def create_aggregate(self, **kwargs): """Creates a new aggregate.""" post_body = json.dumps({'aggregate': kwargs}) resp, body = self.post('os-aggregates', post_body) body = json.loads(body) self.validate_response(v3_schema.create_aggregate, resp, body) return resp, body['aggregate'] def update_aggregate(self, aggregate_id, name, availability_zone=None): """Update a aggregate.""" put_body = { 'name': name, 'availability_zone': availability_zone } put_body = json.dumps({'aggregate': put_body}) resp, body = self.put('os-aggregates/%s' % str(aggregate_id), put_body) body = json.loads(body) self.validate_response(schema.update_aggregate, resp, body) return resp, body['aggregate'] def delete_aggregate(self, aggregate_id): """Deletes the given aggregate.""" resp, body = self.delete("os-aggregates/%s" % str(aggregate_id)) self.validate_response(v3_schema.delete_aggregate, resp, body) return resp, body def is_resource_deleted(self, id): try: self.get_aggregate(id) except exceptions.NotFound: return True return False def add_host(self, aggregate_id, host): """Adds a host to the given aggregate.""" post_body = { 'host': host, } post_body = json.dumps({'add_host': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body) body = json.loads(body) return resp, body['aggregate'] def remove_host(self, aggregate_id, host): """Removes a host from the given aggregate.""" post_body = { 'host': host, } post_body = json.dumps({'remove_host': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body) body = json.loads(body) return resp, body['aggregate'] def set_metadata(self, aggregate_id, meta): """Replaces the aggregate's existing metadata with new metadata.""" post_body = { 'metadata': meta, } post_body = json.dumps({'set_metadata': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body) body = json.loads(body) self.validate_response(schema.aggregate_set_metadata, resp, body) return resp, body['aggregate'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/servers_client.py0000664000175000017500000004507212332757070027767 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 Hewlett-Packard Development Company, L.P. # Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time import urllib from tempest.api_schema.compute import servers as common_schema from tempest.api_schema.compute.v3 import servers as schema from tempest.common import rest_client from tempest.common import waiters from tempest import config from tempest import exceptions CONF = config.CONF class ServersV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ServersV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def create_server(self, name, image_ref, flavor_ref, **kwargs): """ Creates an instance of a server. name (Required): The name of the server. image_ref (Required): Reference to the image used to build the server. flavor_ref (Required): The flavor used to build the server. Following optional keyword arguments are accepted: admin_password: Sets the initial root password. key_name: Key name of keypair that was created earlier. meta: A dictionary of values to be used as metadata. security_groups: A list of security group dicts. networks: A list of network dicts with UUID and fixed_ip. user_data: User data for instance. availability_zone: Availability zone in which to launch instance. access_ip_v4: The IPv4 access address for the server. access_ip_v6: The IPv6 access address for the server. min_count: Count of minimum number of instances to launch. max_count: Count of maximum number of instances to launch. disk_config: Determines if user or admin controls disk configuration. return_reservation_id: Enable/Disable the return of reservation id """ post_body = { 'name': name, 'image_ref': image_ref, 'flavor_ref': flavor_ref } for option in ['admin_password', 'key_name', 'networks', ('os-security-groups:security_groups', 'security_groups'), ('os-user-data:user_data', 'user_data'), ('os-availability-zone:availability_zone', 'availability_zone'), ('os-access-ips:access_ip_v4', 'access_ip_v4'), ('os-access-ips:access_ip_v6', 'access_ip_v6'), ('os-multiple-create:min_count', 'min_count'), ('os-multiple-create:max_count', 'max_count'), ('metadata', 'meta'), ('os-disk-config:disk_config', 'disk_config'), ('os-multiple-create:return_reservation_id', 'return_reservation_id')]: if isinstance(option, tuple): post_param = option[0] key = option[1] else: post_param = option key = option value = kwargs.get(key) if value is not None: post_body[post_param] = value post_body = json.dumps({'server': post_body}) resp, body = self.post('servers', post_body) body = json.loads(body) # NOTE(maurosr): this deals with the case of multiple server create # with return reservation id set True if 'servers_reservation' in body: return resp, body['servers_reservation'] self.validate_response(schema.create_server, resp, body) return resp, body['server'] def update_server(self, server_id, name=None, meta=None, access_ip_v4=None, access_ip_v6=None, disk_config=None): """ Updates the properties of an existing server. server_id: The id of an existing server. name: The name of the server. access_ip_v4: The IPv4 access address for the server. access_ip_v6: The IPv6 access address for the server. """ post_body = {} if meta is not None: post_body['metadata'] = meta if name is not None: post_body['name'] = name if access_ip_v4 is not None: post_body['os-access-ips:access_ip_v4'] = access_ip_v4 if access_ip_v6 is not None: post_body['os-access-ips:access_ip_v6'] = access_ip_v6 if disk_config is not None: post_body['os-disk-config:disk_config'] = disk_config post_body = json.dumps({'server': post_body}) resp, body = self.put("servers/%s" % str(server_id), post_body) body = json.loads(body) self.validate_response(schema.update_server, resp, body) return resp, body['server'] def get_server(self, server_id): """Returns the details of an existing server.""" resp, body = self.get("servers/%s" % str(server_id)) body = json.loads(body) return resp, body['server'] def delete_server(self, server_id): """Deletes the given server.""" resp, body = self.delete("servers/%s" % str(server_id)) self.validate_response(common_schema.delete_server, resp, body) return resp, body def list_servers(self, params=None): """Lists all servers for a user.""" url = 'servers' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body def list_servers_with_detail(self, params=None): """Lists all servers in detail for a user.""" url = 'servers/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body def wait_for_server_status(self, server_id, status, extra_timeout=0, raise_on_error=True): """Waits for a server to reach a given status.""" return waiters.wait_for_server_status(self, server_id, status, extra_timeout=extra_timeout, raise_on_error=raise_on_error) def wait_for_server_termination(self, server_id, ignore_error=False): """Waits for server to reach termination.""" start_time = int(time.time()) while True: try: resp, body = self.get_server(server_id) except exceptions.NotFound: return server_status = body['status'] if server_status == 'ERROR' and not ignore_error: raise exceptions.BuildErrorException(server_id=server_id) if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def list_addresses(self, server_id): """Lists all addresses for a server.""" resp, body = self.get("servers/%s/ips" % str(server_id)) body = json.loads(body) return resp, body['addresses'] def list_addresses_by_network(self, server_id, network_id): """Lists all addresses of a specific network type for a server.""" resp, body = self.get("servers/%s/ips/%s" % (str(server_id), network_id)) body = json.loads(body) return resp, body def action(self, server_id, action_name, response_key, **kwargs): post_body = json.dumps({action_name: kwargs}) resp, body = self.post('servers/%s/action' % str(server_id), post_body) if response_key is not None: body = json.loads(body)[response_key] return resp, body def create_backup(self, server_id, backup_type, rotation, name): """Backup a server instance.""" return self.action(server_id, "create_backup", None, backup_type=backup_type, rotation=rotation, name=name) def change_password(self, server_id, admin_password): """Changes the root password for the server.""" return self.action(server_id, 'change_password', None, admin_password=admin_password) def get_password(self, server_id): resp, body = self.get("servers/%s/os-server-password" % str(server_id)) body = json.loads(body) self.validate_response(common_schema.get_password, resp, body) return resp, body def delete_password(self, server_id): """ Removes the encrypted server password from the metadata server Note that this does not actually change the instance server password. """ return self.delete("servers/%s/os-server-password" % str(server_id)) def reboot(self, server_id, reboot_type): """Reboots a server.""" return self.action(server_id, 'reboot', None, type=reboot_type) def rebuild(self, server_id, image_ref, **kwargs): """Rebuilds a server with a new image.""" kwargs['image_ref'] = image_ref if 'disk_config' in kwargs: kwargs['os-disk-config:disk_config'] = kwargs['disk_config'] del kwargs['disk_config'] return self.action(server_id, 'rebuild', 'server', **kwargs) def resize(self, server_id, flavor_ref, **kwargs): """Changes the flavor of a server.""" kwargs['flavor_ref'] = flavor_ref if 'disk_config' in kwargs: kwargs['os-disk-config:disk_config'] = kwargs['disk_config'] del kwargs['disk_config'] return self.action(server_id, 'resize', None, **kwargs) def confirm_resize(self, server_id, **kwargs): """Confirms the flavor change for a server.""" return self.action(server_id, 'confirm_resize', None, **kwargs) def revert_resize(self, server_id, **kwargs): """Reverts a server back to its original flavor.""" return self.action(server_id, 'revert_resize', None, **kwargs) def create_image(self, server_id, name, meta=None): """Creates an image of the original server.""" post_body = { 'create_image': { 'name': name, } } if meta is not None: post_body['create_image']['metadata'] = meta post_body = json.dumps(post_body) resp, body = self.post('servers/%s/action' % str(server_id), post_body) return resp, body def list_server_metadata(self, server_id): resp, body = self.get("servers/%s/metadata" % str(server_id)) body = json.loads(body) self.validate_response(common_schema.list_server_metadata, resp, body) return resp, body['metadata'] def set_server_metadata(self, server_id, meta, no_metadata_field=False): if no_metadata_field: post_body = "" else: post_body = json.dumps({'metadata': meta}) resp, body = self.put('servers/%s/metadata' % str(server_id), post_body) body = json.loads(body) self.validate_response(common_schema.set_server_metadata, resp, body) return resp, body['metadata'] def update_server_metadata(self, server_id, meta): post_body = json.dumps({'metadata': meta}) resp, body = self.post('servers/%s/metadata' % str(server_id), post_body) body = json.loads(body) return resp, body['metadata'] def get_server_metadata_item(self, server_id, key): resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key)) body = json.loads(body) self.validate_response(schema.set_get_server_metadata_item, resp, body) return resp, body['metadata'] def set_server_metadata_item(self, server_id, key, meta): post_body = json.dumps({'metadata': meta}) resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key), post_body) body = json.loads(body) self.validate_response(schema.set_get_server_metadata_item, resp, body) return resp, body['metadata'] def delete_server_metadata_item(self, server_id, key): resp, body = self.delete("servers/%s/metadata/%s" % (str(server_id), key)) self.validate_response(common_schema.delete_server_metadata_item, resp, body) return resp, body def stop(self, server_id, **kwargs): return self.action(server_id, 'stop', None, **kwargs) def start(self, server_id, **kwargs): return self.action(server_id, 'start', None, **kwargs) def attach_volume(self, server_id, volume_id, device='/dev/vdz'): """Attaches a volume to a server instance.""" resp, body = self.action(server_id, 'attach', None, volume_id=volume_id, device=device) self.validate_response(schema.attach_detach_volume, resp, body) return resp, body def detach_volume(self, server_id, volume_id): """Detaches a volume from a server instance.""" resp, body = self.action(server_id, 'detach', None, volume_id=volume_id) self.validate_response(schema.attach_detach_volume, resp, body) return resp, body def live_migrate_server(self, server_id, dest_host, use_block_migration): """This should be called with administrator privileges .""" migrate_params = { "disk_over_commit": False, "block_migration": use_block_migration, "host": dest_host } req_body = json.dumps({'migrate_live': migrate_params}) resp, body = self.post("servers/%s/action" % str(server_id), req_body) return resp, body def migrate_server(self, server_id, **kwargs): """Migrates a server to a new host.""" return self.action(server_id, 'migrate', None, **kwargs) def lock_server(self, server_id, **kwargs): """Locks the given server.""" return self.action(server_id, 'lock', None, **kwargs) def unlock_server(self, server_id, **kwargs): """UNlocks the given server.""" return self.action(server_id, 'unlock', None, **kwargs) def suspend_server(self, server_id, **kwargs): """Suspends the provided server.""" return self.action(server_id, 'suspend', None, **kwargs) def resume_server(self, server_id, **kwargs): """Un-suspends the provided server.""" return self.action(server_id, 'resume', None, **kwargs) def pause_server(self, server_id, **kwargs): """Pauses the provided server.""" return self.action(server_id, 'pause', None, **kwargs) def unpause_server(self, server_id, **kwargs): """Un-pauses the provided server.""" return self.action(server_id, 'unpause', None, **kwargs) def reset_state(self, server_id, state='error'): """Resets the state of a server to active/error.""" return self.action(server_id, 'reset_state', None, state=state) def shelve_server(self, server_id, **kwargs): """Shelves the provided server.""" return self.action(server_id, 'shelve', None, **kwargs) def unshelve_server(self, server_id, **kwargs): """Un-shelves the provided server.""" return self.action(server_id, 'unshelve', None, **kwargs) def shelve_offload_server(self, server_id, **kwargs): """Shelve-offload the provided server.""" return self.action(server_id, 'shelve_offload', None, **kwargs) def get_console_output(self, server_id, length): return self.action(server_id, 'get_console_output', 'output', length=length) def rescue_server(self, server_id, **kwargs): """Rescue the provided server.""" return self.action(server_id, 'rescue', None, **kwargs) def unrescue_server(self, server_id): """Unrescue the provided server.""" return self.action(server_id, 'unrescue', None) def get_server_diagnostics(self, server_id): """Get the usage data for a server.""" resp, body = self.get("servers/%s/os-server-diagnostics" % str(server_id)) return resp, json.loads(body) def list_server_actions(self, server_id): """List the provided server action.""" resp, body = self.get("servers/%s/os-server-actions" % str(server_id)) body = json.loads(body) return resp, body['server_actions'] def get_server_action(self, server_id, request_id): """Returns the action details of the provided server.""" resp, body = self.get("servers/%s/os-server-actions/%s" % (str(server_id), str(request_id))) body = json.loads(body) return resp, body['server_action'] def force_delete_server(self, server_id, **kwargs): """Force delete a server.""" return self.action(server_id, 'force_delete', None, **kwargs) def restore_soft_deleted_server(self, server_id, **kwargs): """Restore a soft-deleted server.""" return self.action(server_id, 'restore', None, **kwargs) def get_vnc_console(self, server_id, type): """Get URL of VNC console.""" post_body = json.dumps({ "get_vnc_console": { "type": type } }) resp, body = self.post('servers/%s/action' % str(server_id), post_body) body = json.loads(body) self.validate_response(common_schema.get_vnc_console, resp, body) return resp, body['console'] def reset_network(self, server_id, **kwargs): """Resets the Network of a server""" return self.action(server_id, 'reset_network', None, **kwargs) def inject_network_info(self, server_id, **kwargs): """Inject the Network Info into server""" return self.action(server_id, 'inject_network_info', None, **kwargs) tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/interfaces_client.py0000664000175000017500000000773712332757070030427 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time from tempest.api_schema.compute import interfaces as common_schema from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class InterfacesV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(InterfacesV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_interfaces(self, server): resp, body = self.get('servers/%s/os-attach-interfaces' % server) body = json.loads(body) return resp, body['interface_attachments'] def create_interface(self, server, port_id=None, network_id=None, fixed_ip=None): post_body = dict() if port_id: post_body['port_id'] = port_id if network_id: post_body['net_id'] = network_id if fixed_ip: post_body['fixed_ips'] = [dict(ip_address=fixed_ip)] post_body = json.dumps({'interface_attachment': post_body}) resp, body = self.post('servers/%s/os-attach-interfaces' % server, body=post_body) body = json.loads(body) return resp, body['interface_attachment'] def show_interface(self, server, port_id): resp, body =\ self.get('servers/%s/os-attach-interfaces/%s' % (server, port_id)) body = json.loads(body) return resp, body['interface_attachment'] def delete_interface(self, server, port_id): resp, body =\ self.delete('servers/%s/os-attach-interfaces/%s' % (server, port_id)) self.validate_response(common_schema.delete_interface, resp, body) return resp, body def wait_for_interface_status(self, server, port_id, status): """Waits for a interface to reach a given status.""" resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] start = int(time.time()) while(interface_status != status): time.sleep(self.build_interval) resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] timed_out = int(time.time()) - start >= self.build_timeout if interface_status != status and timed_out: message = ('Interface %s failed to reach %s status within ' 'the required time (%s s).' % (port_id, status, self.build_timeout)) raise exceptions.TimeoutException(message) return resp, body def add_fixed_ip(self, server_id, network_id): """Add a fixed IP to input server instance.""" post_body = json.dumps({ 'add_fixed_ip': { 'network_id': network_id } }) resp, body = self.post('servers/%s/action' % str(server_id), post_body) return resp, body def remove_fixed_ip(self, server_id, ip_address): """Remove input fixed IP from input server instance.""" post_body = json.dumps({ 'remove_fixed_ip': { 'address': ip_address } }) resp, body = self.post('servers/%s/action' % str(server_id), post_body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/availability_zone_client.py0000664000175000017500000000311412332757070031772 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v3 import availability_zone as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class AvailabilityZoneV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(AvailabilityZoneV3ClientJSON, self).__init__( auth_provider) self.service = CONF.compute.catalog_v3_type def get_availability_zone_list(self): resp, body = self.get('os-availability-zone') body = json.loads(body) self.validate_response(schema.get_availability_zone_list, resp, body) return resp, body['availability_zone_info'] def get_availability_zone_list_detail(self): resp, body = self.get('os-availability-zone/detail') body = json.loads(body) self.validate_response(schema.get_availability_zone_list_detail, resp, body) return resp, body['availability_zone_info'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/hypervisor_client.py0000664000175000017500000000643712332757070030512 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import hypervisors as common_schema from tempest.api_schema.compute.v3 import hypervisors as v3schema from tempest.common import rest_client from tempest import config CONF = config.CONF class HypervisorV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(HypervisorV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def get_hypervisor_list(self): """List hypervisors information.""" resp, body = self.get('os-hypervisors') body = json.loads(body) self.validate_response(common_schema.common_hypervisors_detail, resp, body) return resp, body['hypervisors'] def get_hypervisor_list_details(self): """Show detailed hypervisors information.""" resp, body = self.get('os-hypervisors/detail') body = json.loads(body) self.validate_response(v3schema.list_hypervisors_detail, resp, body) return resp, body['hypervisors'] def get_hypervisor_show_details(self, hyper_id): """Display the details of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s' % hyper_id) body = json.loads(body) self.validate_response(v3schema.show_hypervisor, resp, body) return resp, body['hypervisor'] def get_hypervisor_servers(self, hyper_name): """List instances belonging to the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/servers' % hyper_name) body = json.loads(body) self.validate_response(v3schema.hypervisors_servers, resp, body) return resp, body['hypervisor'] def get_hypervisor_stats(self): """Get hypervisor statistics over all compute nodes.""" resp, body = self.get('os-hypervisors/statistics') body = json.loads(body) self.validate_response(common_schema.hypervisor_statistics, resp, body) return resp, body['hypervisor_statistics'] def get_hypervisor_uptime(self, hyper_id): """Display the uptime of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id) body = json.loads(body) self.validate_response(common_schema.hypervisor_uptime, resp, body) return resp, body['hypervisor'] def search_hypervisor(self, hyper_name): """Search specified hypervisor.""" resp, body = self.get('os-hypervisors/search?query=%s' % hyper_name) body = json.loads(body) self.validate_response(common_schema.common_hypervisors_detail, resp, body) return resp, body['hypervisors'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/agents_client.py0000664000175000017500000000413212332757070027547 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute import agents as common_schema from tempest.api_schema.compute.v3 import agents as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class AgentsV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(AgentsV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_agents(self, params=None): """List all agent builds.""" url = 'os-agents' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(common_schema.list_agents, resp, body) return resp, body['agents'] def create_agent(self, **kwargs): """Create an agent build.""" post_body = json.dumps({'agent': kwargs}) resp, body = self.post('os-agents', post_body) return resp, self._parse_resp(body) def delete_agent(self, agent_id): """Delete an existing agent build.""" resp, body = self.delete("os-agents/%s" % str(agent_id)) self.validate_response(schema.delete_agent, resp, body) return resp, body def update_agent(self, agent_id, **kwargs): """Update an agent build.""" put_body = json.dumps({'agent': kwargs}) resp, body = self.put('os-agents/%s' % str(agent_id), put_body) return resp, self._parse_resp(body) tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/__init__.py0000664000175000017500000000000012332757070026455 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/keypairs_client.py0000664000175000017500000000455712332757070030130 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import keypairs as common_schema from tempest.api_schema.compute.v3 import keypairs as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class KeyPairsV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(KeyPairsV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_keypairs(self): resp, body = self.get("keypairs") body = json.loads(body) # Each returned keypair is embedded within an unnecessary 'keypair' # element which is a deviation from other resources like floating-ips, # servers, etc. A bug? # For now we shall adhere to the spec, but the spec for keypairs # is yet to be found self.validate_response(common_schema.list_keypairs, resp, body) return resp, body['keypairs'] def get_keypair(self, key_name): resp, body = self.get("keypairs/%s" % str(key_name)) body = json.loads(body) self.validate_response(schema.get_keypair, resp, body) return resp, body['keypair'] def create_keypair(self, name, pub_key=None): post_body = {'keypair': {'name': name}} if pub_key: post_body['keypair']['public_key'] = pub_key post_body = json.dumps(post_body) resp, body = self.post("keypairs", body=post_body) body = json.loads(body) self.validate_response(schema.create_keypair, resp, body) return resp, body['keypair'] def delete_keypair(self, key_name): resp, body = self.delete("keypairs/%s" % str(key_name)) self.validate_response(schema.delete_keypair, resp, body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/quotas_client.py0000664000175000017500000000775112332757070027614 0ustar chuckchuck00000000000000# Copyright 2012 NTT Data # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v3 import quotas as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class QuotasV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(QuotasV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def get_quota_set(self, tenant_id, user_id=None): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % str(tenant_id) if user_id: url += '?user_id=%s' % str(user_id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.quota_set, resp, body) return resp, body['quota_set'] def get_quota_set_detail(self, tenant_id): """Get the quota set detail for a tenant.""" url = 'os-quota-sets/%s/detail' % str(tenant_id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.quota_set_detail, resp, body) return resp, body['quota_set'] def get_default_quota_set(self, tenant_id): """List the default quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % str(tenant_id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.quota_set, resp, body) return resp, body['quota_set'] def update_quota_set(self, tenant_id, user_id=None, force=None, metadata_items=None, ram=None, floating_ips=None, fixed_ips=None, key_pairs=None, instances=None, security_group_rules=None, cores=None, security_groups=None): """ Updates the tenant's quota limits for one or more resources """ post_body = {} if force is not None: post_body['force'] = force if metadata_items is not None: post_body['metadata_items'] = metadata_items if ram is not None: post_body['ram'] = ram if floating_ips is not None: post_body['floating_ips'] = floating_ips if fixed_ips is not None: post_body['fixed_ips'] = fixed_ips if key_pairs is not None: post_body['key_pairs'] = key_pairs if instances is not None: post_body['instances'] = instances if security_group_rules is not None: post_body['security_group_rules'] = security_group_rules if cores is not None: post_body['cores'] = cores if security_groups is not None: post_body['security_groups'] = security_groups post_body = json.dumps({'quota_set': post_body}) if user_id: resp, body = self.put('os-quota-sets/%s?user_id=%s' % (str(tenant_id), str(user_id)), post_body) else: resp, body = self.put('os-quota-sets/%s' % str(tenant_id), post_body) body = json.loads(body) self.validate_response(schema.quota_set, resp, body) return resp, body['quota_set'] def delete_quota_set(self, tenant_id): """Delete the tenant's quota set.""" resp, body = self.delete('os-quota-sets/%s' % str(tenant_id)) self.validate_response(schema.delete_quota, resp, body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/version_client.py0000664000175000017500000000224412332757070027755 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import version as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class VersionV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(VersionV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def get_version(self): resp, body = self.get('') body = json.loads(body) self.validate_response(schema.version, resp, body) return resp, body['version'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/migration_client.py0000664000175000017500000000251412332757070030261 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # # 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 json import urllib from tempest.api_schema.compute import migrations as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class MigrationsV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(MigrationsV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_migrations(self, params=None): """Lists all migrations.""" url = 'os-migrations' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_migrations, resp, body) return resp, body['migrations'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/extensions_client.py0000664000175000017500000000312312332757070030464 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v3 import extensions as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class ExtensionsV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ExtensionsV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_extensions, resp, body) return resp, body['extensions'] def is_enabled(self, extension): _, extensions = self.list_extensions() exts = extensions['extensions'] return any([e for e in exts if e['name'] == extension]) def get_extension(self, extension_alias): resp, body = self.get('extensions/%s' % extension_alias) body = json.loads(body) return resp, body['extension'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/services_client.py0000664000175000017500000000443112332757070030113 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute import services as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class ServicesV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ServicesV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_services(self, params=None): url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_services, resp, body) return resp, body['services'] def enable_service(self, host_name, binary): """ Enable service on a host host_name: Name of host binary: Service binary """ post_body = json.dumps({ 'service': { 'binary': binary, 'host': host_name } }) resp, body = self.put('os-services/enable', post_body) body = json.loads(body) self.validate_response(schema.enable_service, resp, body) return resp, body['service'] def disable_service(self, host_name, binary): """ Disable service on a host host_name: Name of host binary: Service binary """ post_body = json.dumps({ 'service': { 'binary': binary, 'host': host_name } }) resp, body = self.put('os-services/disable', post_body) body = json.loads(body) return resp, body['service'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/certificates_client.py0000664000175000017500000000311612332757070030734 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import certificates as schema from tempest.api_schema.compute.v3 import certificates as v3schema from tempest.common import rest_client from tempest import config CONF = config.CONF class CertificatesV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(CertificatesV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def get_certificate(self, id): url = "os-certificates/%s" % (id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.get_certificate, resp, body) return resp, body['certificate'] def create_certificate(self): """create certificates.""" url = "os-certificates" resp, body = self.post(url, None) body = json.loads(body) self.validate_response(v3schema.create_certificate, resp, body) return resp, body['certificate'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/json/flavors_client.py0000664000175000017500000001611512332757070027746 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute import flavors as common_schema from tempest.api_schema.compute import flavors_access as schema_access from tempest.api_schema.compute import flavors_extra_specs \ as schema_extra_specs from tempest.api_schema.compute.v3 import flavors as v3schema from tempest.common import rest_client from tempest import config CONF = config.CONF class FlavorsV3ClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(FlavorsV3ClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_v3_type def list_flavors(self, params=None): url = 'flavors' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(common_schema.list_flavors, resp, body) return resp, body['flavors'] def list_flavors_with_detail(self, params=None): url = 'flavors/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(v3schema.list_flavors_details, resp, body) return resp, body['flavors'] def get_flavor_details(self, flavor_id): resp, body = self.get("flavors/%s" % str(flavor_id)) body = json.loads(body) self.validate_response(v3schema.get_flavor_details, resp, body) return resp, body['flavor'] def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs): """Creates a new flavor or instance type.""" post_body = { 'name': name, 'ram': ram, 'vcpus': vcpus, 'disk': disk, 'id': flavor_id, } if kwargs.get('ephemeral'): post_body['ephemeral'] = kwargs.get('ephemeral') if kwargs.get('swap'): post_body['swap'] = kwargs.get('swap') if kwargs.get('rxtx'): post_body['os-flavor-rxtx:rxtx_factor'] = kwargs.get('rxtx') if kwargs.get('is_public'): post_body['flavor-access:is_public'] = kwargs.get('is_public') post_body = json.dumps({'flavor': post_body}) resp, body = self.post('flavors', post_body) body = json.loads(body) self.validate_response(v3schema.create_flavor_details, resp, body) return resp, body['flavor'] def delete_flavor(self, flavor_id): """Deletes the given flavor.""" resp, body = self.delete("flavors/{0}".format(flavor_id)) self.validate_response(v3schema.delete_flavor, resp, body) return resp, body def is_resource_deleted(self, id): # Did not use get_flavor_details(id) for verification as it gives # 200 ok even for deleted id. LP #981263 # we can remove the loop here and use get by ID when bug gets sortedout resp, flavors = self.list_flavors_with_detail() for flavor in flavors: if flavor['id'] == id: return False return True def set_flavor_extra_spec(self, flavor_id, specs): """Sets extra Specs to the mentioned flavor.""" post_body = json.dumps({'extra_specs': specs}) resp, body = self.post('flavors/%s/flavor-extra-specs' % flavor_id, post_body) body = json.loads(body) self.validate_response(v3schema.set_flavor_extra_specs, resp, body) return resp, body['extra_specs'] def get_flavor_extra_spec(self, flavor_id): """Gets extra Specs details of the mentioned flavor.""" resp, body = self.get('flavors/%s/flavor-extra-specs' % flavor_id) body = json.loads(body) self.validate_response(schema_extra_specs.flavor_extra_specs, resp, body) return resp, body['extra_specs'] def get_flavor_extra_spec_with_key(self, flavor_id, key): """Gets extra Specs key-value of the mentioned flavor and key.""" resp, body = self.get('flavors/%s/flavor-extra-specs/%s' % (str(flavor_id), key)) body = json.loads(body) self.validate_response(schema_extra_specs.flavor_extra_specs_key, resp, body) return resp, body def update_flavor_extra_spec(self, flavor_id, key, **kwargs): """Update specified extra Specs of the mentioned flavor and key.""" resp, body = self.put('flavors/%s/flavor-extra-specs/%s' % (flavor_id, key), json.dumps(kwargs)) body = json.loads(body) self.validate_response(schema_extra_specs.flavor_extra_specs_key, resp, body) return resp, body def unset_flavor_extra_spec(self, flavor_id, key): """Unsets extra Specs from the mentioned flavor.""" resp, body = self.delete('flavors/%s/flavor-extra-specs/%s' % (str(flavor_id), key)) self.validate_response(v3schema.unset_flavor_extra_specs, resp, body) return resp, body def list_flavor_access(self, flavor_id): """Gets flavor access information given the flavor id.""" resp, body = self.get('flavors/%s/flavor-access' % flavor_id) body = json.loads(body) self.validate_response(schema_access.add_remove_list_flavor_access, resp, body) return resp, body['flavor_access'] def add_flavor_access(self, flavor_id, tenant_id): """Add flavor access for the specified tenant.""" post_body = { 'add_tenant_access': { 'tenant_id': tenant_id } } post_body = json.dumps(post_body) resp, body = self.post('flavors/%s/action' % flavor_id, post_body) body = json.loads(body) self.validate_response(schema_access.add_remove_list_flavor_access, resp, body) return resp, body['flavor_access'] def remove_flavor_access(self, flavor_id, tenant_id): """Remove flavor access from the specified tenant.""" post_body = { 'remove_tenant_access': { 'tenant_id': tenant_id } } post_body = json.dumps(post_body) resp, body = self.post('flavors/%s/action' % flavor_id, post_body) body = json.loads(body) self.validate_response(schema_access.add_remove_list_flavor_access, resp, body) return resp, body['flavor_access'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/v3/__init__.py0000664000175000017500000000000012332757070025504 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/0000775000175000017500000000000012332757136024031 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/hosts_client.py0000664000175000017500000000556712332757070027113 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 json import urllib from tempest.api_schema.compute import hosts as schema from tempest.api_schema.compute.v2 import hosts as v2_schema from tempest.common import rest_client from tempest import config CONF = config.CONF class HostsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(HostsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_hosts(self, params=None): """Lists all hosts.""" url = 'os-hosts' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_hosts, resp, body) return resp, body['hosts'] def show_host_detail(self, hostname): """Show detail information for the host.""" resp, body = self.get("os-hosts/%s" % str(hostname)) body = json.loads(body) self.validate_response(schema.show_host_detail, resp, body) return resp, body['host'] def update_host(self, hostname, **kwargs): """Update a host.""" request_body = { 'status': None, 'maintenance_mode': None, } request_body.update(**kwargs) request_body = json.dumps(request_body) resp, body = self.put("os-hosts/%s" % str(hostname), request_body) body = json.loads(body) self.validate_response(v2_schema.update_host, resp, body) return resp, body def startup_host(self, hostname): """Startup a host.""" resp, body = self.get("os-hosts/%s/startup" % str(hostname)) body = json.loads(body) self.validate_response(v2_schema.startup_host, resp, body) return resp, body['host'] def shutdown_host(self, hostname): """Shutdown a host.""" resp, body = self.get("os-hosts/%s/shutdown" % str(hostname)) body = json.loads(body) self.validate_response(v2_schema.shutdown_host, resp, body) return resp, body['host'] def reboot_host(self, hostname): """reboot a host.""" resp, body = self.get("os-hosts/%s/reboot" % str(hostname)) body = json.loads(body) self.validate_response(v2_schema.reboot_host, resp, body) return resp, body['host'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/volumes_extensions_client.py0000664000175000017500000001026112332757070031707 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time import urllib from tempest.api_schema.compute.v2 import volumes as schema from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class VolumesExtensionsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(VolumesExtensionsClientJSON, self).__init__( auth_provider) self.service = CONF.compute.catalog_type self.build_interval = CONF.volume.build_interval self.build_timeout = CONF.volume.build_timeout def list_volumes(self, params=None): """List all the volumes created.""" url = 'os-volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_volumes, resp, body) return resp, body['volumes'] def list_volumes_with_detail(self, params=None): """List all the details of volumes.""" url = 'os-volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_volumes, resp, body) return resp, body['volumes'] def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "os-volumes/%s" % str(volume_id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.create_get_volume, resp, body) return resp, body['volume'] def create_volume(self, size, **kwargs): """ Creates a new Volume. size(Required): Size of volume in GB. Following optional keyword arguments are accepted: display_name: Optional Volume Name. metadata: A dictionary of values to be used as metadata. """ post_body = { 'size': size, 'display_name': kwargs.get('display_name'), 'metadata': kwargs.get('metadata'), } post_body = json.dumps({'volume': post_body}) resp, body = self.post('os-volumes', post_body) body = json.loads(body) self.validate_response(schema.create_get_volume, resp, body) return resp, body['volume'] def delete_volume(self, volume_id): """Deletes the Specified Volume.""" resp, body = self.delete("os-volumes/%s" % str(volume_id)) self.validate_response(schema.delete_volume, resp, body) return resp, body def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_name = body['displayName'] volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = ('Volume %s failed to reach %s status within ' 'the required time (%s s).' % (volume_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/migrations_client.py0000664000175000017500000000250512332757070030114 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # # 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 json import urllib from tempest.api_schema.compute import migrations as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class MigrationsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(MigrationsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_migrations(self, params=None): """Lists all migrations.""" url = 'os-migrations' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_migrations, resp, body) return resp, body['migrations'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/aggregates_client.py0000664000175000017500000001017212332757070030050 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import aggregates as schema from tempest.api_schema.compute.v2 import aggregates as v2_schema from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class AggregatesClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(AggregatesClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_aggregates(self): """Get aggregate list.""" resp, body = self.get("os-aggregates") body = json.loads(body) self.validate_response(schema.list_aggregates, resp, body) return resp, body['aggregates'] def get_aggregate(self, aggregate_id): """Get details of the given aggregate.""" resp, body = self.get("os-aggregates/%s" % str(aggregate_id)) body = json.loads(body) self.validate_response(schema.get_aggregate, resp, body) return resp, body['aggregate'] def create_aggregate(self, **kwargs): """Creates a new aggregate.""" post_body = json.dumps({'aggregate': kwargs}) resp, body = self.post('os-aggregates', post_body) body = json.loads(body) self.validate_response(v2_schema.create_aggregate, resp, body) return resp, body['aggregate'] def update_aggregate(self, aggregate_id, name, availability_zone=None): """Update a aggregate.""" put_body = { 'name': name, 'availability_zone': availability_zone } put_body = json.dumps({'aggregate': put_body}) resp, body = self.put('os-aggregates/%s' % str(aggregate_id), put_body) body = json.loads(body) self.validate_response(schema.update_aggregate, resp, body) return resp, body['aggregate'] def delete_aggregate(self, aggregate_id): """Deletes the given aggregate.""" resp, body = self.delete("os-aggregates/%s" % str(aggregate_id)) self.validate_response(v2_schema.delete_aggregate, resp, body) return resp, body def is_resource_deleted(self, id): try: self.get_aggregate(id) except exceptions.NotFound: return True return False def add_host(self, aggregate_id, host): """Adds a host to the given aggregate.""" post_body = { 'host': host, } post_body = json.dumps({'add_host': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body) body = json.loads(body) return resp, body['aggregate'] def remove_host(self, aggregate_id, host): """Removes a host from the given aggregate.""" post_body = { 'host': host, } post_body = json.dumps({'remove_host': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body) body = json.loads(body) return resp, body['aggregate'] def set_metadata(self, aggregate_id, meta): """Replaces the aggregate's existing metadata with new metadata.""" post_body = { 'metadata': meta, } post_body = json.dumps({'set_metadata': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body) body = json.loads(body) self.validate_response(schema.aggregate_set_metadata, resp, body) return resp, body['aggregate'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/servers_client.py0000664000175000017500000004572712332757070027446 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time import urllib from tempest.api_schema.compute import servers as common_schema from tempest.api_schema.compute.v2 import servers as schema from tempest.common import rest_client from tempest.common import waiters from tempest import config from tempest import exceptions CONF = config.CONF class ServersClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ServersClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def create_server(self, name, image_ref, flavor_ref, **kwargs): """ Creates an instance of a server. name (Required): The name of the server. image_ref (Required): Reference to the image used to build the server. flavor_ref (Required): The flavor used to build the server. Following optional keyword arguments are accepted: adminPass: Sets the initial root password. key_name: Key name of keypair that was created earlier. meta: A dictionary of values to be used as metadata. personality: A list of dictionaries for files to be injected into the server. security_groups: A list of security group dicts. networks: A list of network dicts with UUID and fixed_ip. user_data: User data for instance. availability_zone: Availability zone in which to launch instance. accessIPv4: The IPv4 access address for the server. accessIPv6: The IPv6 access address for the server. min_count: Count of minimum number of instances to launch. max_count: Count of maximum number of instances to launch. disk_config: Determines if user or admin controls disk configuration. return_reservation_id: Enable/Disable the return of reservation id """ post_body = { 'name': name, 'imageRef': image_ref, 'flavorRef': flavor_ref } for option in ['personality', 'adminPass', 'key_name', 'security_groups', 'networks', 'user_data', 'availability_zone', 'accessIPv4', 'accessIPv6', 'min_count', 'max_count', ('metadata', 'meta'), ('OS-DCF:diskConfig', 'disk_config'), 'return_reservation_id']: if isinstance(option, tuple): post_param = option[0] key = option[1] else: post_param = option key = option value = kwargs.get(key) if value is not None: post_body[post_param] = value post_body = {'server': post_body} if 'sched_hints' in kwargs: hints = {'os:scheduler_hints': kwargs.get('sched_hints')} post_body = dict(post_body.items() + hints.items()) post_body = json.dumps(post_body) resp, body = self.post('servers', post_body) body = json.loads(body) # NOTE(maurosr): this deals with the case of multiple server create # with return reservation id set True if 'reservation_id' in body: return resp, body self.validate_response(schema.create_server, resp, body) return resp, body['server'] def update_server(self, server_id, name=None, meta=None, accessIPv4=None, accessIPv6=None, disk_config=None): """ Updates the properties of an existing server. server_id: The id of an existing server. name: The name of the server. personality: A list of files to be injected into the server. accessIPv4: The IPv4 access address for the server. accessIPv6: The IPv6 access address for the server. """ post_body = {} if meta is not None: post_body['metadata'] = meta if name is not None: post_body['name'] = name if accessIPv4 is not None: post_body['accessIPv4'] = accessIPv4 if accessIPv6 is not None: post_body['accessIPv6'] = accessIPv6 if disk_config is not None: post_body['OS-DCF:diskConfig'] = disk_config post_body = json.dumps({'server': post_body}) resp, body = self.put("servers/%s" % str(server_id), post_body) body = json.loads(body) self.validate_response(schema.update_server, resp, body) return resp, body['server'] def get_server(self, server_id): """Returns the details of an existing server.""" resp, body = self.get("servers/%s" % str(server_id)) body = json.loads(body) return resp, body['server'] def delete_server(self, server_id): """Deletes the given server.""" resp, body = self.delete("servers/%s" % str(server_id)) self.validate_response(common_schema.delete_server, resp, body) return resp, body def list_servers(self, params=None): """Lists all servers for a user.""" url = 'servers' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body def list_servers_with_detail(self, params=None): """Lists all servers in detail for a user.""" url = 'servers/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body def wait_for_server_status(self, server_id, status, extra_timeout=0, raise_on_error=True): """Waits for a server to reach a given status.""" return waiters.wait_for_server_status(self, server_id, status, extra_timeout=extra_timeout, raise_on_error=raise_on_error) def wait_for_server_termination(self, server_id, ignore_error=False): """Waits for server to reach termination.""" start_time = int(time.time()) while True: try: resp, body = self.get_server(server_id) except exceptions.NotFound: return server_status = body['status'] if server_status == 'ERROR' and not ignore_error: raise exceptions.BuildErrorException(server_id=server_id) if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def list_addresses(self, server_id): """Lists all addresses for a server.""" resp, body = self.get("servers/%s/ips" % str(server_id)) body = json.loads(body) return resp, body['addresses'] def list_addresses_by_network(self, server_id, network_id): """Lists all addresses of a specific network type for a server.""" resp, body = self.get("servers/%s/ips/%s" % (str(server_id), network_id)) body = json.loads(body) return resp, body def action(self, server_id, action_name, response_key, schema=None, **kwargs): post_body = json.dumps({action_name: kwargs}) resp, body = self.post('servers/%s/action' % str(server_id), post_body) if response_key is not None: body = json.loads(body) # Check for Schema as 'None' because if we donot have any server # action schema implemented yet then they can pass 'None' to skip # the validation.Once all server action has their schema # implemented then, this check can be removed if every actions are # supposed to validate their response. if schema is not None: self.validate_response(schema, resp, body) body = body[response_key] return resp, body def create_backup(self, server_id, backup_type, rotation, name): """Backup a server instance.""" return self.action(server_id, "createBackup", None, backup_type=backup_type, rotation=rotation, name=name) def change_password(self, server_id, adminPass): """Changes the root password for the server.""" return self.action(server_id, 'changePassword', None, adminPass=adminPass) def get_password(self, server_id): resp, body = self.get("servers/%s/os-server-password" % str(server_id)) body = json.loads(body) self.validate_response(common_schema.get_password, resp, body) return resp, body def delete_password(self, server_id): """ Removes the encrypted server password from the metadata server Note that this does not actually change the instance server password. """ return self.delete("servers/%s/os-server-password" % str(server_id)) def reboot(self, server_id, reboot_type): """Reboots a server.""" return self.action(server_id, 'reboot', None, type=reboot_type) def rebuild(self, server_id, image_ref, **kwargs): """Rebuilds a server with a new image.""" kwargs['imageRef'] = image_ref if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] return self.action(server_id, 'rebuild', 'server', **kwargs) def resize(self, server_id, flavor_ref, **kwargs): """Changes the flavor of a server.""" kwargs['flavorRef'] = flavor_ref if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] return self.action(server_id, 'resize', None, **kwargs) def confirm_resize(self, server_id, **kwargs): """Confirms the flavor change for a server.""" return self.action(server_id, 'confirmResize', None, **kwargs) def revert_resize(self, server_id, **kwargs): """Reverts a server back to its original flavor.""" return self.action(server_id, 'revertResize', None, **kwargs) def list_server_metadata(self, server_id): resp, body = self.get("servers/%s/metadata" % str(server_id)) body = json.loads(body) self.validate_response(common_schema.list_server_metadata, resp, body) return resp, body['metadata'] def set_server_metadata(self, server_id, meta, no_metadata_field=False): if no_metadata_field: post_body = "" else: post_body = json.dumps({'metadata': meta}) resp, body = self.put('servers/%s/metadata' % str(server_id), post_body) body = json.loads(body) self.validate_response(common_schema.set_server_metadata, resp, body) return resp, body['metadata'] def update_server_metadata(self, server_id, meta): post_body = json.dumps({'metadata': meta}) resp, body = self.post('servers/%s/metadata' % str(server_id), post_body) body = json.loads(body) return resp, body['metadata'] def get_server_metadata_item(self, server_id, key): resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key)) body = json.loads(body) self.validate_response(schema.set_get_server_metadata_item, resp, body) return resp, body['meta'] def set_server_metadata_item(self, server_id, key, meta): post_body = json.dumps({'meta': meta}) resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key), post_body) body = json.loads(body) self.validate_response(schema.set_get_server_metadata_item, resp, body) return resp, body['meta'] def delete_server_metadata_item(self, server_id, key): resp, body = self.delete("servers/%s/metadata/%s" % (str(server_id), key)) self.validate_response(common_schema.delete_server_metadata_item, resp, body) return resp, body def stop(self, server_id, **kwargs): return self.action(server_id, 'os-stop', None, **kwargs) def start(self, server_id, **kwargs): return self.action(server_id, 'os-start', None, **kwargs) def attach_volume(self, server_id, volume_id, device='/dev/vdz'): """Attaches a volume to a server instance.""" post_body = json.dumps({ 'volumeAttachment': { 'volumeId': volume_id, 'device': device, } }) resp, body = self.post('servers/%s/os-volume_attachments' % server_id, post_body) body = json.loads(body) self.validate_response(schema.attach_volume, resp, body) return resp, body def detach_volume(self, server_id, volume_id): """Detaches a volume from a server instance.""" resp, body = self.delete('servers/%s/os-volume_attachments/%s' % (server_id, volume_id)) self.validate_response(schema.detach_volume, resp, body) return resp, body def add_security_group(self, server_id, name): """Adds a security group to the server.""" return self.action(server_id, 'addSecurityGroup', None, name=name) def remove_security_group(self, server_id, name): """Removes a security group from the server.""" return self.action(server_id, 'removeSecurityGroup', None, name=name) def live_migrate_server(self, server_id, dest_host, use_block_migration): """This should be called with administrator privileges .""" migrate_params = { "disk_over_commit": False, "block_migration": use_block_migration, "host": dest_host } req_body = json.dumps({'os-migrateLive': migrate_params}) resp, body = self.post("servers/%s/action" % str(server_id), req_body) return resp, body def migrate_server(self, server_id, **kwargs): """Migrates a server to a new host.""" return self.action(server_id, 'migrate', None, **kwargs) def lock_server(self, server_id, **kwargs): """Locks the given server.""" return self.action(server_id, 'lock', None, **kwargs) def unlock_server(self, server_id, **kwargs): """UNlocks the given server.""" return self.action(server_id, 'unlock', None, **kwargs) def suspend_server(self, server_id, **kwargs): """Suspends the provided server.""" return self.action(server_id, 'suspend', None, **kwargs) def resume_server(self, server_id, **kwargs): """Un-suspends the provided server.""" return self.action(server_id, 'resume', None, **kwargs) def pause_server(self, server_id, **kwargs): """Pauses the provided server.""" return self.action(server_id, 'pause', None, **kwargs) def unpause_server(self, server_id, **kwargs): """Un-pauses the provided server.""" return self.action(server_id, 'unpause', None, **kwargs) def reset_state(self, server_id, state='error'): """Resets the state of a server to active/error.""" return self.action(server_id, 'os-resetState', None, state=state) def shelve_server(self, server_id, **kwargs): """Shelves the provided server.""" return self.action(server_id, 'shelve', None, **kwargs) def unshelve_server(self, server_id, **kwargs): """Un-shelves the provided server.""" return self.action(server_id, 'unshelve', None, **kwargs) def shelve_offload_server(self, server_id, **kwargs): """Shelve-offload the provided server.""" return self.action(server_id, 'shelveOffload', None, **kwargs) def get_console_output(self, server_id, length): return self.action(server_id, 'os-getConsoleOutput', 'output', length=length) def list_virtual_interfaces(self, server_id): """ List the virtual interfaces used in an instance. """ resp, body = self.get('/'.join(['servers', server_id, 'os-virtual-interfaces'])) body = json.loads(body) self.validate_response(schema.list_virtual_interfaces, resp, body) return resp, body def rescue_server(self, server_id, **kwargs): """Rescue the provided server.""" return self.action(server_id, 'rescue', None, **kwargs) def unrescue_server(self, server_id): """Unrescue the provided server.""" return self.action(server_id, 'unrescue', None) def get_server_diagnostics(self, server_id): """Get the usage data for a server.""" resp, body = self.get("servers/%s/diagnostics" % str(server_id)) return resp, json.loads(body) def list_instance_actions(self, server_id): """List the provided server action.""" resp, body = self.get("servers/%s/os-instance-actions" % str(server_id)) body = json.loads(body) return resp, body['instanceActions'] def get_instance_action(self, server_id, request_id): """Returns the action details of the provided server.""" resp, body = self.get("servers/%s/os-instance-actions/%s" % (str(server_id), str(request_id))) body = json.loads(body) return resp, body['instanceAction'] def force_delete_server(self, server_id, **kwargs): """Force delete a server.""" return self.action(server_id, 'forceDelete', None, **kwargs) def restore_soft_deleted_server(self, server_id, **kwargs): """Restore a soft-deleted server.""" return self.action(server_id, 'restore', None, **kwargs) def reset_network(self, server_id, **kwargs): """Resets the Network of a server""" return self.action(server_id, 'resetNetwork', None, **kwargs) def inject_network_info(self, server_id, **kwargs): """Inject the Network Info into server""" return self.action(server_id, 'injectNetworkInfo', None, **kwargs) def get_vnc_console(self, server_id, console_type): """Get URL of VNC console.""" return self.action(server_id, "os-getVNCConsole", "console", common_schema.get_vnc_console, type=console_type) tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/security_groups_client.py0000664000175000017500000001255012332757070031207 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute.v2 import security_groups as schema from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class SecurityGroupsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(SecurityGroupsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_security_groups(self, params=None): """List all security groups for a user.""" url = 'os-security-groups' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_security_groups, resp, body) return resp, body['security_groups'] def get_security_group(self, security_group_id): """Get the details of a Security Group.""" url = "os-security-groups/%s" % str(security_group_id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.get_security_group, resp, body) return resp, body['security_group'] def create_security_group(self, name, description): """ Creates a new security group. name (Required): Name of security group. description (Required): Description of security group. """ post_body = { 'name': name, 'description': description, } post_body = json.dumps({'security_group': post_body}) resp, body = self.post('os-security-groups', post_body) body = json.loads(body) self.validate_response(schema.get_security_group, resp, body) return resp, body['security_group'] def update_security_group(self, security_group_id, name=None, description=None): """ Update a security group. security_group_id: a security_group to update name: new name of security group description: new description of security group """ post_body = {} if name: post_body['name'] = name if description: post_body['description'] = description post_body = json.dumps({'security_group': post_body}) resp, body = self.put('os-security-groups/%s' % str(security_group_id), post_body) body = json.loads(body) self.validate_response(schema.update_security_group, resp, body) return resp, body['security_group'] def delete_security_group(self, security_group_id): """Deletes the provided Security Group.""" return self.delete('os-security-groups/%s' % str(security_group_id)) def create_security_group_rule(self, parent_group_id, ip_proto, from_port, to_port, **kwargs): """ Creating a new security group rules. parent_group_id :ID of Security group ip_protocol : ip_proto (icmp, tcp, udp). from_port: Port at start of range. to_port : Port at end of range. Following optional keyword arguments are accepted: cidr : CIDR for address range. group_id : ID of the Source group """ post_body = { 'parent_group_id': parent_group_id, 'ip_protocol': ip_proto, 'from_port': from_port, 'to_port': to_port, 'cidr': kwargs.get('cidr'), 'group_id': kwargs.get('group_id'), } post_body = json.dumps({'security_group_rule': post_body}) url = 'os-security-group-rules' resp, body = self.post(url, post_body) body = json.loads(body) self.validate_response(schema.create_security_group_rule, resp, body) return resp, body['security_group_rule'] def delete_security_group_rule(self, group_rule_id): """Deletes the provided Security Group rule.""" resp, body = self.delete('os-security-group-rules/%s' % str(group_rule_id)) self.validate_response(schema.delete_security_group_rule, resp, body) return resp, body def list_security_group_rules(self, security_group_id): """List all rules for a security group.""" resp, body = self.get('os-security-groups') body = json.loads(body) self.validate_response(schema.list_security_groups, resp, body) for sg in body['security_groups']: if sg['id'] == security_group_id: return resp, sg['rules'] raise exceptions.NotFound('No such Security Group') def is_resource_deleted(self, id): try: self.get_security_group(id) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/interfaces_client.py0000664000175000017500000000776212332757070030075 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import time from tempest.api_schema.compute import interfaces as common_schema from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class InterfacesClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(InterfacesClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_interfaces(self, server): resp, body = self.get('servers/%s/os-interface' % server) body = json.loads(body) return resp, body['interfaceAttachments'] def create_interface(self, server, port_id=None, network_id=None, fixed_ip=None): post_body = dict(interfaceAttachment=dict()) if port_id: post_body['interfaceAttachment']['port_id'] = port_id if network_id: post_body['interfaceAttachment']['net_id'] = network_id if fixed_ip: fip = dict(ip_address=fixed_ip) post_body['interfaceAttachment']['fixed_ips'] = [fip] post_body = json.dumps(post_body) resp, body = self.post('servers/%s/os-interface' % server, body=post_body) body = json.loads(body) return resp, body['interfaceAttachment'] def show_interface(self, server, port_id): resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id)) body = json.loads(body) return resp, body['interfaceAttachment'] def delete_interface(self, server, port_id): resp, body = self.delete('servers/%s/os-interface/%s' % (server, port_id)) self.validate_response(common_schema.delete_interface, resp, body) return resp, body def wait_for_interface_status(self, server, port_id, status): """Waits for a interface to reach a given status.""" resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] start = int(time.time()) while(interface_status != status): time.sleep(self.build_interval) resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] timed_out = int(time.time()) - start >= self.build_timeout if interface_status != status and timed_out: message = ('Interface %s failed to reach %s status within ' 'the required time (%s s).' % (port_id, status, self.build_timeout)) raise exceptions.TimeoutException(message) return resp, body def add_fixed_ip(self, server_id, network_id): """Add a fixed IP to input server instance.""" post_body = json.dumps({ 'addFixedIp': { 'networkId': network_id } }) resp, body = self.post('servers/%s/action' % str(server_id), post_body) return resp, body def remove_fixed_ip(self, server_id, ip_address): """Remove input fixed IP from input server instance.""" post_body = json.dumps({ 'removeFixedIp': { 'address': ip_address } }) resp, body = self.post('servers/%s/action' % str(server_id), post_body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/availability_zone_client.py0000664000175000017500000000310112332757070031436 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v2 import availability_zone as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class AvailabilityZoneClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(AvailabilityZoneClientJSON, self).__init__( auth_provider) self.service = CONF.compute.catalog_type def get_availability_zone_list(self): resp, body = self.get('os-availability-zone') body = json.loads(body) self.validate_response(schema.get_availability_zone_list, resp, body) return resp, body['availabilityZoneInfo'] def get_availability_zone_list_detail(self): resp, body = self.get('os-availability-zone/detail') body = json.loads(body) self.validate_response(schema.get_availability_zone_list_detail, resp, body) return resp, body['availabilityZoneInfo'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/floating_ips_client.py0000664000175000017500000001011512332757070030412 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute.v2 import floating_ips as schema from tempest.common import rest_client from tempest import config from tempest import exceptions CONF = config.CONF class FloatingIPsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(FloatingIPsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_floating_ips(self, params=None): """Returns a list of all floating IPs filtered by any parameters.""" url = 'os-floating-ips' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_floating_ips, resp, body) return resp, body['floating_ips'] def get_floating_ip_details(self, floating_ip_id): """Get the details of a floating IP.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.get(url) body = json.loads(body) if resp.status == 404: raise exceptions.NotFound(body) self.validate_response(schema.floating_ip, resp, body) return resp, body['floating_ip'] def create_floating_ip(self, pool_name=None): """Allocate a floating IP to the project.""" url = 'os-floating-ips' post_body = {'pool': pool_name} post_body = json.dumps(post_body) resp, body = self.post(url, post_body) body = json.loads(body) self.validate_response(schema.floating_ip, resp, body) return resp, body['floating_ip'] def delete_floating_ip(self, floating_ip_id): """Deletes the provided floating IP from the project.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.delete(url) self.validate_response(schema.add_remove_floating_ip, resp, body) return resp, body def associate_floating_ip_to_server(self, floating_ip, server_id): """Associate the provided floating IP to a specific server.""" url = "servers/%s/action" % str(server_id) post_body = { 'addFloatingIp': { 'address': floating_ip, } } post_body = json.dumps(post_body) resp, body = self.post(url, post_body) self.validate_response(schema.add_remove_floating_ip, resp, body) return resp, body def disassociate_floating_ip_from_server(self, floating_ip, server_id): """Disassociate the provided floating IP from a specific server.""" url = "servers/%s/action" % str(server_id) post_body = { 'removeFloatingIp': { 'address': floating_ip, } } post_body = json.dumps(post_body) resp, body = self.post(url, post_body) self.validate_response(schema.add_remove_floating_ip, resp, body) return resp, body def is_resource_deleted(self, id): try: self.get_floating_ip_details(id) except exceptions.NotFound: return True return False def list_floating_ip_pools(self, params=None): """Returns a list of all floating IP Pools.""" url = 'os-floating-ip-pools' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.floating_ip_pools, resp, body) return resp, body['floating_ip_pools'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/hypervisor_client.py0000664000175000017500000000655112332757070030157 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import hypervisors as common_schema from tempest.api_schema.compute.v2 import hypervisors as v2schema from tempest.common import rest_client from tempest import config CONF = config.CONF class HypervisorClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(HypervisorClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_hypervisor_list(self): """List hypervisors information.""" resp, body = self.get('os-hypervisors') body = json.loads(body) self.validate_response(common_schema.common_hypervisors_detail, resp, body) return resp, body['hypervisors'] def get_hypervisor_list_details(self): """Show detailed hypervisors information.""" resp, body = self.get('os-hypervisors/detail') body = json.loads(body) self.validate_response(common_schema.common_list_hypervisors_detail, resp, body) return resp, body['hypervisors'] def get_hypervisor_show_details(self, hyper_id): """Display the details of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s' % hyper_id) body = json.loads(body) self.validate_response(common_schema.common_show_hypervisor, resp, body) return resp, body['hypervisor'] def get_hypervisor_servers(self, hyper_name): """List instances belonging to the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/servers' % hyper_name) body = json.loads(body) self.validate_response(v2schema.hypervisors_servers, resp, body) return resp, body['hypervisors'] def get_hypervisor_stats(self): """Get hypervisor statistics over all compute nodes.""" resp, body = self.get('os-hypervisors/statistics') body = json.loads(body) self.validate_response(common_schema.hypervisor_statistics, resp, body) return resp, body['hypervisor_statistics'] def get_hypervisor_uptime(self, hyper_id): """Display the uptime of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id) body = json.loads(body) self.validate_response(common_schema.hypervisor_uptime, resp, body) return resp, body['hypervisor'] def search_hypervisor(self, hyper_name): """Search specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/search' % hyper_name) body = json.loads(body) self.validate_response(common_schema.common_hypervisors_detail, resp, body) return resp, body['hypervisors'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/limits_client.py0000664000175000017500000000303612332757070027241 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v2 import limits as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class LimitsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(LimitsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_absolute_limits(self): resp, body = self.get("limits") body = json.loads(body) self.validate_response(schema.get_limit, resp, body) return resp, body['limits']['absolute'] def get_specific_absolute_limit(self, absolute_limit): resp, body = self.get("limits") body = json.loads(body) self.validate_response(schema.get_limit, resp, body) if absolute_limit not in body['limits']['absolute']: return None else: return body['limits']['absolute'][absolute_limit] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/instance_usage_audit_log_client.py0000664000175000017500000000325112332757070032756 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v2 import instance_usage_audit_logs \ as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class InstanceUsagesAuditLogClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(InstanceUsagesAuditLogClientJSON, self).__init__( auth_provider) self.service = CONF.compute.catalog_type def list_instance_usage_audit_logs(self): url = 'os-instance_usage_audit_log' resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_instance_usage_audit_log, resp, body) return resp, body["instance_usage_audit_logs"] def get_instance_usage_audit_log(self, time_before): url = 'os-instance_usage_audit_log/%s' % time_before resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.get_instance_usage_audit_log, resp, body) return resp, body["instance_usage_audit_log"] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/agents_client.py0000664000175000017500000000416712332757070027227 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute import agents as common_schema from tempest.api_schema.compute.v2 import agents as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class AgentsClientJSON(rest_client.RestClient): """ Tests Agents API """ def __init__(self, auth_provider): super(AgentsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_agents(self, params=None): """List all agent builds.""" url = 'os-agents' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(common_schema.list_agents, resp, body) return resp, body['agents'] def create_agent(self, **kwargs): """Create an agent build.""" post_body = json.dumps({'agent': kwargs}) resp, body = self.post('os-agents', post_body) return resp, self._parse_resp(body) def delete_agent(self, agent_id): """Delete an existing agent build.""" resp, body = self.delete("os-agents/%s" % str(agent_id)) self.validate_response(schema.delete_agent, resp, body) return resp, body def update_agent(self, agent_id, **kwargs): """Update an agent build.""" put_body = json.dumps({'para': kwargs}) resp, body = self.put('os-agents/%s' % str(agent_id), put_body) return resp, self._parse_resp(body) tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/__init__.py0000664000175000017500000000000012332757070026125 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/keypairs_client.py0000664000175000017500000000456412332757070027576 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import keypairs as common_schema from tempest.api_schema.compute.v2 import keypairs as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class KeyPairsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(KeyPairsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_keypairs(self): resp, body = self.get("os-keypairs") body = json.loads(body) # Each returned keypair is embedded within an unnecessary 'keypair' # element which is a deviation from other resources like floating-ips, # servers, etc. A bug? # For now we shall adhere to the spec, but the spec for keypairs # is yet to be found self.validate_response(common_schema.list_keypairs, resp, body) return resp, body['keypairs'] def get_keypair(self, key_name): resp, body = self.get("os-keypairs/%s" % str(key_name)) body = json.loads(body) self.validate_response(schema.get_keypair, resp, body) return resp, body['keypair'] def create_keypair(self, name, pub_key=None): post_body = {'keypair': {'name': name}} if pub_key: post_body['keypair']['public_key'] = pub_key post_body = json.dumps(post_body) resp, body = self.post("os-keypairs", body=post_body) body = json.loads(body) self.validate_response(schema.create_keypair, resp, body) return resp, body['keypair'] def delete_keypair(self, key_name): resp, body = self.delete("os-keypairs/%s" % str(key_name)) self.validate_response(schema.delete_keypair, resp, body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/quotas_client.py0000664000175000017500000001023612332757070027254 0ustar chuckchuck00000000000000# Copyright 2012 NTT Data # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v2 import quotas as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class QuotasClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(QuotasClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_quota_set(self, tenant_id, user_id=None): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % str(tenant_id) if user_id: url += '?user_id=%s' % str(user_id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.quota_set, resp, body) return resp, body['quota_set'] def get_default_quota_set(self, tenant_id): """List the default quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % str(tenant_id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.quota_set, resp, body) return resp, body['quota_set'] def update_quota_set(self, tenant_id, user_id=None, force=None, injected_file_content_bytes=None, metadata_items=None, ram=None, floating_ips=None, fixed_ips=None, key_pairs=None, instances=None, security_group_rules=None, injected_files=None, cores=None, injected_file_path_bytes=None, security_groups=None): """ Updates the tenant's quota limits for one or more resources """ post_body = {} if force is not None: post_body['force'] = force if injected_file_content_bytes is not None: post_body['injected_file_content_bytes'] = \ injected_file_content_bytes if metadata_items is not None: post_body['metadata_items'] = metadata_items if ram is not None: post_body['ram'] = ram if floating_ips is not None: post_body['floating_ips'] = floating_ips if fixed_ips is not None: post_body['fixed_ips'] = fixed_ips if key_pairs is not None: post_body['key_pairs'] = key_pairs if instances is not None: post_body['instances'] = instances if security_group_rules is not None: post_body['security_group_rules'] = security_group_rules if injected_files is not None: post_body['injected_files'] = injected_files if cores is not None: post_body['cores'] = cores if injected_file_path_bytes is not None: post_body['injected_file_path_bytes'] = injected_file_path_bytes if security_groups is not None: post_body['security_groups'] = security_groups post_body = json.dumps({'quota_set': post_body}) if user_id: resp, body = self.put('os-quota-sets/%s?user_id=%s' % (str(tenant_id), str(user_id)), post_body) else: resp, body = self.put('os-quota-sets/%s' % str(tenant_id), post_body) body = json.loads(body) self.validate_response(schema.quota_set_update, resp, body) return resp, body['quota_set'] def delete_quota_set(self, tenant_id): """Delete the tenant's quota set.""" resp, body = self.delete('os-quota-sets/%s' % str(tenant_id)) self.validate_response(schema.delete_quota, resp, body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/tenant_usages_client.py0000664000175000017500000000326012332757070030577 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute.v2 import tenant_usages as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class TenantUsagesClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(TenantUsagesClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_tenant_usages(self, params=None): url = 'os-simple-tenant-usage' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_tenant, resp, body) return resp, body['tenant_usages'][0] def get_tenant_usage(self, tenant_id, params=None): url = 'os-simple-tenant-usage/%s' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.get_tenant, resp, body) return resp, body['tenant_usage'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/extensions_client.py0000664000175000017500000000311412332757070030134 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v2 import extensions as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class ExtensionsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ExtensionsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_extensions, resp, body) return resp, body['extensions'] def is_enabled(self, extension): _, extensions = self.list_extensions() exts = extensions['extensions'] return any([e for e in exts if e['name'] == extension]) def get_extension(self, extension_alias): resp, body = self.get('extensions/%s' % extension_alias) body = json.loads(body) return resp, body['extension'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/services_client.py0000664000175000017500000000416012332757070027562 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute import services as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class ServicesClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ServicesClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_services(self, params=None): url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_services, resp, body) return resp, body['services'] def enable_service(self, host_name, binary): """ Enable service on a host host_name: Name of host binary: Service binary """ post_body = json.dumps({'binary': binary, 'host': host_name}) resp, body = self.put('os-services/enable', post_body) body = json.loads(body) self.validate_response(schema.enable_service, resp, body) return resp, body['service'] def disable_service(self, host_name, binary): """ Disable service on a host host_name: Name of host binary: Service binary """ post_body = json.dumps({'binary': binary, 'host': host_name}) resp, body = self.put('os-services/disable', post_body) body = json.loads(body) return resp, body['service'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/fixed_ips_client.py0000664000175000017500000000300212332757070027703 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute.v2 import fixed_ips as schema from tempest.common import rest_client from tempest import config CONF = config.CONF class FixedIPsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(FixedIPsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_fixed_ip_details(self, fixed_ip): url = "os-fixed-ips/%s" % (fixed_ip) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.fixed_ips, resp, body) return resp, body['fixed_ip'] def reserve_fixed_ip(self, ip, body): """This reserves and unreserves fixed ips.""" url = "os-fixed-ips/%s/action" % (ip) resp, body = self.post(url, json.dumps(body)) self.validate_response(schema.fixed_ip_action, resp, body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/images_client.py0000664000175000017500000001260712332757070027211 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute.v2 import images as schema from tempest.common import rest_client from tempest.common import waiters from tempest import config from tempest import exceptions CONF = config.CONF class ImagesClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(ImagesClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout def create_image(self, server_id, name, meta=None): """Creates an image of the original server.""" post_body = { 'createImage': { 'name': name, } } if meta is not None: post_body['createImage']['metadata'] = meta post_body = json.dumps(post_body) resp, body = self.post('servers/%s/action' % str(server_id), post_body) self.validate_response(schema.create_image, resp, body) return resp, body def list_images(self, params=None): """Returns a list of all images filtered by any parameters.""" url = 'images' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_images, resp, body) return resp, body['images'] def list_images_with_detail(self, params=None): """Returns a detailed list of images filtered by any parameters.""" url = 'images/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.list_images_details, resp, body) return resp, body['images'] def get_image(self, image_id): """Returns the details of a single image.""" resp, body = self.get("images/%s" % str(image_id)) self.expected_success(200, resp) body = json.loads(body) self.validate_response(schema.get_image, resp, body) return resp, body['image'] def delete_image(self, image_id): """Deletes the provided image.""" resp, body = self.delete("images/%s" % str(image_id)) self.validate_response(schema.delete, resp, body) return resp, body def wait_for_image_status(self, image_id, status): """Waits for an image to reach a given status.""" waiters.wait_for_image_status(self, image_id, status) def list_image_metadata(self, image_id): """Lists all metadata items for an image.""" resp, body = self.get("images/%s/metadata" % str(image_id)) body = json.loads(body) self.validate_response(schema.image_metadata, resp, body) return resp, body['metadata'] def set_image_metadata(self, image_id, meta): """Sets the metadata for an image.""" post_body = json.dumps({'metadata': meta}) resp, body = self.put('images/%s/metadata' % str(image_id), post_body) body = json.loads(body) self.validate_response(schema.image_metadata, resp, body) return resp, body['metadata'] def update_image_metadata(self, image_id, meta): """Updates the metadata for an image.""" post_body = json.dumps({'metadata': meta}) resp, body = self.post('images/%s/metadata' % str(image_id), post_body) body = json.loads(body) self.validate_response(schema.image_metadata, resp, body) return resp, body['metadata'] def get_image_metadata_item(self, image_id, key): """Returns the value for a specific image metadata key.""" resp, body = self.get("images/%s/metadata/%s" % (str(image_id), key)) body = json.loads(body) self.validate_response(schema.image_meta_item, resp, body) return resp, body['meta'] def set_image_metadata_item(self, image_id, key, meta): """Sets the value for a specific image metadata key.""" post_body = json.dumps({'meta': meta}) resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key), post_body) body = json.loads(body) self.validate_response(schema.image_meta_item, resp, body) return resp, body['meta'] def delete_image_metadata_item(self, image_id, key): """Deletes a single image metadata key/value pair.""" resp, body = self.delete("images/%s/metadata/%s" % (str(image_id), key)) self.validate_response(schema.delete, resp, body) return resp, body def is_resource_deleted(self, id): try: self.get_image(id) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/certificates_client.py0000664000175000017500000000310712332757070030404 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from tempest.api_schema.compute import certificates as schema from tempest.api_schema.compute.v2 import certificates as v2schema from tempest.common import rest_client from tempest import config CONF = config.CONF class CertificatesClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(CertificatesClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_certificate(self, id): url = "os-certificates/%s" % (id) resp, body = self.get(url) body = json.loads(body) self.validate_response(schema.get_certificate, resp, body) return resp, body['certificate'] def create_certificate(self): """create certificates.""" url = "os-certificates" resp, body = self.post(url, None) body = json.loads(body) self.validate_response(v2schema.create_certificate, resp, body) return resp, body['certificate'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/json/flavors_client.py0000664000175000017500000001613712332757070027422 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import urllib from tempest.api_schema.compute import flavors as common_schema from tempest.api_schema.compute import flavors_access as schema_access from tempest.api_schema.compute import flavors_extra_specs \ as schema_extra_specs from tempest.api_schema.compute.v2 import flavors as v2schema from tempest.common import rest_client from tempest import config CONF = config.CONF class FlavorsClientJSON(rest_client.RestClient): def __init__(self, auth_provider): super(FlavorsClientJSON, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_flavors(self, params=None): url = 'flavors' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(common_schema.list_flavors, resp, body) return resp, body['flavors'] def list_flavors_with_detail(self, params=None): url = 'flavors/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self.validate_response(v2schema.list_flavors_details, resp, body) return resp, body['flavors'] def get_flavor_details(self, flavor_id): resp, body = self.get("flavors/%s" % str(flavor_id)) body = json.loads(body) self.validate_response(v2schema.create_get_flavor_details, resp, body) return resp, body['flavor'] def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs): """Creates a new flavor or instance type.""" post_body = { 'name': name, 'ram': ram, 'vcpus': vcpus, 'disk': disk, 'id': flavor_id, } if kwargs.get('ephemeral'): post_body['OS-FLV-EXT-DATA:ephemeral'] = kwargs.get('ephemeral') if kwargs.get('swap'): post_body['swap'] = kwargs.get('swap') if kwargs.get('rxtx'): post_body['rxtx_factor'] = kwargs.get('rxtx') if kwargs.get('is_public'): post_body['os-flavor-access:is_public'] = kwargs.get('is_public') post_body = json.dumps({'flavor': post_body}) resp, body = self.post('flavors', post_body) body = json.loads(body) self.validate_response(v2schema.create_get_flavor_details, resp, body) return resp, body['flavor'] def delete_flavor(self, flavor_id): """Deletes the given flavor.""" resp, body = self.delete("flavors/{0}".format(flavor_id)) self.validate_response(v2schema.delete_flavor, resp, body) return resp, body def is_resource_deleted(self, id): # Did not use get_flavor_details(id) for verification as it gives # 200 ok even for deleted id. LP #981263 # we can remove the loop here and use get by ID when bug gets sortedout resp, flavors = self.list_flavors_with_detail() for flavor in flavors: if flavor['id'] == id: return False return True def set_flavor_extra_spec(self, flavor_id, specs): """Sets extra Specs to the mentioned flavor.""" post_body = json.dumps({'extra_specs': specs}) resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id, post_body) body = json.loads(body) self.validate_response(schema_extra_specs.flavor_extra_specs, resp, body) return resp, body['extra_specs'] def get_flavor_extra_spec(self, flavor_id): """Gets extra Specs details of the mentioned flavor.""" resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id) body = json.loads(body) self.validate_response(schema_extra_specs.flavor_extra_specs, resp, body) return resp, body['extra_specs'] def get_flavor_extra_spec_with_key(self, flavor_id, key): """Gets extra Specs key-value of the mentioned flavor and key.""" resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id), key)) body = json.loads(body) self.validate_response(schema_extra_specs.flavor_extra_specs_key, resp, body) return resp, body def update_flavor_extra_spec(self, flavor_id, key, **kwargs): """Update specified extra Specs of the mentioned flavor and key.""" resp, body = self.put('flavors/%s/os-extra_specs/%s' % (flavor_id, key), json.dumps(kwargs)) body = json.loads(body) self.validate_response(schema_extra_specs.flavor_extra_specs_key, resp, body) return resp, body def unset_flavor_extra_spec(self, flavor_id, key): """Unsets extra Specs from the mentioned flavor.""" resp, body = self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id), key)) self.validate_response(v2schema.unset_flavor_extra_specs, resp, body) return resp, body def list_flavor_access(self, flavor_id): """Gets flavor access information given the flavor id.""" resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id) body = json.loads(body) self.validate_response(schema_access.add_remove_list_flavor_access, resp, body) return resp, body['flavor_access'] def add_flavor_access(self, flavor_id, tenant_id): """Add flavor access for the specified tenant.""" post_body = { 'addTenantAccess': { 'tenant': tenant_id } } post_body = json.dumps(post_body) resp, body = self.post('flavors/%s/action' % flavor_id, post_body) body = json.loads(body) self.validate_response(schema_access.add_remove_list_flavor_access, resp, body) return resp, body['flavor_access'] def remove_flavor_access(self, flavor_id, tenant_id): """Remove flavor access from the specified tenant.""" post_body = { 'removeTenantAccess': { 'tenant': tenant_id } } post_body = json.dumps(post_body) resp, body = self.post('flavors/%s/action' % flavor_id, post_body) body = json.loads(body) self.validate_response(schema_access.add_remove_list_flavor_access, resp, body) return resp, body['flavor_access'] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/__init__.py0000664000175000017500000000000012332757070025154 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/0000775000175000017500000000000012332757136023660 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/hosts_client.py0000664000175000017500000000554612332757070026737 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class HostsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(HostsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_hosts(self, params=None): """Lists all hosts.""" url = 'os-hosts' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) node = etree.fromstring(body) body = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, body def show_host_detail(self, hostname): """Show detail information for the host.""" resp, body = self.get("os-hosts/%s" % str(hostname)) node = etree.fromstring(body) body = [xml_utils.xml_to_json(node)] return resp, body def update_host(self, hostname, **kwargs): """Update a host.""" request_body = xml_utils.Element("updates") if kwargs: for k, v in kwargs.iteritems(): request_body.append(xml_utils.Element(k, v)) resp, body = self.put("os-hosts/%s" % str(hostname), str(xml_utils.Document(request_body))) node = etree.fromstring(body) body = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, body def startup_host(self, hostname): """Startup a host.""" resp, body = self.get("os-hosts/%s/startup" % str(hostname)) node = etree.fromstring(body) body = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, body def shutdown_host(self, hostname): """Shutdown a host.""" resp, body = self.get("os-hosts/%s/shutdown" % str(hostname)) node = etree.fromstring(body) body = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, body def reboot_host(self, hostname): """Reboot a host.""" resp, body = self.get("os-hosts/%s/reboot" % str(hostname)) node = etree.fromstring(body) body = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/volumes_extensions_client.py0000664000175000017500000001173312332757070031543 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config from tempest import exceptions CONF = config.CONF class VolumesExtensionsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(VolumesExtensionsClientXML, self).__init__( auth_provider) self.service = CONF.compute.catalog_type self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout def _parse_volume(self, body): vol = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) if tag == 'metadata': vol['metadata'] = dict((meta.get('key'), meta.text) for meta in list(child)) else: vol[tag] = xml_utils.xml_to_json(child) return vol def list_volumes(self, params=None): """List all the volumes created.""" url = 'os-volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] return resp, volumes def list_volumes_with_detail(self, params=None): """List all the details of volumes.""" url = 'os-volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] return resp, volumes def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "os-volumes/%s" % str(volume_id) resp, body = self.get(url) body = etree.fromstring(body) return resp, self._parse_volume(body) def create_volume(self, size, display_name=None, metadata=None): """Creates a new Volume. :param size: Size of volume in GB. (Required) :param display_name: Optional Volume Name. :param metadata: An optional dictionary of values for metadata. """ volume = xml_utils.Element("volume", xmlns=xml_utils.XMLNS_11, size=size) if display_name: volume.add_attr('display_name', display_name) if metadata: _metadata = xml_utils.Element('metadata') volume.append(_metadata) for key, value in metadata.items(): meta = xml_utils.Element('meta') meta.add_attr('key', key) meta.append(xml_utils.Text(value)) _metadata.append(meta) resp, body = self.post('os-volumes', str(xml_utils.Document(volume))) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("os-volumes/%s" % str(volume_id)) def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_name = body['displayName'] volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = 'Volume %s failed to reach %s status within '\ 'the required time (%s s).' % (volume_name, status, self.build_timeout) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/aggregates_client.py0000664000175000017500000001170312332757070027700 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config from tempest import exceptions CONF = config.CONF class AggregatesClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(AggregatesClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _format_aggregate(self, g): agg = xml_utils.xml_to_json(g) aggregate = {} for key, value in agg.items(): if key == 'hosts': aggregate['hosts'] = [] for k, v in value.items(): aggregate['hosts'].append(v) elif key == 'availability_zone': aggregate[key] = None if value == 'None' else value else: aggregate[key] = value return aggregate def _parse_array(self, node): return [self._format_aggregate(x) for x in node] def list_aggregates(self): """Get aggregate list.""" resp, body = self.get("os-aggregates") aggregates = self._parse_array(etree.fromstring(body)) return resp, aggregates def get_aggregate(self, aggregate_id): """Get details of the given aggregate.""" resp, body = self.get("os-aggregates/%s" % str(aggregate_id)) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def create_aggregate(self, name, availability_zone=None): """Creates a new aggregate.""" if availability_zone is not None: post_body = xml_utils.Element("aggregate", name=name, availability_zone=availability_zone) else: post_body = xml_utils.Element("aggregate", name=name) resp, body = self.post('os-aggregates', str(xml_utils.Document(post_body))) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def update_aggregate(self, aggregate_id, name, availability_zone=None): """Update a aggregate.""" if availability_zone is not None: put_body = xml_utils.Element("aggregate", name=name, availability_zone=availability_zone) else: put_body = xml_utils.Element("aggregate", name=name) resp, body = self.put('os-aggregates/%s' % str(aggregate_id), str(xml_utils.Document(put_body))) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def delete_aggregate(self, aggregate_id): """Deletes the given aggregate.""" return self.delete("os-aggregates/%s" % str(aggregate_id)) def is_resource_deleted(self, id): try: self.get_aggregate(id) except exceptions.NotFound: return True return False def add_host(self, aggregate_id, host): """Adds a host to the given aggregate.""" post_body = xml_utils.Element("add_host", host=host) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, str(xml_utils.Document(post_body))) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def remove_host(self, aggregate_id, host): """Removes a host from the given aggregate.""" post_body = xml_utils.Element("remove_host", host=host) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, str(xml_utils.Document(post_body))) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def set_metadata(self, aggregate_id, meta): """Replaces the aggregate's existing metadata with new metadata.""" post_body = xml_utils.Element("set_metadata") metadata = xml_utils.Element("metadata") post_body.append(metadata) for k, v in meta.items(): meta = xml_utils.Element(k) meta.append(xml_utils.Text(v)) metadata.append(meta) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, str(xml_utils.Document(post_body))) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/servers_client.py0000664000175000017500000006600012332757070027260 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time import urllib from lxml import etree from tempest.common import rest_client from tempest.common import waiters from tempest.common import xml_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) def _translate_ip_xml_json(ip): """ Convert the address version to int. """ ip = dict(ip) version = ip.get('version') if version: ip['version'] = int(version) # NOTE(maurosr): just a fast way to avoid the xml version with the # expanded xml namespace. type_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips/' 'api/v1.1}type') mac_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips_mac' '/api/v1.1}mac_addr') if type_ns_prefix in ip: ip['OS-EXT-IPS:type'] = ip.pop(type_ns_prefix) if mac_ns_prefix in ip: ip['OS-EXT-IPS-MAC:mac_addr'] = ip.pop(mac_ns_prefix) return ip def _translate_network_xml_to_json(network): return [_translate_ip_xml_json(ip.attrib) for ip in network.findall('{%s}ip' % xml_utils.XMLNS_11)] def _translate_addresses_xml_to_json(xml_addresses): return dict((network.attrib['id'], _translate_network_xml_to_json(network)) for network in xml_addresses.findall('{%s}network' % xml_utils.XMLNS_11)) def _translate_server_xml_to_json(xml_dom): """Convert server XML to server JSON. The addresses collection does not convert well by the dumb xml_to_json. This method does some pre and post-processing to deal with that. Translate XML addresses subtree to JSON. Having xml_doc similar to the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce something like {'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4}, {'addr': '2001:0:0:1:2:3:4:5', 'version': 6}], 'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}} """ nsmap = {'api': xml_utils.XMLNS_11} addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap) if addresses: if len(addresses) > 1: raise ValueError('Expected only single `addresses` element.') json_addresses = _translate_addresses_xml_to_json(addresses[0]) json = xml_utils.xml_to_json(xml_dom) json['addresses'] = json_addresses else: json = xml_utils.xml_to_json(xml_dom) diskConfig = ('{http://docs.openstack.org' '/compute/ext/disk_config/api/v1.1}diskConfig') terminated_at = ('{http://docs.openstack.org/' 'compute/ext/server_usage/api/v1.1}terminated_at') launched_at = ('{http://docs.openstack.org' '/compute/ext/server_usage/api/v1.1}launched_at') power_state = ('{http://docs.openstack.org' '/compute/ext/extended_status/api/v1.1}power_state') availability_zone = ('{http://docs.openstack.org' '/compute/ext/extended_availability_zone/api/v2}' 'availability_zone') vm_state = ('{http://docs.openstack.org' '/compute/ext/extended_status/api/v1.1}vm_state') task_state = ('{http://docs.openstack.org' '/compute/ext/extended_status/api/v1.1}task_state') if 'tenantId' in json: json['tenant_id'] = json.pop('tenantId') if 'userId' in json: json['user_id'] = json.pop('userId') if diskConfig in json: json['OS-DCF:diskConfig'] = json.pop(diskConfig) if terminated_at in json: json['OS-SRV-USG:terminated_at'] = json.pop(terminated_at) if launched_at in json: json['OS-SRV-USG:launched_at'] = json.pop(launched_at) if power_state in json: json['OS-EXT-STS:power_state'] = json.pop(power_state) if availability_zone in json: json['OS-EXT-AZ:availability_zone'] = json.pop(availability_zone) if vm_state in json: json['OS-EXT-STS:vm_state'] = json.pop(vm_state) if task_state in json: json['OS-EXT-STS:task_state'] = json.pop(task_state) return json class ServersClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(ServersClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _parse_key_value(self, node): """Parse value data into {'key': 'value'}.""" data = {} for node in node.getchildren(): data[node.get('key')] = node.text return data def _parse_links(self, node, json): del json['link'] json['links'] = [] for linknode in node.findall('{http://www.w3.org/2005/Atom}link'): json['links'].append(xml_utils.xml_to_json(linknode)) def _parse_server(self, body): json = _translate_server_xml_to_json(body) if 'metadata' in json and json['metadata']: # NOTE(danms): if there was metadata, we need to re-parse # that as a special type metadata_tag = body.find('{%s}metadata' % xml_utils.XMLNS_11) json["metadata"] = self._parse_key_value(metadata_tag) if 'link' in json: self._parse_links(body, json) for sub in ['image', 'flavor']: if sub in json and 'link' in json[sub]: self._parse_links(body, json[sub]) return json def _parse_xml_virtual_interfaces(self, xml_dom): """ Return server's virtual interfaces XML as JSON. """ data = {"virtual_interfaces": []} for iface in xml_dom.getchildren(): data["virtual_interfaces"].append( {"id": iface.get("id"), "mac_address": iface.get("mac_address")}) return data def get_server(self, server_id): """Returns the details of an existing server.""" resp, body = self.get("servers/%s" % str(server_id)) server = self._parse_server(etree.fromstring(body)) return resp, server def migrate_server(self, server_id, **kwargs): """Migrates the given server .""" return self.action(server_id, 'migrate', None, **kwargs) def lock_server(self, server_id, **kwargs): """Locks the given server.""" return self.action(server_id, 'lock', None, **kwargs) def unlock_server(self, server_id, **kwargs): """Unlocks the given server.""" return self.action(server_id, 'unlock', None, **kwargs) def suspend_server(self, server_id, **kwargs): """Suspends the provided server.""" return self.action(server_id, 'suspend', None, **kwargs) def resume_server(self, server_id, **kwargs): """Un-suspends the provided server.""" return self.action(server_id, 'resume', None, **kwargs) def pause_server(self, server_id, **kwargs): """Pauses the provided server.""" return self.action(server_id, 'pause', None, **kwargs) def unpause_server(self, server_id, **kwargs): """Un-pauses the provided server.""" return self.action(server_id, 'unpause', None, **kwargs) def shelve_server(self, server_id, **kwargs): """Shelves the provided server.""" return self.action(server_id, 'shelve', None, **kwargs) def unshelve_server(self, server_id, **kwargs): """Un-shelves the provided server.""" return self.action(server_id, 'unshelve', None, **kwargs) def shelve_offload_server(self, server_id, **kwargs): """Shelve-offload the provided server.""" return self.action(server_id, 'shelveOffload', None, **kwargs) def reset_state(self, server_id, state='error'): """Resets the state of a server to active/error.""" return self.action(server_id, 'os-resetState', None, state=state) def delete_server(self, server_id): """Deletes the given server.""" return self.delete("servers/%s" % str(server_id)) def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_utils.xml_to_json(child)) return array def _parse_server_array(self, node): array = [] for child in node.getchildren(): array.append(self._parse_server(child)) return array def list_servers(self, params=None): url = 'servers' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) servers = self._parse_server_array(etree.fromstring(body)) return resp, {"servers": servers} def list_servers_with_detail(self, params=None): url = 'servers/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) servers = self._parse_server_array(etree.fromstring(body)) return resp, {"servers": servers} def update_server(self, server_id, name=None, meta=None, accessIPv4=None, accessIPv6=None, disk_config=None): doc = xml_utils.Document() server = xml_utils.Element("server") doc.append(server) if name is not None: server.add_attr("name", name) if accessIPv4 is not None: server.add_attr("accessIPv4", accessIPv4) if accessIPv6 is not None: server.add_attr("accessIPv6", accessIPv6) if disk_config is not None: server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/" "compute/ext/disk_config/api/v1.1") server.add_attr("OS-DCF:diskConfig", disk_config) if meta is not None: metadata = xml_utils.Element("metadata") server.append(metadata) for k, v in meta: meta = xml_utils.Element("meta", key=k) meta.append(xml_utils.Text(v)) metadata.append(meta) resp, body = self.put('servers/%s' % str(server_id), str(doc)) return resp, xml_utils.xml_to_json(etree.fromstring(body)) def create_server(self, name, image_ref, flavor_ref, **kwargs): """ Creates an instance of a server. name (Required): The name of the server. image_ref (Required): Reference to the image used to build the server. flavor_ref (Required): The flavor used to build the server. Following optional keyword arguments are accepted: adminPass: Sets the initial root password. key_name: Key name of keypair that was created earlier. meta: A dictionary of values to be used as metadata. personality: A list of dictionaries for files to be injected into the server. security_groups: A list of security group dicts. networks: A list of network dicts with UUID and fixed_ip. user_data: User data for instance. availability_zone: Availability zone in which to launch instance. accessIPv4: The IPv4 access address for the server. accessIPv6: The IPv6 access address for the server. min_count: Count of minimum number of instances to launch. max_count: Count of maximum number of instances to launch. disk_config: Determines if user or admin controls disk configuration. """ server = xml_utils.Element("server", xmlns=xml_utils.XMLNS_11, imageRef=image_ref, flavorRef=flavor_ref, name=name) for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name", "user_data", "availability_zone", "min_count", "max_count", "return_reservation_id"]: if attr in kwargs: server.add_attr(attr, kwargs[attr]) if 'disk_config' in kwargs: server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/" "compute/ext/disk_config/api/v1.1") server.add_attr('OS-DCF:diskConfig', kwargs['disk_config']) if 'security_groups' in kwargs: secgroups = xml_utils.Element("security_groups") server.append(secgroups) for secgroup in kwargs['security_groups']: s = xml_utils.Element("security_group", name=secgroup['name']) secgroups.append(s) if 'networks' in kwargs: networks = xml_utils.Element("networks") server.append(networks) for network in kwargs['networks']: s = xml_utils.Element("network", uuid=network['uuid'], fixed_ip=network['fixed_ip']) networks.append(s) if 'meta' in kwargs: metadata = xml_utils.Element("metadata") server.append(metadata) for k, v in kwargs['meta'].items(): meta = xml_utils.Element("meta", key=k) meta.append(xml_utils.Text(v)) metadata.append(meta) if 'personality' in kwargs: personality = xml_utils.Element('personality') server.append(personality) for k in kwargs['personality']: temp = xml_utils.Element('file', path=k['path']) temp.append(xml_utils.Text(k['contents'])) personality.append(temp) if 'sched_hints' in kwargs: sched_hints = kwargs.get('sched_hints') hints = xml_utils.Element("os:scheduler_hints") hints.add_attr('xmlns:os', xml_utils.XMLNS_11) for attr in sched_hints: p1 = xml_utils.Element(attr) p1.append(sched_hints[attr]) hints.append(p1) server.append(hints) resp, body = self.post('servers', str(xml_utils.Document(server))) server = self._parse_server(etree.fromstring(body)) return resp, server def wait_for_server_status(self, server_id, status, extra_timeout=0, raise_on_error=True): """Waits for a server to reach a given status.""" return waiters.wait_for_server_status(self, server_id, status, extra_timeout=extra_timeout, raise_on_error=raise_on_error) def wait_for_server_termination(self, server_id, ignore_error=False): """Waits for server to reach termination.""" start_time = int(time.time()) while True: try: resp, body = self.get_server(server_id) except exceptions.NotFound: return server_status = body['status'] if server_status == 'ERROR' and not ignore_error: raise exceptions.BuildErrorException(server_id=server_id) if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def _parse_network(self, node): addrs = [] for child in node.getchildren(): addrs.append({'version': int(child.get('version')), 'addr': child.get('addr')}) return {node.get('id'): addrs} def list_addresses(self, server_id): """Lists all addresses for a server.""" resp, body = self.get("servers/%s/ips" % str(server_id)) networks = {} xml_list = etree.fromstring(body) for child in xml_list.getchildren(): network = self._parse_network(child) networks.update(**network) return resp, networks def list_addresses_by_network(self, server_id, network_id): """Lists all addresses of a specific network type for a server.""" resp, body = self.get("servers/%s/ips/%s" % (str(server_id), network_id)) network = self._parse_network(etree.fromstring(body)) return resp, network def action(self, server_id, action_name, response_key, **kwargs): if 'xmlns' not in kwargs: kwargs['xmlns'] = xml_utils.XMLNS_11 doc = xml_utils.Document((xml_utils.Element(action_name, **kwargs))) resp, body = self.post("servers/%s/action" % server_id, str(doc)) if response_key is not None: body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def create_backup(self, server_id, backup_type, rotation, name): """Backup a server instance.""" return self.action(server_id, "createBackup", None, backup_type=backup_type, rotation=rotation, name=name) def change_password(self, server_id, password): return self.action(server_id, "changePassword", None, adminPass=password) def get_password(self, server_id): resp, body = self.get("servers/%s/os-server-password" % str(server_id)) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def delete_password(self, server_id): """ Removes the encrypted server password from the metadata server Note that this does not actually change the instance server password. """ return self.delete("servers/%s/os-server-password" % str(server_id)) def reboot(self, server_id, reboot_type): return self.action(server_id, "reboot", None, type=reboot_type) def rebuild(self, server_id, image_ref, **kwargs): kwargs['imageRef'] = image_ref if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\ "compute/ext/disk_config/api/v1.1" kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom" if 'xmlns' not in kwargs: kwargs['xmlns'] = xml_utils.XMLNS_11 attrs = kwargs.copy() if 'metadata' in attrs: del attrs['metadata'] rebuild = xml_utils.Element("rebuild", **attrs) if 'metadata' in kwargs: metadata = xml_utils.Element("metadata") rebuild.append(metadata) for k, v in kwargs['metadata'].items(): meta = xml_utils.Element("meta", key=k) meta.append(xml_utils.Text(v)) metadata.append(meta) resp, body = self.post('servers/%s/action' % server_id, str(xml_utils.Document(rebuild))) server = self._parse_server(etree.fromstring(body)) return resp, server def resize(self, server_id, flavor_ref, **kwargs): if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\ "compute/ext/disk_config/api/v1.1" kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom" kwargs['flavorRef'] = flavor_ref return self.action(server_id, 'resize', None, **kwargs) def confirm_resize(self, server_id, **kwargs): return self.action(server_id, 'confirmResize', None, **kwargs) def revert_resize(self, server_id, **kwargs): return self.action(server_id, 'revertResize', None, **kwargs) def stop(self, server_id, **kwargs): return self.action(server_id, 'os-stop', None, **kwargs) def start(self, server_id, **kwargs): return self.action(server_id, 'os-start', None, **kwargs) def create_image(self, server_id, name): return self.action(server_id, 'createImage', None, name=name) def add_security_group(self, server_id, name): return self.action(server_id, 'addSecurityGroup', None, name=name) def remove_security_group(self, server_id, name): return self.action(server_id, 'removeSecurityGroup', None, name=name) def live_migrate_server(self, server_id, dest_host, use_block_migration): """This should be called with administrator privileges .""" req_body = xml_utils.Element("os-migrateLive", xmlns=xml_utils.XMLNS_11, disk_over_commit=False, block_migration=use_block_migration, host=dest_host) resp, body = self.post("servers/%s/action" % str(server_id), str(xml_utils.Document(req_body))) return resp, body def list_server_metadata(self, server_id): resp, body = self.get("servers/%s/metadata" % str(server_id)) body = self._parse_key_value(etree.fromstring(body)) return resp, body def set_server_metadata(self, server_id, meta, no_metadata_field=False): doc = xml_utils.Document() if not no_metadata_field: metadata = xml_utils.Element("metadata") doc.append(metadata) for k, v in meta.items(): meta_element = xml_utils.Element("meta", key=k) meta_element.append(xml_utils.Text(v)) metadata.append(meta_element) resp, body = self.put('servers/%s/metadata' % str(server_id), str(doc)) return resp, xml_utils.xml_to_json(etree.fromstring(body)) def update_server_metadata(self, server_id, meta): doc = xml_utils.Document() metadata = xml_utils.Element("metadata") doc.append(metadata) for k, v in meta.items(): meta_element = xml_utils.Element("meta", key=k) meta_element.append(xml_utils.Text(v)) metadata.append(meta_element) resp, body = self.post("/servers/%s/metadata" % str(server_id), str(doc)) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def get_server_metadata_item(self, server_id, key): resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key)) return resp, dict([(etree.fromstring(body).attrib['key'], xml_utils.xml_to_json(etree.fromstring(body)))]) def set_server_metadata_item(self, server_id, key, meta): doc = xml_utils.Document() for k, v in meta.items(): meta_element = xml_utils.Element("meta", key=k) meta_element.append(xml_utils.Text(v)) doc.append(meta_element) resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key), str(doc)) return resp, xml_utils.xml_to_json(etree.fromstring(body)) def delete_server_metadata_item(self, server_id, key): resp, body = self.delete("servers/%s/metadata/%s" % (str(server_id), key)) return resp, body def get_console_output(self, server_id, length): return self.action(server_id, 'os-getConsoleOutput', 'output', length=length) def list_virtual_interfaces(self, server_id): """ List the virtual interfaces used in an instance. """ resp, body = self.get('/'.join(['servers', server_id, 'os-virtual-interfaces'])) virt_int = self._parse_xml_virtual_interfaces(etree.fromstring(body)) return resp, virt_int def rescue_server(self, server_id, **kwargs): """Rescue the provided server.""" return self.action(server_id, 'rescue', None, **kwargs) def unrescue_server(self, server_id): """Unrescue the provided server.""" return self.action(server_id, 'unrescue', None) def attach_volume(self, server_id, volume_id, device='/dev/vdz'): post_body = xml_utils.Element("volumeAttachment", volumeId=volume_id, device=device) resp, body = self.post('servers/%s/os-volume_attachments' % server_id, str(xml_utils.Document(post_body))) return resp, body def detach_volume(self, server_id, volume_id): headers = {'Content-Type': 'application/xml', 'Accept': 'application/xml'} resp, body = self.delete('servers/%s/os-volume_attachments/%s' % (server_id, volume_id), headers) return resp, body def get_server_diagnostics(self, server_id): """Get the usage data for a server.""" resp, body = self.get("servers/%s/diagnostics" % server_id) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def list_instance_actions(self, server_id): """List the provided server action.""" resp, body = self.get("servers/%s/os-instance-actions" % server_id) body = self._parse_array(etree.fromstring(body)) return resp, body def get_instance_action(self, server_id, request_id): """Returns the action details of the provided server.""" resp, body = self.get("servers/%s/os-instance-actions/%s" % (server_id, request_id)) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def force_delete_server(self, server_id, **kwargs): """Force delete a server.""" return self.action(server_id, 'forceDelete', None, **kwargs) def restore_soft_deleted_server(self, server_id, **kwargs): """Restore a soft-deleted server.""" return self.action(server_id, 'restore', None, **kwargs) def reset_network(self, server_id, **kwargs): """Resets the Network of a server""" return self.action(server_id, 'resetNetwork', None, **kwargs) def inject_network_info(self, server_id, **kwargs): """Inject the Network Info into server""" return self.action(server_id, 'injectNetworkInfo', None, **kwargs) def get_vnc_console(self, server_id, console_type): """Get URL of VNC console.""" return self.action(server_id, "os-getVNCConsole", "console", type=console_type) tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/security_groups_client.py0000664000175000017500000001363712332757070031045 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree import urllib from tempest.common import rest_client from tempest.common import xml_utils from tempest import config from tempest import exceptions CONF = config.CONF class SecurityGroupsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(SecurityGroupsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_utils.xml_to_json(child)) return array def _parse_body(self, body): json = xml_utils.xml_to_json(body) return json def list_security_groups(self, params=None): """List all security groups for a user.""" url = 'os-security-groups' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = self._parse_array(etree.fromstring(body)) return resp, body def get_security_group(self, security_group_id): """Get the details of a Security Group.""" url = "os-security-groups/%s" % str(security_group_id) resp, body = self.get(url) body = self._parse_body(etree.fromstring(body)) return resp, body def create_security_group(self, name, description): """ Creates a new security group. name (Required): Name of security group. description (Required): Description of security group. """ security_group = xml_utils.Element("security_group", name=name) des = xml_utils.Element("description") des.append(xml_utils.Text(content=description)) security_group.append(des) resp, body = self.post('os-security-groups', str(xml_utils.Document(security_group))) body = self._parse_body(etree.fromstring(body)) return resp, body def update_security_group(self, security_group_id, name=None, description=None): """ Update a security group. security_group_id: a security_group to update name: new name of security group description: new description of security group """ security_group = xml_utils.Element("security_group") if name: sg_name = xml_utils.Element("name") sg_name.append(xml_utils.Text(content=name)) security_group.append(sg_name) if description: des = xml_utils.Element("description") des.append(xml_utils.Text(content=description)) security_group.append(des) resp, body = self.put('os-security-groups/%s' % str(security_group_id), str(xml_utils.Document(security_group))) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_security_group(self, security_group_id): """Deletes the provided Security Group.""" return self.delete('os-security-groups/%s' % str(security_group_id)) def create_security_group_rule(self, parent_group_id, ip_proto, from_port, to_port, **kwargs): """ Creating a new security group rules. parent_group_id :ID of Security group ip_protocol : ip_proto (icmp, tcp, udp). from_port: Port at start of range. to_port : Port at end of range. Following optional keyword arguments are accepted: cidr : CIDR for address range. group_id : ID of the Source group """ group_rule = xml_utils.Element("security_group_rule") elements = dict() elements['cidr'] = kwargs.get('cidr') elements['group_id'] = kwargs.get('group_id') elements['parent_group_id'] = parent_group_id elements['ip_protocol'] = ip_proto elements['from_port'] = from_port elements['to_port'] = to_port for k, v in elements.items(): if v is not None: element = xml_utils.Element(k) element.append(xml_utils.Text(content=str(v))) group_rule.append(element) url = 'os-security-group-rules' resp, body = self.post(url, str(xml_utils.Document(group_rule))) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_security_group_rule(self, group_rule_id): """Deletes the provided Security Group rule.""" return self.delete('os-security-group-rules/%s' % str(group_rule_id)) def list_security_group_rules(self, security_group_id): """List all rules for a security group.""" url = "os-security-groups" resp, body = self.get(url) body = etree.fromstring(body) secgroups = body.getchildren() for secgroup in secgroups: if secgroup.get('id') == security_group_id: node = secgroup.find('{%s}rules' % xml_utils.XMLNS_11) rules = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, rules raise exceptions.NotFound('No such Security Group') def is_resource_deleted(self, id): try: self.get_security_group(id) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/interfaces_client.py0000664000175000017500000001164112332757070027713 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config from tempest import exceptions CONF = config.CONF class InterfacesClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(InterfacesClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _process_xml_interface(self, node): iface = xml_utils.xml_to_json(node) # NOTE(danms): if multiple addresses per interface is ever required, # xml_utils.xml_to_json will need to be fixed or replaced in this case iface['fixed_ips'] = [dict(iface['fixed_ips']['fixed_ip'].items())] return iface def list_interfaces(self, server): resp, body = self.get('servers/%s/os-interface' % server) node = etree.fromstring(body) interfaces = [self._process_xml_interface(x) for x in node.getchildren()] return resp, interfaces def create_interface(self, server, port_id=None, network_id=None, fixed_ip=None): doc = xml_utils.Document() iface = xml_utils.Element('interfaceAttachment') if port_id: _port_id = xml_utils.Element('port_id') _port_id.append(xml_utils.Text(port_id)) iface.append(_port_id) if network_id: _network_id = xml_utils.Element('net_id') _network_id.append(xml_utils.Text(network_id)) iface.append(_network_id) if fixed_ip: _fixed_ips = xml_utils.Element('fixed_ips') _fixed_ip = xml_utils.Element('fixed_ip') _ip_address = xml_utils.Element('ip_address') _ip_address.append(xml_utils.Text(fixed_ip)) _fixed_ip.append(_ip_address) _fixed_ips.append(_fixed_ip) iface.append(_fixed_ips) doc.append(iface) resp, body = self.post('servers/%s/os-interface' % server, body=str(doc)) body = self._process_xml_interface(etree.fromstring(body)) return resp, body def show_interface(self, server, port_id): resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id)) body = self._process_xml_interface(etree.fromstring(body)) return resp, body def delete_interface(self, server, port_id): resp, body = self.delete('servers/%s/os-interface/%s' % (server, port_id)) return resp, body def wait_for_interface_status(self, server, port_id, status): """Waits for a interface to reach a given status.""" resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] start = int(time.time()) while(interface_status != status): time.sleep(self.build_interval) resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] timed_out = int(time.time()) - start >= self.build_timeout if interface_status != status and timed_out: message = ('Interface %s failed to reach %s status within ' 'the required time (%s s).' % (port_id, status, self.build_timeout)) raise exceptions.TimeoutException(message) return resp, body def add_fixed_ip(self, server_id, network_id): """Add a fixed IP to input server instance.""" post_body = xml_utils.Element("addFixedIp", xmlns=xml_utils.XMLNS_11, networkId=network_id) resp, body = self.post('servers/%s/action' % str(server_id), str(xml_utils.Document(post_body))) return resp, body def remove_fixed_ip(self, server_id, ip_address): """Remove input fixed IP from input server instance.""" post_body = xml_utils.Element("removeFixedIp", xmlns=xml_utils.XMLNS_11, address=ip_address) resp, body = self.post('servers/%s/action' % str(server_id), str(xml_utils.Document(post_body))) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/availability_zone_client.py0000664000175000017500000000301012332757070031264 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class AvailabilityZoneClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(AvailabilityZoneClientXML, self).__init__( auth_provider) self.service = CONF.compute.catalog_type def _parse_array(self, node): return [xml_utils.xml_to_json(x) for x in node] def get_availability_zone_list(self): resp, body = self.get('os-availability-zone') availability_zone = self._parse_array(etree.fromstring(body)) return resp, availability_zone def get_availability_zone_list_detail(self): resp, body = self.get('os-availability-zone/detail') availability_zone = self._parse_array(etree.fromstring(body)) return resp, availability_zone tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/floating_ips_client.py0000664000175000017500000001010412332757070030237 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree import urllib from tempest.common import rest_client from tempest.common import xml_utils from tempest import config from tempest import exceptions CONF = config.CONF class FloatingIPsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(FloatingIPsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_utils.xml_to_json(child)) return array def _parse_floating_ip(self, body): json = xml_utils.xml_to_json(body) return json def list_floating_ips(self, params=None): """Returns a list of all floating IPs filtered by any parameters.""" url = 'os-floating-ips' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = self._parse_array(etree.fromstring(body)) return resp, body def get_floating_ip_details(self, floating_ip_id): """Get the details of a floating IP.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.get(url) body = self._parse_floating_ip(etree.fromstring(body)) if resp.status == 404: raise exceptions.NotFound(body) return resp, body def create_floating_ip(self, pool_name=None): """Allocate a floating IP to the project.""" url = 'os-floating-ips' if pool_name: doc = xml_utils.Document() pool = xml_utils.Element("pool") pool.append(xml_utils.Text(pool_name)) doc.append(pool) resp, body = self.post(url, str(doc)) else: resp, body = self.post(url, None) body = self._parse_floating_ip(etree.fromstring(body)) return resp, body def delete_floating_ip(self, floating_ip_id): """Deletes the provided floating IP from the project.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.delete(url) return resp, body def associate_floating_ip_to_server(self, floating_ip, server_id): """Associate the provided floating IP to a specific server.""" url = "servers/%s/action" % str(server_id) doc = xml_utils.Document() server = xml_utils.Element("addFloatingIp") doc.append(server) server.add_attr("address", floating_ip) resp, body = self.post(url, str(doc)) return resp, body def disassociate_floating_ip_from_server(self, floating_ip, server_id): """Disassociate the provided floating IP from a specific server.""" url = "servers/%s/action" % str(server_id) doc = xml_utils.Document() server = xml_utils.Element("removeFloatingIp") doc.append(server) server.add_attr("address", floating_ip) resp, body = self.post(url, str(doc)) return resp, body def is_resource_deleted(self, id): try: self.get_floating_ip_details(id) except exceptions.NotFound: return True return False def list_floating_ip_pools(self, params=None): """Returns a list of all floating IP Pools.""" url = 'os-floating-ip-pools' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = self._parse_array(etree.fromstring(body)) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/hypervisor_client.py0000664000175000017500000000553512332757070030007 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class HypervisorClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(HypervisorClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _parse_array(self, node): return [xml_utils.xml_to_json(x) for x in node] def get_hypervisor_list(self): """List hypervisors information.""" resp, body = self.get('os-hypervisors') hypervisors = self._parse_array(etree.fromstring(body)) return resp, hypervisors def get_hypervisor_list_details(self): """Show detailed hypervisors information.""" resp, body = self.get('os-hypervisors/detail') hypervisors = self._parse_array(etree.fromstring(body)) return resp, hypervisors def get_hypervisor_show_details(self, hyper_id): """Display the details of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s' % hyper_id) hypervisor = xml_utils.xml_to_json(etree.fromstring(body)) return resp, hypervisor def get_hypervisor_servers(self, hyper_name): """List instances belonging to the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/servers' % hyper_name) hypervisors = self._parse_array(etree.fromstring(body)) return resp, hypervisors def get_hypervisor_stats(self): """Get hypervisor statistics over all compute nodes.""" resp, body = self.get('os-hypervisors/statistics') stats = xml_utils.xml_to_json(etree.fromstring(body)) return resp, stats def get_hypervisor_uptime(self, hyper_id): """Display the uptime of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id) uptime = xml_utils.xml_to_json(etree.fromstring(body)) return resp, uptime def search_hypervisor(self, hyper_name): """Search specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/search' % hyper_name) hypervisors = self._parse_array(etree.fromstring(body)) return resp, hypervisors tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/limits_client.py0000664000175000017500000000336112332757070027071 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import objectify from tempest.common import rest_client from tempest import config CONF = config.CONF NS = "{http://docs.openstack.org/common/api/v1.0}" class LimitsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(LimitsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_absolute_limits(self): resp, body = self.get("limits") body = objectify.fromstring(body) lim = NS + 'absolute' ret = {} for el in body[lim].iterchildren(): attributes = el.attrib ret[attributes['name']] = attributes['value'] return resp, ret def get_specific_absolute_limit(self, absolute_limit): resp, body = self.get("limits") body = objectify.fromstring(body) lim = NS + 'absolute' ret = {} for el in body[lim].iterchildren(): attributes = el.attrib ret[attributes['name']] = attributes['value'] if absolute_limit not in ret: return None else: return ret[absolute_limit] tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/instance_usage_audit_log_client.py0000664000175000017500000000310112332757070032577 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class InstanceUsagesAuditLogClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(InstanceUsagesAuditLogClientXML, self).__init__( auth_provider) self.service = CONF.compute.catalog_type def list_instance_usage_audit_logs(self): url = 'os-instance_usage_audit_log' resp, body = self.get(url) instance_usage_audit_logs = xml_utils.xml_to_json( etree.fromstring(body)) return resp, instance_usage_audit_logs def get_instance_usage_audit_log(self, time_before): url = 'os-instance_usage_audit_log/%s' % time_before resp, body = self.get(url) instance_usage_audit_log = xml_utils.xml_to_json( etree.fromstring(body)) return resp, instance_usage_audit_log tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/__init__.py0000664000175000017500000000000012332757070025754 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/keypairs_client.py0000664000175000017500000000434312332757070027420 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class KeyPairsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(KeyPairsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_keypairs(self): resp, body = self.get("os-keypairs") node = etree.fromstring(body) body = [{'keypair': xml_utils.xml_to_json(x)} for x in node.getchildren()] return resp, body def get_keypair(self, key_name): resp, body = self.get("os-keypairs/%s" % str(key_name)) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def create_keypair(self, name, pub_key=None): doc = xml_utils.Document() keypair_element = xml_utils.Element("keypair") if pub_key: public_key_element = xml_utils.Element("public_key") public_key_text = xml_utils.Text(pub_key) public_key_element.append(public_key_text) keypair_element.append(public_key_element) name_element = xml_utils.Element("name") name_text = xml_utils.Text(name) name_element.append(name_text) keypair_element.append(name_element) doc.append(keypair_element) resp, body = self.post("os-keypairs", body=str(doc)) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def delete_keypair(self, key_name): return self.delete("os-keypairs/%s" % str(key_name)) tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/quotas_client.py0000664000175000017500000001103112332757070027075 0ustar chuckchuck00000000000000# Copyright 2012 NTT Data # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class QuotasClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(QuotasClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _format_quota(self, q): quota = {} for k, v in q.items(): try: v = int(v) except ValueError: pass quota[k] = v return quota def get_quota_set(self, tenant_id, user_id=None): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % str(tenant_id) if user_id: url += '?user_id=%s' % str(user_id) resp, body = self.get(url) body = xml_utils.xml_to_json(etree.fromstring(body)) body = self._format_quota(body) return resp, body def get_default_quota_set(self, tenant_id): """List the default quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % str(tenant_id) resp, body = self.get(url) body = xml_utils.xml_to_json(etree.fromstring(body)) body = self._format_quota(body) return resp, body def update_quota_set(self, tenant_id, user_id=None, force=None, injected_file_content_bytes=None, metadata_items=None, ram=None, floating_ips=None, fixed_ips=None, key_pairs=None, instances=None, security_group_rules=None, injected_files=None, cores=None, injected_file_path_bytes=None, security_groups=None): """ Updates the tenant's quota limits for one or more resources """ post_body = xml_utils.Element("quota_set", xmlns=xml_utils.XMLNS_11) if force is not None: post_body.add_attr('force', force) if injected_file_content_bytes is not None: post_body.add_attr('injected_file_content_bytes', injected_file_content_bytes) if metadata_items is not None: post_body.add_attr('metadata_items', metadata_items) if ram is not None: post_body.add_attr('ram', ram) if floating_ips is not None: post_body.add_attr('floating_ips', floating_ips) if fixed_ips is not None: post_body.add_attr('fixed_ips', fixed_ips) if key_pairs is not None: post_body.add_attr('key_pairs', key_pairs) if instances is not None: post_body.add_attr('instances', instances) if security_group_rules is not None: post_body.add_attr('security_group_rules', security_group_rules) if injected_files is not None: post_body.add_attr('injected_files', injected_files) if cores is not None: post_body.add_attr('cores', cores) if injected_file_path_bytes is not None: post_body.add_attr('injected_file_path_bytes', injected_file_path_bytes) if security_groups is not None: post_body.add_attr('security_groups', security_groups) if user_id: resp, body = self.put('os-quota-sets/%s?user_id=%s' % (str(tenant_id), str(user_id)), str(xml_utils.Document(post_body))) else: resp, body = self.put('os-quota-sets/%s' % str(tenant_id), str(xml_utils.Document(post_body))) body = xml_utils.xml_to_json(etree.fromstring(body)) body = self._format_quota(body) return resp, body def delete_quota_set(self, tenant_id): """Delete the tenant's quota set.""" return self.delete('os-quota-sets/%s' % str(tenant_id)) tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/tenant_usages_client.py0000664000175000017500000000332112332757070030424 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class TenantUsagesClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(TenantUsagesClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _parse_array(self, node): json = xml_utils.xml_to_json(node) return json def list_tenant_usages(self, params=None): url = 'os-simple-tenant-usage' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) tenant_usage = self._parse_array(etree.fromstring(body)) return resp, tenant_usage['tenant_usage'] def get_tenant_usage(self, tenant_id, params=None): url = 'os-simple-tenant-usage/%s' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) tenant_usage = self._parse_array(etree.fromstring(body)) return resp, tenant_usage tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/extensions_client.py0000664000175000017500000000330112332757070027761 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class ExtensionsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(ExtensionsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _parse_array(self, node): array = [] for child in node: array.append(xml_utils.xml_to_json(child)) return array def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = self._parse_array(etree.fromstring(body)) return resp, body def is_enabled(self, extension): _, extensions = self.list_extensions() exts = extensions['extensions'] return any([e for e in exts if e['name'] == extension]) def get_extension(self, extension_alias): resp, body = self.get('extensions/%s' % extension_alias) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/services_client.py0000664000175000017500000000446112332757070027415 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class ServicesClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(ServicesClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def list_services(self, params=None): url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) node = etree.fromstring(body) body = [xml_utils.xml_to_json(x) for x in node.getchildren()] return resp, body def enable_service(self, host_name, binary): """ Enable service on a host host_name: Name of host binary: Service binary """ post_body = xml_utils.Element("service") post_body.add_attr('binary', binary) post_body.add_attr('host', host_name) resp, body = self.put('os-services/enable', str( xml_utils.Document(post_body))) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def disable_service(self, host_name, binary): """ Disable service on a host host_name: Name of host binary: Service binary """ post_body = xml_utils.Element("service") post_body.add_attr('binary', binary) post_body.add_attr('host', host_name) resp, body = self.put('os-services/disable', str( xml_utils.Document(post_body))) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/fixed_ips_client.py0000664000175000017500000000327512332757070027546 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF class FixedIPsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(FixedIPsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_fixed_ip_details(self, fixed_ip): url = "os-fixed-ips/%s" % (fixed_ip) resp, body = self.get(url) body = self._parse_resp(body) return resp, body def reserve_fixed_ip(self, ip, body): """This reserves and unreserves fixed ips.""" url = "os-fixed-ips/%s/action" % (ip) # NOTE(maurosr): First converts the dict body to a json string then # accept any action key value here to permit tests to cover cases with # invalid actions raising badrequest. key, value = body.popitem() xml_body = xml_utils.Element(key) xml_body.append(xml_utils.Text(value)) resp, body = self.post(url, str(xml_utils.Document(xml_body))) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/images_client.py0000664000175000017500000001711112332757070027033 0ustar chuckchuck00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from lxml import etree from tempest.common import rest_client from tempest.common import waiters from tempest.common import xml_utils from tempest import config from tempest import exceptions CONF = config.CONF class ImagesClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(ImagesClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type self.build_interval = CONF.compute.build_interval self.build_timeout = CONF.compute.build_timeout def _parse_server(self, node): data = xml_utils.xml_to_json(node) return self._parse_links(node, data) def _parse_image(self, node): """Parses detailed XML image information into dictionary.""" data = xml_utils.xml_to_json(node) self._parse_links(node, data) # parse all metadata if 'metadata' in data: tag = node.find('{%s}metadata' % xml_utils.XMLNS_11) data['metadata'] = dict((x.get('key'), x.text) for x in tag.getchildren()) # parse server information if 'server' in data: tag = node.find('{%s}server' % xml_utils.XMLNS_11) data['server'] = self._parse_server(tag) return data def _parse_links(self, node, data): """Append multiple links under a list.""" # look for links if 'link' in data: # remove single link element del data['link'] data['links'] = [xml_utils.xml_to_json(x) for x in node.findall('{http://www.w3.org/2005/Atom}link')] return data def _parse_images(self, xml): data = {'images': []} images = xml.getchildren() for image in images: data['images'].append(self._parse_image(image)) return data def _parse_key_value(self, node): """Parse value data into {'key': 'value'}.""" data = {} for node in node.getchildren(): data[node.get('key')] = node.text return data def _parse_metadata(self, node): """Parse the response body without children.""" data = {} data[node.get('key')] = node.text return data def create_image(self, server_id, name, meta=None): """Creates an image of the original server.""" post_body = xml_utils.Element('createImage', name=name) if meta: metadata = xml_utils.Element('metadata') post_body.append(metadata) for k, v in meta.items(): data = xml_utils.Element('meta', key=k) data.append(xml_utils.Text(v)) metadata.append(data) resp, body = self.post('servers/%s/action' % str(server_id), str(xml_utils.Document(post_body))) return resp, body def list_images(self, params=None): """Returns a list of all images filtered by any parameters.""" url = 'images' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = self._parse_images(etree.fromstring(body)) return resp, body['images'] def list_images_with_detail(self, params=None): """Returns a detailed list of images filtered by any parameters.""" url = 'images/detail' if params: param_list = urllib.urlencode(params) url = "images/detail?" + param_list resp, body = self.get(url) body = self._parse_images(etree.fromstring(body)) return resp, body['images'] def get_image(self, image_id): """Returns the details of a single image.""" resp, body = self.get("images/%s" % str(image_id)) self.expected_success(200, resp) body = self._parse_image(etree.fromstring(body)) return resp, body def delete_image(self, image_id): """Deletes the provided image.""" return self.delete("images/%s" % str(image_id)) def wait_for_image_status(self, image_id, status): """Waits for an image to reach a given status.""" waiters.wait_for_image_status(self, image_id, status) def _metadata_body(self, meta): post_body = xml_utils.Element('metadata') for k, v in meta.items(): data = xml_utils.Element('meta', key=k) data.append(xml_utils.Text(v)) post_body.append(data) return post_body def list_image_metadata(self, image_id): """Lists all metadata items for an image.""" resp, body = self.get("images/%s/metadata" % str(image_id)) body = self._parse_key_value(etree.fromstring(body)) return resp, body def set_image_metadata(self, image_id, meta): """Sets the metadata for an image.""" post_body = self._metadata_body(meta) resp, body = self.put('images/%s/metadata' % image_id, str(xml_utils.Document(post_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_image_metadata(self, image_id, meta): """Updates the metadata for an image.""" post_body = self._metadata_body(meta) resp, body = self.post('images/%s/metadata' % str(image_id), str(xml_utils.Document(post_body))) body = self._parse_key_value(etree.fromstring(body)) return resp, body def get_image_metadata_item(self, image_id, key): """Returns the value for a specific image metadata key.""" resp, body = self.get("images/%s/metadata/%s.xml" % (str(image_id), key)) body = self._parse_metadata(etree.fromstring(body)) return resp, body def set_image_metadata_item(self, image_id, key, meta): """Sets the value for a specific image metadata key.""" for k, v in meta.items(): post_body = xml_utils.Element('meta', key=key) post_body.append(xml_utils.Text(v)) resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key), str(xml_utils.Document(post_body))) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def update_image_metadata_item(self, image_id, key, meta): """Sets the value for a specific image metadata key.""" post_body = xml_utils.Document('meta', xml_utils.Text(meta), key=key) resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key), post_body) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body['meta'] def delete_image_metadata_item(self, image_id, key): """Deletes a single image metadata key/value pair.""" return self.delete("images/%s/metadata/%s" % (str(image_id), key)) def is_resource_deleted(self, id): try: self.get_image(id) except exceptions.NotFound: return True return False tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/certificates_client.py0000664000175000017500000000245412332757070030237 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.common import rest_client from tempest import config CONF = config.CONF class CertificatesClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(CertificatesClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def get_certificate(self, id): url = "os-certificates/%s" % (id) resp, body = self.get(url) body = self._parse_resp(body) return resp, body def create_certificate(self): """create certificates.""" url = "os-certificates" resp, body = self.post(url, None) body = self._parse_resp(body) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/compute/xml/flavors_client.py0000664000175000017500000001762212332757070027251 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import urllib from lxml import etree from tempest.common import rest_client from tempest.common import xml_utils from tempest import config CONF = config.CONF XMLNS_OS_FLV_EXT_DATA = \ "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1" XMLNS_OS_FLV_ACCESS = \ "http://docs.openstack.org/compute/ext/flavor_access/api/v2" class FlavorsClientXML(rest_client.RestClient): TYPE = "xml" def __init__(self, auth_provider): super(FlavorsClientXML, self).__init__(auth_provider) self.service = CONF.compute.catalog_type def _format_flavor(self, f): flavor = {'links': []} for k, v in f.items(): if k == 'id': flavor['id'] = v continue if k == 'link': flavor['links'].append(v) continue if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA: k = 'OS-FLV-EXT-DATA:ephemeral' if k == '{%s}is_public' % XMLNS_OS_FLV_ACCESS: k = 'os-flavor-access:is_public' v = True if v == 'True' else False if k == 'extra_specs': k = 'OS-FLV-WITH-EXT-SPECS:extra_specs' flavor[k] = dict(v) continue try: v = int(v) except ValueError: try: v = float(v) except ValueError: pass flavor[k] = v return flavor def _parse_array(self, node): return [self._format_flavor(xml_utils.xml_to_json(x)) for x in node] def _list_flavors(self, url, params): if params: url += "?%s" % urllib.urlencode(params) resp, body = self.get(url) flavors = self._parse_array(etree.fromstring(body)) return resp, flavors def list_flavors(self, params=None): url = 'flavors' return self._list_flavors(url, params) def list_flavors_with_detail(self, params=None): url = 'flavors/detail' return self._list_flavors(url, params) def get_flavor_details(self, flavor_id): resp, body = self.get("flavors/%s" % str(flavor_id)) body = xml_utils.xml_to_json(etree.fromstring(body)) flavor = self._format_flavor(body) return resp, flavor def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs): """Creates a new flavor or instance type.""" flavor = xml_utils.Element("flavor", xmlns=xml_utils.XMLNS_11, ram=ram, vcpus=vcpus, disk=disk, id=flavor_id, name=name) if kwargs.get('rxtx'): flavor.add_attr('rxtx_factor', kwargs.get('rxtx')) if kwargs.get('swap'): flavor.add_attr('swap', kwargs.get('swap')) if kwargs.get('ephemeral'): flavor.add_attr('OS-FLV-EXT-DATA:ephemeral', kwargs.get('ephemeral')) if kwargs.get('is_public'): flavor.add_attr('os-flavor-access:is_public', kwargs.get('is_public')) flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA) flavor.add_attr('xmlns:os-flavor-access', XMLNS_OS_FLV_ACCESS) resp, body = self.post('flavors', str(xml_utils.Document(flavor))) body = xml_utils.xml_to_json(etree.fromstring(body)) flavor = self._format_flavor(body) return resp, flavor def delete_flavor(self, flavor_id): """Deletes the given flavor.""" return self.delete("flavors/%s" % str(flavor_id)) def is_resource_deleted(self, id): # Did not use get_flavor_details(id) for verification as it gives # 200 ok even for deleted id. LP #981263 # we can remove the loop here and use get by ID when bug gets sortedout resp, flavors = self.list_flavors_with_detail() for flavor in flavors: if flavor['id'] == id: return False return True def set_flavor_extra_spec(self, flavor_id, specs): """Sets extra Specs to the mentioned flavor.""" extra_specs = xml_utils.Element("extra_specs") for key in specs.keys(): extra_specs.add_attr(key, specs[key]) resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id, str(xml_utils.Document(extra_specs))) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def get_flavor_extra_spec(self, flavor_id): """Gets extra Specs of the mentioned flavor.""" resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, body def get_flavor_extra_spec_with_key(self, flavor_id, key): """Gets extra Specs key-value of the mentioned flavor and key.""" resp, xml_body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id), key)) body = {} element = etree.fromstring(xml_body) key = element.get('key') body[key] = xml_utils.xml_to_json(element) return resp, body def update_flavor_extra_spec(self, flavor_id, key, **kwargs): """Update extra Specs details of the mentioned flavor and key.""" doc = xml_utils.Document() for (k, v) in kwargs.items(): element = xml_utils.Element(k) doc.append(element) value = xml_utils.Text(v) element.append(value) resp, body = self.put('flavors/%s/os-extra_specs/%s' % (flavor_id, key), str(doc)) body = xml_utils.xml_to_json(etree.fromstring(body)) return resp, {key: body} def unset_flavor_extra_spec(self, flavor_id, key): """Unsets an extra spec based on the mentioned flavor and key.""" return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id), key)) def _parse_array_access(self, node): return [xml_utils.xml_to_json(x) for x in node] def list_flavor_access(self, flavor_id): """Gets flavor access information given the flavor id.""" resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id)) body = self._parse_array(etree.fromstring(body)) return resp, body def add_flavor_access(self, flavor_id, tenant_id): """Add flavor access for the specified tenant.""" doc = xml_utils.Document() server = xml_utils.Element("addTenantAccess") doc.append(server) server.add_attr("tenant", tenant_id) resp, body = self.post('flavors/%s/action' % str(flavor_id), str(doc)) body = self._parse_array_access(etree.fromstring(body)) return resp, body def remove_flavor_access(self, flavor_id, tenant_id): """Remove flavor access from the specified tenant.""" doc = xml_utils.Document() server = xml_utils.Element("removeTenantAccess") doc.append(server) server.add_attr("tenant", tenant_id) resp, body = self.post('flavors/%s/action' % str(flavor_id), str(doc)) body = self._parse_array_access(etree.fromstring(body)) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/services/botoclients.py0000664000175000017500000002174112332757070024305 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ConfigParser import contextlib import types import urlparse from tempest import config from tempest import exceptions import boto import boto.ec2 import boto.s3.connection CONF = config.CONF class BotoClientBase(object): ALLOWED_METHODS = set() def __init__(self, username=None, password=None, auth_url=None, tenant_name=None, *args, **kwargs): # FIXME(andreaf) replace credentials and auth_url with auth_provider self.connection_timeout = str(CONF.boto.http_socket_timeout) self.num_retries = str(CONF.boto.num_retries) self.build_timeout = CONF.boto.build_timeout self.ks_cred = {"username": username, "password": password, "auth_url": auth_url, "tenant_name": tenant_name} def _keystone_aws_get(self): # FIXME(andreaf) Move EC2 credentials to AuthProvider import keystoneclient.v2_0.client keystone = keystoneclient.v2_0.client.Client(**self.ks_cred) ec2_cred_list = keystone.ec2.list(keystone.auth_user_id) ec2_cred = None for cred in ec2_cred_list: if cred.tenant_id == keystone.auth_tenant_id: ec2_cred = cred break else: ec2_cred = keystone.ec2.create(keystone.auth_user_id, keystone.auth_tenant_id) if not all((ec2_cred, ec2_cred.access, ec2_cred.secret)): raise exceptions.NotFound("Unable to get access and secret keys") return ec2_cred def _config_boto_timeout(self, timeout, retries): try: boto.config.add_section("Boto") except ConfigParser.DuplicateSectionError: pass boto.config.set("Boto", "http_socket_timeout", timeout) boto.config.set("Boto", "num_retries", retries) def __getattr__(self, name): """Automatically creates methods for the allowed methods set.""" if name in self.ALLOWED_METHODS: def func(self, *args, **kwargs): with contextlib.closing(self.get_connection()) as conn: return getattr(conn, name)(*args, **kwargs) func.__name__ = name setattr(self, name, types.MethodType(func, self, self.__class__)) setattr(self.__class__, name, types.MethodType(func, None, self.__class__)) return getattr(self, name) else: raise AttributeError(name) def get_connection(self): self._config_boto_timeout(self.connection_timeout, self.num_retries) if not all((self.connection_data["aws_access_key_id"], self.connection_data["aws_secret_access_key"])): if all(self.ks_cred.itervalues()): ec2_cred = self._keystone_aws_get() self.connection_data["aws_access_key_id"] = \ ec2_cred.access self.connection_data["aws_secret_access_key"] = \ ec2_cred.secret else: raise exceptions.InvalidConfiguration( "Unable to get access and secret keys") return self.connect_method(**self.connection_data) class APIClientEC2(BotoClientBase): def connect_method(self, *args, **kwargs): return boto.connect_ec2(*args, **kwargs) def __init__(self, *args, **kwargs): super(APIClientEC2, self).__init__(*args, **kwargs) aws_access = CONF.boto.aws_access aws_secret = CONF.boto.aws_secret purl = urlparse.urlparse(CONF.boto.ec2_url) region_name = CONF.compute.region if not region_name: region_name = CONF.identity.region region = boto.ec2.regioninfo.RegionInfo(name=region_name, endpoint=purl.hostname) port = purl.port if port is None: if purl.scheme is not "https": port = 80 else: port = 443 else: port = int(port) self.connection_data = {"aws_access_key_id": aws_access, "aws_secret_access_key": aws_secret, "is_secure": purl.scheme == "https", "region": region, "host": purl.hostname, "port": port, "path": purl.path} ALLOWED_METHODS = set(('create_key_pair', 'get_key_pair', 'delete_key_pair', 'import_key_pair', 'get_all_key_pairs', 'get_all_tags', 'create_image', 'get_image', 'register_image', 'deregister_image', 'get_all_images', 'get_image_attribute', 'modify_image_attribute', 'reset_image_attribute', 'get_all_kernels', 'create_volume', 'delete_volume', 'get_all_volume_status', 'get_all_volumes', 'get_volume_attribute', 'modify_volume_attribute' 'bundle_instance', 'cancel_spot_instance_requests', 'confirm_product_instanc', 'get_all_instance_status', 'get_all_instances', 'get_all_reserved_instances', 'get_all_spot_instance_requests', 'get_instance_attribute', 'monitor_instance', 'monitor_instances', 'unmonitor_instance', 'unmonitor_instances', 'purchase_reserved_instance_offering', 'reboot_instances', 'request_spot_instances', 'reset_instance_attribute', 'run_instances', 'start_instances', 'stop_instances', 'terminate_instances', 'attach_network_interface', 'attach_volume', 'detach_network_interface', 'detach_volume', 'get_console_output', 'delete_network_interface', 'create_subnet', 'create_network_interface', 'delete_subnet', 'get_all_network_interfaces', 'allocate_address', 'associate_address', 'disassociate_address', 'get_all_addresses', 'release_address', 'create_snapshot', 'delete_snapshot', 'get_all_snapshots', 'get_snapshot_attribute', 'modify_snapshot_attribute', 'reset_snapshot_attribute', 'trim_snapshots', 'get_all_regions', 'get_all_zones', 'get_all_security_groups', 'create_security_group', 'delete_security_group', 'authorize_security_group', 'authorize_security_group_egress', 'revoke_security_group', 'revoke_security_group_egress')) class ObjectClientS3(BotoClientBase): def connect_method(self, *args, **kwargs): return boto.connect_s3(*args, **kwargs) def __init__(self, *args, **kwargs): super(ObjectClientS3, self).__init__(*args, **kwargs) aws_access = CONF.boto.aws_access aws_secret = CONF.boto.aws_secret purl = urlparse.urlparse(CONF.boto.s3_url) port = purl.port if port is None: if purl.scheme is not "https": port = 80 else: port = 443 else: port = int(port) self.connection_data = {"aws_access_key_id": aws_access, "aws_secret_access_key": aws_secret, "is_secure": purl.scheme == "https", "host": purl.hostname, "port": port, "calling_format": boto.s3.connection. OrdinaryCallingFormat()} ALLOWED_METHODS = set(('create_bucket', 'delete_bucket', 'generate_url', 'get_all_buckets', 'get_bucket', 'delete_key', 'lookup')) tempest-2014.1.dev4108.gf22b6cc/tempest/api/0000775000175000017500000000000012332757136020332 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/0000775000175000017500000000000012332757136023324 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_container_quotas.py0000664000175000017500000001054212332757070030312 0ustar chuckchuck00000000000000# Copyright 2013 Cloudwatt # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF QUOTA_BYTES = 10 QUOTA_COUNT = 3 class ContainerQuotasTest(base.BaseObjectTest): """Attemps to test the perfect behavior of quotas in a container.""" def setUp(self): """Creates and sets a container with quotas. Quotas are set by adding meta values to the container, and are validated when set: - X-Container-Meta-Quota-Bytes: Maximum size of the container, in bytes. - X-Container-Meta-Quota-Count: Maximum object count of the container. """ super(ContainerQuotasTest, self).setUp() self.container_name = data_utils.rand_name(name="TestContainer") self.container_client.create_container(self.container_name) metadata = {"quota-bytes": str(QUOTA_BYTES), "quota-count": str(QUOTA_COUNT), } self.container_client.update_container_metadata( self.container_name, metadata) def tearDown(self): """Cleans the container of any object after each test.""" self.delete_containers([self.container_name]) super(ContainerQuotasTest, self).tearDown() @test.requires_ext(extension='container_quotas', service='object') @test.attr(type="smoke") def test_upload_valid_object(self): """Attempts to uploads an object smaller than the bytes quota.""" object_name = data_utils.rand_name(name="TestObject") data = data_utils.arbitrary_string(QUOTA_BYTES) nbefore = self._get_bytes_used() resp, _ = self.object_client.create_object( self.container_name, object_name, data) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'PUT') nafter = self._get_bytes_used() self.assertEqual(nbefore + len(data), nafter) @test.requires_ext(extension='container_quotas', service='object') @test.attr(type="smoke") def test_upload_large_object(self): """Attempts to upload an object lagger than the bytes quota.""" object_name = data_utils.rand_name(name="TestObject") data = data_utils.arbitrary_string(QUOTA_BYTES + 1) nbefore = self._get_bytes_used() self.assertRaises(exceptions.OverLimit, self.object_client.create_object, self.container_name, object_name, data) nafter = self._get_bytes_used() self.assertEqual(nbefore, nafter) @test.requires_ext(extension='container_quotas', service='object') @test.attr(type="smoke") def test_upload_too_many_objects(self): """Attempts to upload many objects that exceeds the count limit.""" for _ in range(QUOTA_COUNT): name = data_utils.rand_name(name="TestObject") self.object_client.create_object(self.container_name, name, "") nbefore = self._get_object_count() self.assertEqual(nbefore, QUOTA_COUNT) self.assertRaises(exceptions.OverLimit, self.object_client.create_object, self.container_name, "OverQuotaObject", "") nafter = self._get_object_count() self.assertEqual(nbefore, nafter) def _get_container_metadata(self): resp, _ = self.container_client.list_container_metadata( self.container_name) return resp def _get_object_count(self): resp = self._get_container_metadata() return int(resp["x-container-object-count"]) def _get_bytes_used(self): resp = self._get_container_metadata() return int(resp["x-container-bytes-used"]) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_container_services.py0000664000175000017500000004426712332757070030634 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import test class ContainerTest(base.BaseObjectTest): def setUp(self): super(ContainerTest, self).setUp() self.containers = [] def tearDown(self): self.delete_containers(self.containers) super(ContainerTest, self).tearDown() def _create_container(self): # setup container container_name = data_utils.rand_name(name='TestContainer') self.container_client.create_container(container_name) self.containers.append(container_name) return container_name def _create_object(self, container_name, object_name=None): # setup object if object_name is None: object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() self.object_client.create_object(container_name, object_name, data) return object_name @test.attr(type='smoke') def test_create_container(self): container_name = data_utils.rand_name(name='TestContainer') resp, body = self.container_client.create_container(container_name) self.containers.append(container_name) self.assertIn(resp['status'], ('202', '201')) self.assertHeaders(resp, 'Container', 'PUT') @test.attr(type='smoke') def test_create_container_overwrite(self): # overwrite container with the same name container_name = data_utils.rand_name(name='TestContainer') self.container_client.create_container(container_name) self.containers.append(container_name) resp, _ = self.container_client.create_container(container_name) self.assertIn(resp['status'], ('202', '201')) self.assertHeaders(resp, 'Container', 'PUT') @test.attr(type='smoke') def test_create_container_with_metadata_key(self): # create container with the blank value of metadata container_name = data_utils.rand_name(name='TestContainer') metadata = {'test-container-meta': ''} resp, _ = self.container_client.create_container( container_name, metadata=metadata) self.containers.append(container_name) self.assertIn(resp['status'], ('201', '202')) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) # if the value of metadata is blank, metadata is not registered # in the server self.assertNotIn('x-container-meta-test-container-meta', resp) @test.attr(type='smoke') def test_create_container_with_metadata_value(self): # create container with metadata value container_name = data_utils.rand_name(name='TestContainer') metadata = {'test-container-meta': 'Meta1'} resp, _ = self.container_client.create_container( container_name, metadata=metadata) self.containers.append(container_name) self.assertIn(resp['status'], ('201', '202')) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) self.assertIn('x-container-meta-test-container-meta', resp) self.assertEqual(resp['x-container-meta-test-container-meta'], metadata['test-container-meta']) @test.attr(type='smoke') def test_create_container_with_remove_metadata_key(self): # create container with the blank value of remove metadata container_name = data_utils.rand_name(name='TestContainer') metadata_1 = {'test-container-meta': 'Meta1'} self.container_client.create_container( container_name, metadata=metadata_1) self.containers.append(container_name) metadata_2 = {'test-container-meta': ''} resp, _ = self.container_client.create_container( container_name, remove_metadata=metadata_2) self.assertIn(resp['status'], ('201', '202')) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta', resp) @test.attr(type='smoke') def test_create_container_with_remove_metadata_value(self): # create container with remove metadata container_name = data_utils.rand_name(name='TestContainer') metadata = {'test-container-meta': 'Meta1'} self.container_client.create_container(container_name, metadata=metadata) self.containers.append(container_name) resp, _ = self.container_client.create_container( container_name, remove_metadata=metadata) self.assertIn(resp['status'], ('201', '202')) self.assertHeaders(resp, 'Container', 'PUT') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta', resp) @test.attr(type='smoke') def test_delete_container(self): # create a container container_name = self._create_container() # delete container resp, _ = self.container_client.delete_container(container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'DELETE') self.containers.remove(container_name) @test.attr(type='smoke') def test_list_container_contents(self): # get container contents list container_name = self._create_container() object_name = self._create_object(container_name) resp, object_list = self.container_client.list_container_contents( container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual(object_name, object_list.strip('\n')) @test.attr(type='smoke') def test_list_container_contents_with_no_object(self): # get empty container contents list container_name = self._create_container() resp, object_list = self.container_client.list_container_contents( container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual('', object_list.strip('\n')) @test.attr(type='smoke') def test_list_container_contents_with_delimiter(self): # get container contents list using delimiter param container_name = self._create_container() object_name = data_utils.rand_name(name='TestObject/') self._create_object(container_name, object_name) params = {'delimiter': '/'} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual(object_name.split('/')[0], object_list.strip('/\n')) @test.attr(type='smoke') def test_list_container_contents_with_end_marker(self): # get container contents list using end_marker param container_name = self._create_container() object_name = self._create_object(container_name) params = {'end_marker': 'ZzzzObject1234567890'} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual(object_name, object_list.strip('\n')) @test.attr(type='smoke') def test_list_container_contents_with_format_json(self): # get container contents list using format_json param container_name = self._create_container() self._create_object(container_name) params = {'format': 'json'} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertIsNotNone(object_list) self.assertTrue([c['name'] for c in object_list]) self.assertTrue([c['hash'] for c in object_list]) self.assertTrue([c['bytes'] for c in object_list]) self.assertTrue([c['content_type'] for c in object_list]) self.assertTrue([c['last_modified'] for c in object_list]) @test.attr(type='smoke') def test_list_container_contents_with_format_xml(self): # get container contents list using format_xml param container_name = self._create_container() self._create_object(container_name) params = {'format': 'xml'} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertIsNotNone(object_list) self.assertEqual(object_list.tag, 'container') self.assertTrue('name' in object_list.keys()) self.assertEqual(object_list.find(".//object").tag, 'object') self.assertEqual(object_list.find(".//name").tag, 'name') self.assertEqual(object_list.find(".//hash").tag, 'hash') self.assertEqual(object_list.find(".//bytes").tag, 'bytes') self.assertEqual(object_list.find(".//content_type").tag, 'content_type') self.assertEqual(object_list.find(".//last_modified").tag, 'last_modified') @test.attr(type='smoke') def test_list_container_contents_with_limit(self): # get container contents list using limit param container_name = self._create_container() object_name = self._create_object(container_name) params = {'limit': data_utils.rand_int_id(1, 10000)} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual(object_name, object_list.strip('\n')) @test.attr(type='smoke') def test_list_container_contents_with_marker(self): # get container contents list using marker param container_name = self._create_container() object_name = self._create_object(container_name) params = {'marker': 'AaaaObject1234567890'} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual(object_name, object_list.strip('\n')) @test.attr(type='smoke') def test_list_container_contents_with_path(self): # get container contents list using path param container_name = self._create_container() object_name = data_utils.rand_name(name='Swift/TestObject') self._create_object(container_name, object_name) params = {'path': 'Swift'} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual(object_name, object_list.strip('\n')) @test.attr(type='smoke') def test_list_container_contents_with_prefix(self): # get container contents list using prefix param container_name = self._create_container() object_name = self._create_object(container_name) prefix_key = object_name[0:8] params = {'prefix': prefix_key} resp, object_list = self.container_client.list_container_contents( container_name, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertEqual(object_name, object_list.strip('\n')) @test.attr(type='smoke') def test_list_container_metadata(self): # List container metadata container_name = self._create_container() metadata = {'name': 'Pictures'} self.container_client.update_container_metadata( container_name, metadata=metadata) resp, _ = self.container_client.list_container_metadata( container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'HEAD') self.assertIn('x-container-meta-name', resp) self.assertEqual(resp['x-container-meta-name'], metadata['name']) @test.attr(type='smoke') def test_list_no_container_metadata(self): # HEAD container without metadata container_name = self._create_container() resp, _ = self.container_client.list_container_metadata( container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'HEAD') self.assertNotIn('x-container-meta-', str(resp)) @test.attr(type='smoke') def test_update_container_metadata_with_create_and_delete_matadata(self): # Send one request of adding and deleting metadata container_name = data_utils.rand_name(name='TestContainer') metadata_1 = {'test-container-meta1': 'Meta1'} self.container_client.create_container(container_name, metadata=metadata_1) self.containers.append(container_name) metadata_2 = {'test-container-meta2': 'Meta2'} resp, _ = self.container_client.update_container_metadata( container_name, metadata=metadata_2, remove_metadata=metadata_1) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) self.assertIn('x-container-meta-test-container-meta2', resp) self.assertEqual(resp['x-container-meta-test-container-meta2'], metadata_2['test-container-meta2']) @test.attr(type='smoke') def test_update_container_metadata_with_create_metadata(self): # update container metadata using add metadata container_name = self._create_container() metadata = {'test-container-meta1': 'Meta1'} resp, _ = self.container_client.update_container_metadata( container_name, metadata=metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertIn('x-container-meta-test-container-meta1', resp) self.assertEqual(resp['x-container-meta-test-container-meta1'], metadata['test-container-meta1']) @test.attr(type='smoke') def test_update_container_metadata_with_delete_metadata(self): # update container metadata using delete metadata container_name = data_utils.rand_name(name='TestContainer') metadata = {'test-container-meta1': 'Meta1'} self.container_client.create_container(container_name, metadata=metadata) self.containers.append(container_name) resp, _ = self.container_client.delete_container_metadata( container_name, metadata=metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) @test.attr(type='smoke') def test_update_container_metadata_with_create_matadata_key(self): # update container metadata with a blenk value of metadata container_name = self._create_container() metadata = {'test-container-meta1': ''} resp, _ = self.container_client.update_container_metadata( container_name, metadata=metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata( container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) @test.attr(type='smoke') def test_update_container_metadata_with_delete_metadata_key(self): # update container metadata with a blank value of matadata container_name = data_utils.rand_name(name='TestContainer') metadata = {'test-container-meta1': 'Meta1'} self.container_client.create_container(container_name, metadata=metadata) self.containers.append(container_name) metadata = {'test-container-meta1': ''} resp, _ = self.container_client.delete_container_metadata( container_name, metadata=metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'POST') resp, _ = self.container_client.list_container_metadata(container_name) self.assertNotIn('x-container-meta-test-container-meta1', resp) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_version.py0000664000175000017500000001014312332757070027744 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import test class ContainerTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ContainerTest, cls).setUpClass() cls.containers = [] @classmethod def tearDownClass(cls): cls.delete_containers(cls.containers) super(ContainerTest, cls).tearDownClass() def assertContainer(self, container, count, byte, versioned): resp, _ = self.container_client.list_container_metadata(container) self.assertEqual(resp['status'], ('204')) self.assertHeaders(resp, 'Container', 'HEAD') header_value = resp.get('x-container-object-count', 'Missing Header') self.assertEqual(header_value, count) header_value = resp.get('x-container-bytes-used', 'Missing Header') self.assertEqual(header_value, byte) header_value = resp.get('x-versions-location', 'Missing Header') self.assertEqual(header_value, versioned) @test.attr(type='smoke') def test_versioned_container(self): # create container vers_container_name = data_utils.rand_name(name='TestVersionContainer') resp, body = self.container_client.create_container( vers_container_name) self.containers.append(vers_container_name) self.assertIn(resp['status'], ('202', '201')) self.assertHeaders(resp, 'Container', 'PUT') self.assertContainer(vers_container_name, '0', '0', 'Missing Header') base_container_name = data_utils.rand_name(name='TestBaseContainer') headers = {'X-versions-Location': vers_container_name} resp, body = self.container_client.create_container( base_container_name, metadata=headers, metadata_prefix='') self.containers.append(base_container_name) self.assertIn(resp['status'], ('202', '201')) self.assertHeaders(resp, 'Container', 'PUT') self.assertContainer(base_container_name, '0', '0', vers_container_name) object_name = data_utils.rand_name(name='TestObject') # create object resp, _ = self.object_client.create_object(base_container_name, object_name, '1') # create 2nd version of object resp, _ = self.object_client.create_object(base_container_name, object_name, '2') resp, body = self.object_client.get_object(base_container_name, object_name) self.assertEqual(body, '2') # delete object version 2 resp, _ = self.object_client.delete_object(base_container_name, object_name) self.assertContainer(base_container_name, '1', '1', vers_container_name) resp, body = self.object_client.get_object(base_container_name, object_name) self.assertEqual(body, '1') # delete object version 1 resp, _ = self.object_client.delete_object(base_container_name, object_name) # containers should be empty self.assertContainer(base_container_name, '0', '0', vers_container_name) self.assertContainer(vers_container_name, '0', '0', 'Missing Header') tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_container_acl.py0000664000175000017500000000754712332757070027550 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.object_storage import base from tempest import clients from tempest.common.utils import data_utils from tempest import test class ObjectTestACLs(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ObjectTestACLs, cls).setUpClass() cls.data.setup_test_user() test_os = clients.Manager(cls.data.test_credentials) cls.test_auth_data = test_os.auth_provider.auth_data @classmethod def tearDownClass(cls): cls.data.teardown_all() super(ObjectTestACLs, cls).tearDownClass() def setUp(self): super(ObjectTestACLs, self).setUp() self.container_name = data_utils.rand_name(name='TestContainer') self.container_client.create_container(self.container_name) def tearDown(self): self.delete_containers([self.container_name]) super(ObjectTestACLs, self).tearDown() @test.attr(type='smoke') def test_read_object_with_rights(self): # attempt to read object using authorized user # update X-Container-Read metadata ACL cont_headers = {'X-Container-Read': self.data.test_tenant + ':' + self.data.test_user} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object(self.container_name, object_name, 'data') self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # Trying to read the object with rights self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) resp, _ = self.custom_object_client.get_object( self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') @test.attr(type='smoke') def test_write_object_with_rights(self): # attempt to write object using authorized user # update X-Container-Write metadata ACL cont_headers = {'X-Container-Write': self.data.test_tenant + ':' + self.data.test_user} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # Trying to write the object with rights self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) object_name = data_utils.rand_name(name='Object') resp, _ = self.custom_object_client.create_object( self.container_name, object_name, 'data') self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'PUT') tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_account_quotas_negative.py0000664000175000017500000001214612332757070031650 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 tempest.api.object_storage import base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class AccountQuotasNegativeTest(base.BaseObjectTest): @classmethod @test.safe_setup def setUpClass(cls): super(AccountQuotasNegativeTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name="TestContainer") cls.container_client.create_container(cls.container_name) cls.data.setup_test_user() cls.os_reselleradmin = clients.Manager(cls.data.test_credentials) # Retrieve the ResellerAdmin role id reseller_role_id = None try: _, roles = cls.os_admin.identity_client.list_roles() reseller_role_id = next(r['id'] for r in roles if r['name'] == CONF.object_storage.reseller_admin_role) except StopIteration: msg = "No ResellerAdmin role found" raise exceptions.NotFound(msg) # Retrieve the ResellerAdmin tenant id _, users = cls.os_admin.identity_client.get_users() reseller_user_id = next(usr['id'] for usr in users if usr['name'] == cls.data.test_user) # Retrieve the ResellerAdmin tenant id _, tenants = cls.os_admin.identity_client.list_tenants() reseller_tenant_id = next(tnt['id'] for tnt in tenants if tnt['name'] == cls.data.test_tenant) # Assign the newly created user the appropriate ResellerAdmin role cls.os_admin.identity_client.assign_user_role( reseller_tenant_id, reseller_user_id, reseller_role_id) # Retrieve a ResellerAdmin auth data and use it to set a quota # on the client's account cls.reselleradmin_auth_data = \ cls.os_reselleradmin.auth_provider.auth_data def setUp(self): super(AccountQuotasNegativeTest, self).setUp() # Set the reselleradmin auth in headers for next custom_account_client # request self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # Set a quota of 20 bytes on the user's account before each test headers = {"X-Account-Meta-Quota-Bytes": "20"} self.os.custom_account_client.request("POST", url="", headers=headers, body="") def tearDown(self): # Set the reselleradmin auth in headers for next custom_account_client # request self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # remove the quota from the container headers = {"X-Remove-Account-Meta-Quota-Bytes": "x"} self.os.custom_account_client.request("POST", url="", headers=headers, body="") super(AccountQuotasNegativeTest, self).tearDown() @classmethod def tearDownClass(cls): if hasattr(cls, "container_name"): cls.delete_containers([cls.container_name]) cls.data.teardown_all() super(AccountQuotasNegativeTest, cls).tearDownClass() @test.attr(type=["negative", "smoke"]) @test.requires_ext(extension='account_quotas', service='object') def test_user_modify_quota(self): """Test that a user is not able to modify or remove a quota on its account. """ # Not able to remove quota self.assertRaises(exceptions.Unauthorized, self.account_client.create_account_metadata, {"Quota-Bytes": ""}) # Not able to modify quota self.assertRaises(exceptions.Unauthorized, self.account_client.create_account_metadata, {"Quota-Bytes": "100"}) @test.attr(type=["negative", "smoke"]) @test.skip_because(bug="1310597") @test.requires_ext(extension='account_quotas', service='object') def test_upload_large_object(self): object_name = data_utils.rand_name(name="TestObject") data = data_utils.arbitrary_string(30) self.assertRaises(exceptions.OverLimit, self.object_client.create_object, self.container_name, object_name, data) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_temp_url_negative.py0000664000175000017500000000754412332757070032003 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 hashlib import hmac import time import urlparse from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ObjectTempUrlNegativeTest(base.BaseObjectTest): metadata = {} containers = [] @classmethod @test.safe_setup def setUpClass(cls): super(ObjectTempUrlNegativeTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name='TestContainer') cls.container_client.create_container(cls.container_name) cls.containers = [cls.container_name] # update account metadata cls.key = 'Meta' cls.metadata = {'Temp-URL-Key': cls.key} cls.account_client.create_account_metadata(metadata=cls.metadata) cls.account_client_metadata, _ = \ cls.account_client.list_account_metadata() @classmethod def tearDownClass(cls): resp, _ = cls.account_client.delete_account_metadata( metadata=cls.metadata) cls.delete_containers(cls.containers) # delete the user setup created cls.data.teardown_all() super(ObjectTempUrlNegativeTest, cls).tearDownClass() def setUp(self): super(ObjectTempUrlNegativeTest, self).setUp() # make sure the metadata has been set self.assertIn('x-account-meta-temp-url-key', self.account_client_metadata) self.assertEqual( self.account_client_metadata['x-account-meta-temp-url-key'], self.key) # create object self.object_name = data_utils.rand_name(name='ObjectTemp') self.data = data_utils.arbitrary_string(size=len(self.object_name), base_text=self.object_name) self.object_client.create_object(self.container_name, self.object_name, self.data) def _get_expiry_date(self, expiration_time=1000): return int(time.time() + expiration_time) def _get_temp_url(self, container, object_name, method, expires, key): """Create the temporary URL.""" path = "%s/%s/%s" % ( urlparse.urlparse(self.object_client.base_url).path, container, object_name) hmac_body = '%s\n%s\n%s' % (method, expires, path) sig = hmac.new(key, hmac_body, hashlib.sha1).hexdigest() url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container, object_name, sig, expires) return url @test.attr(type=['gate', 'negative']) @test.requires_ext(extension='tempurl', service='object') def test_get_object_after_expiration_time(self): expires = self._get_expiry_date(1) # get a temp URL for the created object url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, self.key) # temp URL is valid for 1 seconds, let's wait 2 time.sleep(2) self.assertRaises(exceptions.Unauthorized, self.object_client.get, url) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_expiry.py0000664000175000017500000000611012332757070027576 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ObjectExpiryTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ObjectExpiryTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name='TestContainer') cls.container_client.create_container(cls.container_name) def setUp(self): super(ObjectExpiryTest, self).setUp() # create object self.object_name = data_utils.rand_name(name='TestObject') resp, _ = self.object_client.create_object(self.container_name, self.object_name, '') @classmethod def tearDownClass(cls): cls.delete_containers([cls.container_name]) super(ObjectExpiryTest, cls).tearDownClass() def _test_object_expiry(self, metadata): # update object metadata resp, _ = \ self.object_client.update_object_metadata(self.container_name, self.object_name, metadata, metadata_prefix='') # verify object metadata resp, _ = \ self.object_client.list_object_metadata(self.container_name, self.object_name) self.assertEqual(resp['status'], '200') self.assertHeaders(resp, 'Object', 'HEAD') self.assertIn('x-delete-at', resp) resp, body = self.object_client.get_object(self.container_name, self.object_name) self.assertEqual(resp['status'], '200') self.assertHeaders(resp, 'Object', 'GET') self.assertIn('x-delete-at', resp) # sleep for over 5 seconds, so that object expires time.sleep(5) # object should not be there anymore self.assertRaises(exceptions.NotFound, self.object_client.get_object, self.container_name, self.object_name) @test.attr(type='gate') def test_get_object_after_expiry_time(self): metadata = {'X-Delete-After': '3'} self._test_object_expiry(metadata) @test.attr(type='gate') def test_get_object_at_expiry_time(self): metadata = {'X-Delete-At': str(int(time.time()) + 3)} self._test_object_expiry(metadata) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_temp_url.py0000664000175000017500000001574612332757070030124 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 hashlib import hmac import time import urlparse from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class ObjectTempUrlTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ObjectTempUrlTest, cls).setUpClass() # create a container cls.container_name = data_utils.rand_name(name='TestContainer') cls.container_client.create_container(cls.container_name) cls.containers = [cls.container_name] # update account metadata cls.key = 'Meta' cls.metadatas = [] metadata = {'Temp-URL-Key': cls.key} cls.metadatas.append(metadata) cls.account_client.create_account_metadata(metadata=metadata) # create an object cls.object_name = data_utils.rand_name(name='ObjectTemp') cls.content = data_utils.arbitrary_string(size=len(cls.object_name), base_text=cls.object_name) cls.object_client.create_object(cls.container_name, cls.object_name, cls.content) @classmethod def tearDownClass(cls): for metadata in cls.metadatas: cls.account_client.delete_account_metadata( metadata=metadata) cls.delete_containers(cls.containers) # delete the user setup created cls.data.teardown_all() super(ObjectTempUrlTest, cls).tearDownClass() def setUp(self): super(ObjectTempUrlTest, self).setUp() # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key'], self.key) def _get_expiry_date(self, expiration_time=1000): return int(time.time() + expiration_time) def _get_temp_url(self, container, object_name, method, expires, key): """Create the temporary URL.""" path = "%s/%s/%s" % ( urlparse.urlparse(self.object_client.base_url).path, container, object_name) hmac_body = '%s\n%s\n%s' % (method, expires, path) sig = hmac.new(key, hmac_body, hashlib.sha1).hexdigest() url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container, object_name, sig, expires) return url @test.attr(type='gate') @test.requires_ext(extension='tempurl', service='object') def test_get_object_using_temp_url(self): expires = self._get_expiry_date() # get a temp URL for the created object url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, self.key) # trying to get object using temp url within expiry time resp, body = self.object_client.get(url) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, self.content) # Testing a HEAD on this Temp URL resp, body = self.object_client.head(url) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'HEAD') @test.attr(type='gate') @test.requires_ext(extension='tempurl', service='object') def test_get_object_using_temp_url_key_2(self): key2 = 'Meta2-' metadata = {'Temp-URL-Key-2': key2} self.account_client.create_account_metadata(metadata=metadata) self.metadatas.append(metadata) # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key-2', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key-2'], key2) expires = self._get_expiry_date() url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, key2) resp, body = self.object_client.get(url) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertEqual(body, self.content) @test.attr(type='gate') @test.requires_ext(extension='tempurl', service='object') def test_put_object_using_temp_url(self): new_data = data_utils.arbitrary_string( size=len(self.object_name), base_text=data_utils.rand_name(name="random")) expires = self._get_expiry_date() url = self._get_temp_url(self.container_name, self.object_name, "PUT", expires, self.key) # trying to put random data in the object using temp url resp, body = self.object_client.put(url, new_data, None) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'PUT') # Testing a HEAD on this Temp URL resp, body = self.object_client.head(url) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'HEAD') # Validate that the content of the object has been modified url = self._get_temp_url(self.container_name, self.object_name, "GET", expires, self.key) _, body = self.object_client.get(url) self.assertEqual(body, new_data) @test.attr(type='gate') @test.requires_ext(extension='tempurl', service='object') def test_head_object_using_temp_url(self): expires = self._get_expiry_date() # get a temp URL for the created object url = self._get_temp_url(self.container_name, self.object_name, "HEAD", expires, self.key) # Testing a HEAD on this Temp URL resp, body = self.object_client.head(url) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'HEAD') tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_slo.py0000664000175000017500000001564512332757070027070 0ustar chuckchuck00000000000000# Copyright 2013 NTT Corporation # # 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 hashlib import json from tempest.api.object_storage import base from tempest.common import custom_matchers from tempest.common.utils import data_utils from tempest import exceptions from tempest import test # Each segment, except for the final one, must be at least 1 megabyte MIN_SEGMENT_SIZE = 1024 * 1024 class ObjectSloTest(base.BaseObjectTest): def setUp(self): super(ObjectSloTest, self).setUp() self.container_name = data_utils.rand_name(name='TestContainer') self.container_client.create_container(self.container_name) self.objects = [] def tearDown(self): for obj in self.objects: try: self.object_client.delete_object( self.container_name, obj) except exceptions.NotFound: pass self.container_client.delete_container(self.container_name) super(ObjectSloTest, self).tearDown() def _create_object(self, container_name, object_name, data, params=None): resp, _ = self.object_client.create_object(container_name, object_name, data, params) self.objects.append(object_name) return resp def _create_manifest(self): # Create a manifest file for SLO uploading object_name = data_utils.rand_name(name='TestObject') object_name_base_1 = object_name + '_01' object_name_base_2 = object_name + '_02' data_size = MIN_SEGMENT_SIZE self.data = data_utils.arbitrary_string(data_size) self._create_object(self.container_name, object_name_base_1, self.data) self._create_object(self.container_name, object_name_base_2, self.data) path_object_1 = '/%s/%s' % (self.container_name, object_name_base_1) path_object_2 = '/%s/%s' % (self.container_name, object_name_base_2) data_manifest = [{'path': path_object_1, 'etag': hashlib.md5(self.data).hexdigest(), 'size_bytes': data_size}, {'path': path_object_2, 'etag': hashlib.md5(self.data).hexdigest(), 'size_bytes': data_size}] return json.dumps(data_manifest) def _create_large_object(self): # Create a large object for preparation of testing various SLO # features manifest = self._create_manifest() params = {'multipart-manifest': 'put'} object_name = data_utils.rand_name(name='TestObject') self._create_object(self.container_name, object_name, manifest, params) return object_name def _assertHeadersSLO(self, resp, method): # When sending GET or HEAD requests to SLO the response contains # 'X-Static-Large-Object' header if method in ('GET', 'HEAD'): self.assertIn('x-static-large-object', resp) self.assertEqual(resp['x-static-large-object'], 'True') # Etag value of a large object is enclosed in double-quotations. # After etag quotes are checked they are removed and the response is # checked if all common headers are present and well formatted self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) resp['etag'] = resp['etag'].strip('"') self.assertHeaders(resp, 'Object', method) @test.attr(type='gate') def test_upload_manifest(self): # create static large object from multipart manifest manifest = self._create_manifest() params = {'multipart-manifest': 'put'} object_name = data_utils.rand_name(name='TestObject') resp = self._create_object(self.container_name, object_name, manifest, params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self._assertHeadersSLO(resp, 'PUT') @test.attr(type='gate') def test_list_large_object_metadata(self): # list static large object metadata using multipart manifest object_name = self._create_large_object() resp, body = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self._assertHeadersSLO(resp, 'HEAD') @test.attr(type='gate') def test_retrieve_large_object(self): # list static large object using multipart manifest object_name = self._create_large_object() resp, body = self.object_client.get_object( self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self._assertHeadersSLO(resp, 'GET') sum_data = self.data + self.data self.assertEqual(body, sum_data) @test.attr(type='gate') def test_delete_large_object(self): # delete static large object using multipart manifest object_name = self._create_large_object() params_del = {'multipart-manifest': 'delete'} resp, body = self.object_client.delete_object( self.container_name, object_name, params=params_del) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # When deleting SLO using multipart manifest, the response contains # not 'content-length' but 'transfer-encoding' header. This is the # special case, therefore the existence of response headers is checked # outside of custom matcher. self.assertIn('transfer-encoding', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) resp, body = self.container_client.list_container_contents( self.container_name) self.assertEqual(int(resp['x-container-object-count']), 0) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_formpost.py0000664000175000017500000001106112332757070030130 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Christian Schwede # # 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 hashlib import hmac import time import urlparse from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import test class ObjectFormPostTest(base.BaseObjectTest): metadata = {} containers = [] @classmethod @test.safe_setup def setUpClass(cls): super(ObjectFormPostTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name='TestContainer') cls.object_name = data_utils.rand_name(name='ObjectTemp') cls.container_client.create_container(cls.container_name) cls.containers = [cls.container_name] cls.key = 'Meta' cls.metadata = {'Temp-URL-Key': cls.key} cls.account_client.create_account_metadata(metadata=cls.metadata) def setUp(self): super(ObjectFormPostTest, self).setUp() # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key'], self.key) @classmethod def tearDownClass(cls): cls.account_client.delete_account_metadata(metadata=cls.metadata) cls.delete_containers(cls.containers) cls.data.teardown_all() super(ObjectFormPostTest, cls).tearDownClass() def get_multipart_form(self, expires=600): path = "%s/%s/%s" % ( urlparse.urlparse(self.container_client.base_url).path, self.container_name, self.object_name) redirect = '' max_file_size = 104857600 max_file_count = 10 expires += int(time.time()) hmac_body = '%s\n%s\n%s\n%s\n%s' % (path, redirect, max_file_size, max_file_count, expires) signature = hmac.new(self.key, hmac_body, hashlib.sha1).hexdigest() fields = {'redirect': redirect, 'max_file_size': str(max_file_size), 'max_file_count': str(max_file_count), 'expires': str(expires), 'signature': signature} boundary = '--boundary--' data = [] for (key, value) in fields.items(): data.append('--' + boundary) data.append('Content-Disposition: form-data; name="%s"' % key) data.append('') data.append(value) data.append('--' + boundary) data.append('Content-Disposition: form-data; ' 'name="file1"; filename="testfile"') data.append('Content-Type: application/octet-stream') data.append('') data.append('hello world') data.append('--' + boundary + '--') data.append('') body = '\r\n'.join(data) content_type = 'multipart/form-data; boundary=%s' % boundary return body, content_type @test.requires_ext(extension='formpost', service='object') @test.attr(type='gate') def test_post_object_using_form(self): body, content_type = self.get_multipart_form() headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} url = "%s/%s" % (self.container_name, self.object_name) resp, body = self.object_client.post(url, body, headers=headers) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, "Object", "POST") # Ensure object is available resp, body = self.object_client.get("%s/%s%s" % ( self.container_name, self.object_name, "testfile")) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, "Object", "GET") self.assertEqual(body, "hello world") tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/__init__.py0000664000175000017500000000000012332757070025420 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_account_quotas.py0000664000175000017500000001307312332757070027766 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 tempest.api.object_storage import base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class AccountQuotasTest(base.BaseObjectTest): @classmethod @test.safe_setup def setUpClass(cls): super(AccountQuotasTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name="TestContainer") cls.container_client.create_container(cls.container_name) cls.data.setup_test_user() cls.os_reselleradmin = clients.Manager(cls.data.test_credentials) # Retrieve the ResellerAdmin role id reseller_role_id = None try: _, roles = cls.os_admin.identity_client.list_roles() reseller_role_id = next(r['id'] for r in roles if r['name'] == CONF.object_storage.reseller_admin_role) except StopIteration: msg = "No ResellerAdmin role found" raise exceptions.NotFound(msg) # Retrieve the ResellerAdmin tenant id _, users = cls.os_admin.identity_client.get_users() reseller_user_id = next(usr['id'] for usr in users if usr['name'] == cls.data.test_user) # Retrieve the ResellerAdmin tenant id _, tenants = cls.os_admin.identity_client.list_tenants() reseller_tenant_id = next(tnt['id'] for tnt in tenants if tnt['name'] == cls.data.test_tenant) # Assign the newly created user the appropriate ResellerAdmin role cls.os_admin.identity_client.assign_user_role( reseller_tenant_id, reseller_user_id, reseller_role_id) # Retrieve a ResellerAdmin auth data and use it to set a quota # on the client's account cls.reselleradmin_auth_data = \ cls.os_reselleradmin.auth_provider.auth_data def setUp(self): super(AccountQuotasTest, self).setUp() # Set the reselleradmin auth in headers for next custom_account_client # request self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # Set a quota of 20 bytes on the user's account before each test headers = {"X-Account-Meta-Quota-Bytes": "20"} self.os.custom_account_client.request("POST", url="", headers=headers, body="") def tearDown(self): # Set the reselleradmin auth in headers for next custom_account_client # request self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) # remove the quota from the container headers = {"X-Remove-Account-Meta-Quota-Bytes": "x"} self.os.custom_account_client.request("POST", url="", headers=headers, body="") super(AccountQuotasTest, self).tearDown() @classmethod def tearDownClass(cls): if hasattr(cls, "container_name"): cls.delete_containers([cls.container_name]) cls.data.teardown_all() super(AccountQuotasTest, cls).tearDownClass() @test.attr(type="smoke") @test.requires_ext(extension='account_quotas', service='object') def test_upload_valid_object(self): object_name = data_utils.rand_name(name="TestObject") data = data_utils.arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertEqual(resp["status"], "201") self.assertHeaders(resp, 'Object', 'PUT') @test.attr(type=["smoke"]) @test.requires_ext(extension='account_quotas', service='object') def test_admin_modify_quota(self): """Test that the ResellerAdmin is able to modify and remove the quota on a user's account. Using the custom_account client, the test modifies the quota successively to: * "25": a random value different from the initial quota value. * "" : an empty value, equivalent to the removal of the quota. * "20": set the quota to its initial value. """ for quota in ("25", "", "20"): self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.reselleradmin_auth_data ) headers = {"X-Account-Meta-Quota-Bytes": quota} resp, _ = self.os.custom_account_client.request("POST", url="", headers=headers, body="") self.assertEqual(resp["status"], "204") self.assertHeaders(resp, 'Account', 'POST') tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_account_services_negative.py0000664000175000017500000000367512332757070032166 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 tempest.api.object_storage import base from tempest import clients from tempest import exceptions from tempest.test import attr class AccountNegativeTest(base.BaseObjectTest): @attr(type=['negative', 'gate']) def test_list_containers_with_non_authorized_user(self): # list containers using non-authorized user # create user self.data.setup_test_user() test_os = clients.Manager(self.data.test_credentials) test_auth_provider = test_os.auth_provider # Get auth for the test user test_auth_provider.auth_data # Get fresh auth for test user and set it to next auth request for # custom_account_client delattr(test_auth_provider, 'auth_data') test_auth_new_data = test_auth_provider.auth_data self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=test_auth_new_data ) params = {'format': 'json'} # list containers with non-authorized user token self.assertRaises(exceptions.Unauthorized, self.custom_account_client.list_account_containers, params=params) # delete the user which was created self.data.teardown_all() tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_formpost_negative.py0000664000175000017500000001172512332757070032021 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # Author: Joe H. Rahme # # 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 hashlib import hmac import time import urlparse from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ObjectFormPostNegativeTest(base.BaseObjectTest): metadata = {} containers = [] @classmethod @test.safe_setup def setUpClass(cls): super(ObjectFormPostNegativeTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name='TestContainer') cls.object_name = data_utils.rand_name(name='ObjectTemp') cls.container_client.create_container(cls.container_name) cls.containers = [cls.container_name] cls.key = 'Meta' cls.metadata = {'Temp-URL-Key': cls.key} cls.account_client.create_account_metadata(metadata=cls.metadata) def setUp(self): super(ObjectFormPostNegativeTest, self).setUp() # make sure the metadata has been set account_client_metadata, _ = \ self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key', account_client_metadata) self.assertEqual( account_client_metadata['x-account-meta-temp-url-key'], self.key) @classmethod def tearDownClass(cls): cls.account_client.delete_account_metadata(metadata=cls.metadata) cls.delete_containers(cls.containers) cls.data.teardown_all() super(ObjectFormPostNegativeTest, cls).tearDownClass() def get_multipart_form(self, expires=600): path = "%s/%s/%s" % ( urlparse.urlparse(self.container_client.base_url).path, self.container_name, self.object_name) redirect = '' max_file_size = 104857600 max_file_count = 10 expires += int(time.time()) hmac_body = '%s\n%s\n%s\n%s\n%s' % (path, redirect, max_file_size, max_file_count, expires) signature = hmac.new(self.key, hmac_body, hashlib.sha1).hexdigest() fields = {'redirect': redirect, 'max_file_size': str(max_file_size), 'max_file_count': str(max_file_count), 'expires': str(expires), 'signature': signature} boundary = '--boundary--' data = [] for (key, value) in fields.items(): data.append('--' + boundary) data.append('Content-Disposition: form-data; name="%s"' % key) data.append('') data.append(value) data.append('--' + boundary) data.append('Content-Disposition: form-data; ' 'name="file1"; filename="testfile"') data.append('Content-Type: application/octet-stream') data.append('') data.append('hello world') data.append('--' + boundary + '--') data.append('') body = '\r\n'.join(data) content_type = 'multipart/form-data; boundary=%s' % boundary return body, content_type @test.requires_ext(extension='formpost', service='object') @test.attr(type=['gate', 'negative']) def test_post_object_using_form_expired(self): body, content_type = self.get_multipart_form(expires=1) time.sleep(2) headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} url = "%s/%s" % (self.container_name, self.object_name) exc = self.assertRaises( exceptions.Unauthorized, self.object_client.post, url, body, headers=headers) self.assertIn('FormPost: Form Expired', str(exc)) @test.requires_ext(extension='formpost', service='object') @test.attr(type='gate') def test_post_object_using_form_invalid_signature(self): self.key = "Wrong" body, content_type = self.get_multipart_form() headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} url = "%s/%s" % (self.container_name, self.object_name) exc = self.assertRaises( exceptions.Unauthorized, self.object_client.post, url, body, headers=headers) self.assertIn('FormPost: Invalid Signature', str(exc)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_container_sync.py0000664000175000017500000001252712332757070027757 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time import urlparse from tempest.api.object_storage import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF # This test can be quite long to run due to its # dependency on container-sync process running interval. # You can obviously reduce the container-sync interval in the # container-server configuration. class ContainerSyncTest(base.BaseObjectTest): clients = {} @classmethod @test.safe_setup def setUpClass(cls): super(ContainerSyncTest, cls).setUpClass() cls.containers = [] cls.objects = [] # Default container-server config only allows localhost cls.local_ip = '127.0.0.1' # Must be configure according to container-sync interval container_sync_timeout = \ int(CONF.object_storage.container_sync_timeout) cls.container_sync_interval = \ int(CONF.object_storage.container_sync_interval) cls.attempts = \ int(container_sync_timeout / cls.container_sync_interval) # define container and object clients cls.clients[data_utils.rand_name(name='TestContainerSync')] = \ (cls.container_client, cls.object_client) cls.clients[data_utils.rand_name(name='TestContainerSync')] = \ (cls.container_client_alt, cls.object_client_alt) for cont_name, client in cls.clients.items(): client[0].create_container(cont_name) cls.containers.append(cont_name) @classmethod def tearDownClass(cls): for client in cls.clients.values(): cls.delete_containers(cls.containers, client[0], client[1]) super(ContainerSyncTest, cls).tearDownClass() @test.attr(type='slow') def test_container_synchronization(self): # container to container synchronization # to allow/accept sync requests to/from other accounts # turn container synchronization on and create object in container for cont in (self.containers, self.containers[::-1]): cont_client = [self.clients[c][0] for c in cont] obj_client = [self.clients[c][1] for c in cont] # tell first container to synchronize to a second client_proxy_ip = \ urlparse.urlparse(cont_client[1].base_url).netloc.split(':')[0] client_base_url = \ cont_client[1].base_url.replace(client_proxy_ip, self.local_ip) headers = {'X-Container-Sync-Key': 'sync_key', 'X-Container-Sync-To': "%s/%s" % (client_base_url, str(cont[1]))} resp, body = \ cont_client[0].put(str(cont[0]), body=None, headers=headers) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # create object in container object_name = data_utils.rand_name(name='TestSyncObject') data = object_name[::-1] # data_utils.arbitrary_string() resp, _ = obj_client[0].create_object(cont[0], object_name, data) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.objects.append(object_name) # wait until container contents list is not empty cont_client = [self.clients[c][0] for c in self.containers] params = {'format': 'json'} while self.attempts > 0: object_lists = [] for client_index in (0, 1): resp, object_list = \ cont_client[client_index].\ list_container_contents(self.containers[client_index], params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) object_lists.append(dict( (obj['name'], obj) for obj in object_list)) # check that containers are not empty and have equal keys() # or wait for next attempt if not object_lists[0] or not object_lists[1] or \ set(object_lists[0].keys()) != set(object_lists[1].keys()): time.sleep(self.container_sync_interval) self.attempts -= 1 else: break self.assertEqual(object_lists[0], object_lists[1], 'Different object lists in containers.') # Verify object content obj_clients = [(self.clients[c][1], c) for c in self.containers] for obj_client, cont in obj_clients: for obj_name in object_lists[0]: resp, object_content = obj_client.get_object(cont, obj_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertEqual(object_content, obj_name[::-1]) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_container_acl_negative.py0000664000175000017500000002405612332757070031424 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 tempest.api.object_storage import base from tempest import clients from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ObjectACLsNegativeTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ObjectACLsNegativeTest, cls).setUpClass() cls.data.setup_test_user() test_os = clients.Manager(cls.data.test_credentials) cls.test_auth_data = test_os.auth_provider.auth_data @classmethod def tearDownClass(cls): cls.data.teardown_all() super(ObjectACLsNegativeTest, cls).tearDownClass() def setUp(self): super(ObjectACLsNegativeTest, self).setUp() self.container_name = data_utils.rand_name(name='TestContainer') self.container_client.create_container(self.container_name) def tearDown(self): self.delete_containers([self.container_name]) super(ObjectACLsNegativeTest, self).tearDown() @test.attr(type=['negative', 'gate']) def test_write_object_without_using_creds(self): # trying to create object with empty headers # X-Auth-Token is not provided object_name = data_utils.rand_name(name='Object') self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) self.assertRaises(exceptions.Unauthorized, self.custom_object_client.create_object, self.container_name, object_name, 'data') @test.attr(type=['negative', 'gate']) def test_delete_object_without_using_creds(self): # create object object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object(self.container_name, object_name, 'data') # trying to delete object with empty headers # X-Auth-Token is not provided self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) self.assertRaises(exceptions.Unauthorized, self.custom_object_client.delete_object, self.container_name, object_name) @test.attr(type=['negative', 'gate']) def test_write_object_with_non_authorized_user(self): # attempt to upload another file using non-authorized user # User provided token is forbidden. ACL are not set object_name = data_utils.rand_name(name='Object') # trying to create object with non-authorized user self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(exceptions.Unauthorized, self.custom_object_client.create_object, self.container_name, object_name, 'data') @test.attr(type=['negative', 'gate']) def test_read_object_with_non_authorized_user(self): # attempt to read object using non-authorized user # User provided token is forbidden. ACL are not set object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object( self.container_name, object_name, 'data') self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # trying to get object with non authorized user token self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(exceptions.Unauthorized, self.custom_object_client.get_object, self.container_name, object_name) @test.attr(type=['negative', 'gate']) def test_delete_object_with_non_authorized_user(self): # attempt to delete object using non-authorized user # User provided token is forbidden. ACL are not set object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object( self.container_name, object_name, 'data') self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # trying to delete object with non-authorized user token self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(exceptions.Unauthorized, self.custom_object_client.delete_object, self.container_name, object_name) @test.attr(type=['negative', 'smoke']) def test_read_object_without_rights(self): # attempt to read object using non-authorized user # update X-Container-Read metadata ACL cont_headers = {'X-Container-Read': 'badtenant:baduser'} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object(self.container_name, object_name, 'data') self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # Trying to read the object without rights self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(exceptions.Unauthorized, self.custom_object_client.get_object, self.container_name, object_name) @test.attr(type=['negative', 'smoke']) def test_write_object_without_rights(self): # attempt to write object using non-authorized user # update X-Container-Write metadata ACL cont_headers = {'X-Container-Write': 'badtenant:baduser'} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # Trying to write the object without rights self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) object_name = data_utils.rand_name(name='Object') self.assertRaises(exceptions.Unauthorized, self.custom_object_client.create_object, self.container_name, object_name, 'data') @test.attr(type=['negative', 'smoke']) def test_write_object_without_write_rights(self): # attempt to write object using non-authorized user # update X-Container-Read and X-Container-Write metadata ACL cont_headers = {'X-Container-Read': self.data.test_tenant + ':' + self.data.test_user, 'X-Container-Write': ''} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # Trying to write the object without write rights self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) object_name = data_utils.rand_name(name='Object') self.assertRaises(exceptions.Unauthorized, self.custom_object_client.create_object, self.container_name, object_name, 'data') @test.attr(type=['negative', 'smoke']) def test_delete_object_without_write_rights(self): # attempt to delete object using non-authorized user # update X-Container-Read and X-Container-Write metadata ACL cont_headers = {'X-Container-Read': self.data.test_tenant + ':' + self.data.test_user, 'X-Container-Write': ''} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') resp, _ = self.object_client.create_object(self.container_name, object_name, 'data') self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # Trying to delete the object without write rights self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=self.test_auth_data ) self.assertRaises(exceptions.Unauthorized, self.custom_object_client.delete_object, self.container_name, object_name) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_container_staticweb.py0000664000175000017500000001534112332757070030765 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 tempest.api.object_storage import base from tempest.common import custom_matchers from tempest.common.utils import data_utils from tempest import test class StaticWebTest(base.BaseObjectTest): @classmethod @test.safe_setup def setUpClass(cls): super(StaticWebTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name="TestContainer") # This header should be posted on the container before every test cls.headers_public_read_acl = {'Read': '.r:*'} # Create test container and create one object in it cls.container_client.create_container(cls.container_name) cls.object_name = data_utils.rand_name(name="TestObject") cls.object_data = data_utils.arbitrary_string() cls.object_client.create_object(cls.container_name, cls.object_name, cls.object_data) cls.container_client.update_container_metadata( cls.container_name, metadata=cls.headers_public_read_acl, metadata_prefix="X-Container-") @classmethod def tearDownClass(cls): if hasattr(cls, "container_name"): cls.delete_containers([cls.container_name]) cls.data.teardown_all() super(StaticWebTest, cls).tearDownClass() @test.requires_ext(extension='staticweb', service='object') @test.attr('gate') def test_web_index(self): headers = {'web-index': self.object_name} self.container_client.update_container_metadata( self.container_name, metadata=headers) # Maintain original headers, no auth added self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) # test GET on http://account_url/container_name # we should retrieve the self.object_name file resp, body = self.custom_account_client.request("GET", self.container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # This request is equivalent to GET object self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, self.object_data) # clean up before exiting self.container_client.update_container_metadata(self.container_name, {'web-index': ""}) _, body = self.container_client.list_container_metadata( self.container_name) self.assertNotIn('x-container-meta-web-index', body) @test.requires_ext(extension='staticweb', service='object') @test.attr('gate') def test_web_listing(self): headers = {'web-listings': 'true'} self.container_client.update_container_metadata( self.container_name, metadata=headers) # test GET on http://account_url/container_name # we should retrieve a listing of objects resp, body = self.custom_account_client.request("GET", self.container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # The target of the request is not any Swift resource. Therefore, the # existence of response header is checked without a custom matcher. self.assertIn('content-length', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) self.assertIn(self.object_name, body) # clean up before exiting self.container_client.update_container_metadata(self.container_name, {'web-listings': ""}) _, body = self.container_client.list_container_metadata( self.container_name) self.assertNotIn('x-container-meta-web-listings', body) @test.requires_ext(extension='staticweb', service='object') @test.attr('gate') def test_web_listing_css(self): headers = {'web-listings': 'true', 'web-listings-css': 'listings.css'} self.container_client.update_container_metadata( self.container_name, metadata=headers) # Maintain original headers, no auth added self.custom_account_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) # test GET on http://account_url/container_name # we should retrieve a listing of objects resp, body = self.custom_account_client.request("GET", self.container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertIn(self.object_name, body) css = '' self.assertIn(css, body) @test.requires_ext(extension='staticweb', service='object') @test.attr('gate') def test_web_error(self): headers = {'web-listings': 'true', 'web-error': self.object_name} self.container_client.update_container_metadata( self.container_name, metadata=headers) # Create object to return when requested object not found object_name_404 = "404" + self.object_name object_data_404 = data_utils.arbitrary_string() self.object_client.create_object(self.container_name, object_name_404, object_data_404) # Do not set auth in HTTP headers for next request self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) # Request non-existing object resp, body = self.custom_object_client.get_object(self.container_name, "notexisting") self.assertEqual(resp['status'], '404') self.assertEqual(body, object_data_404) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_healthcheck.py0000664000175000017500000000334712332757070027204 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 tempest.api.object_storage import base from tempest.common import custom_matchers from tempest import test class HealthcheckTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(HealthcheckTest, cls).setUpClass() def setUp(self): super(HealthcheckTest, self).setUp() # Turning http://.../v1/foobar into http://.../ self.account_client.skip_path() @test.attr('gate') def test_get_healthcheck(self): resp, _ = self.account_client.get("healthcheck", {}) # The status is expected to be 200 self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # The target of the request is not any Swift resource. Therefore, the # existence of response header is checked without a custom matcher. self.assertIn('content-length', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_account_bulk.py0000664000175000017500000001201212332757070027377 0ustar chuckchuck00000000000000# Copyright 2013 NTT Corporation # # 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 tarfile import tempfile from tempest.api.object_storage import base from tempest.common import custom_matchers from tempest import test class BulkTest(base.BaseObjectTest): def setUp(self): super(BulkTest, self).setUp() self.containers = [] def tearDown(self): self.delete_containers(self.containers) super(BulkTest, self).tearDown() def _create_archive(self): # Create an archived file for bulk upload testing. # Directory and files contained in the directory correspond to # container and subsidiary objects. tmp_dir = tempfile.mkdtemp() tmp_file = tempfile.mkstemp(dir=tmp_dir) # Extract a container name and an object name container_name = tmp_dir.split("/")[-1] object_name = tmp_file[1].split("/")[-1] # Create tar file tarpath = tempfile.NamedTemporaryFile(suffix=".tar") tar = tarfile.open(None, 'w', tarpath) tar.add(tmp_dir, arcname=container_name) tar.close() tarpath.flush() return tarpath.name, container_name, object_name @test.attr(type='gate') def test_extract_archive(self): # Test bulk operation of file upload with an archived file filepath, container_name, object_name = self._create_archive() params = {'extract-archive': 'tar'} with open(filepath) as fh: mydata = fh.read() resp, body = self.account_client.create_account(data=mydata, params=params) self.containers.append(container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # When uploading an archived file with the bulk operation, the response # does not contain 'content-length' header. This is the special case, # therefore the existence of response headers is checked without # custom matcher. self.assertIn('transfer-encoding', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) param = {'format': 'json'} resp, body = self.account_client.list_account_containers(param) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'GET') self.assertIn(container_name, [b['name'] for b in body]) param = {'format': 'json'} resp, contents_list = self.container_client.list_container_contents( container_name, param) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'GET') self.assertIn(object_name, [c['name'] for c in contents_list]) @test.attr(type='gate') def test_bulk_delete(self): # Test bulk operation of deleting multiple files filepath, container_name, object_name = self._create_archive() params = {'extract-archive': 'tar'} with open(filepath) as fh: mydata = fh.read() resp, body = self.account_client.create_account(data=mydata, params=params) data = '%s/%s\n%s' % (container_name, object_name, container_name) params = {'bulk-delete': ''} resp, body = self.account_client.delete_account(data=data, params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # When deleting multiple files using the bulk operation, the response # does not contain 'content-length' header. This is the special case, # therefore the existence of response headers is checked without # custom matcher. self.assertIn('transfer-encoding', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) # Check if a container is deleted param = {'format': 'txt'} resp, body = self.account_client.list_account_containers(param) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'GET') self.assertNotIn(container_name, body) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_crossdomain.py0000664000175000017500000000553312332757070027261 0ustar chuckchuck00000000000000# Copyright (C) 2013 eNovance SAS # # Author: Joe H. Rahme # # 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 tempest.api.object_storage import base from tempest import clients from tempest.common import custom_matchers from tempest import test class CrossdomainTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(CrossdomainTest, cls).setUpClass() # creates a test user. The test user will set its base_url to the Swift # endpoint and test the healthcheck feature. cls.data.setup_test_user() cls.os_test_user = clients.Manager(cls.data.test_credentials) cls.xml_start = '\n' \ '\n\n' cls.xml_end = "" @classmethod def tearDownClass(cls): cls.data.teardown_all() super(CrossdomainTest, cls).tearDownClass() def setUp(self): super(CrossdomainTest, self).setUp() client = self.os_test_user.account_client # Turning http://.../v1/foobar into http://.../ client.skip_path() def tearDown(self): # clear the base_url for subsequent requests self.os_test_user.account_client.reset_path() super(CrossdomainTest, self).tearDown() @test.attr('gate') @test.requires_ext(extension='crossdomain', service='object') def test_get_crossdomain_policy(self): resp, body = self.os_test_user.account_client.get("crossdomain.xml", {}) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertTrue(body.startswith(self.xml_start) and body.endswith(self.xml_end)) # The target of the request is not any Swift resource. Therefore, the # existence of response header is checked without a custom matcher. self.assertIn('content-length', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_object_services.py0000664000175000017500000010507012332757070030106 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import hashlib import random import re from six import moves import time from tempest.api.object_storage import base from tempest.common import custom_matchers from tempest.common.utils import data_utils from tempest import test class ObjectTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ObjectTest, cls).setUpClass() cls.container_name = data_utils.rand_name(name='TestContainer') cls.container_client.create_container(cls.container_name) cls.containers = [cls.container_name] @classmethod def tearDownClass(cls): cls.delete_containers(cls.containers) super(ObjectTest, cls).tearDownClass() def _create_object(self, metadata=None): # setup object object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() self.object_client.create_object(self.container_name, object_name, data, metadata=metadata) return object_name, data def _upload_segments(self): # create object object_name = data_utils.rand_name(name='LObject') data = data_utils.arbitrary_string() segments = 10 data_segments = [data + str(i) for i in moves.xrange(segments)] # uploading segments for i in moves.xrange(segments): resp, _ = self.object_client.create_object_segments( self.container_name, object_name, i, data_segments[i]) self.assertEqual(resp['status'], '201') return object_name, data_segments @test.attr(type='smoke') def test_create_object(self): # create object object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # create another object object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') @test.attr(type='smoke') def test_delete_object(self): # create object object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # delete object resp, _ = self.object_client.delete_object(self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'DELETE') @test.attr(type='smoke') def test_update_object_metadata(self): # update object metadata object_name, data = self._create_object() metadata = {'X-Object-Meta-test-meta': 'Meta'} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, metadata, metadata_prefix='') self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], 'Meta') def test_update_object_metadata_with_remove_metadata(self): # update object metadata with remove metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) update_metadata = {'X-Remove-Object-Meta-test-meta1': 'Meta1'} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, update_metadata, metadata_prefix='') self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta1', resp) @test.attr(type='smoke') def test_update_object_metadata_with_create_and_remove_metadata(self): # creation and deletion of metadata with one request object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) update_metadata = {'X-Object-Meta-test-meta2': 'Meta2', 'X-Remove-Object-Meta-test-meta1': 'Meta1'} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, update_metadata, metadata_prefix='') self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta1', resp) self.assertIn('x-object-meta-test-meta2', resp) self.assertEqual(resp['x-object-meta-test-meta2'], 'Meta2') @test.attr(type='smoke') def test_update_object_metadata_with_x_object_manifest(self): # update object metadata with x_object_manifest # uploading segments object_name, data_segments = self._upload_segments() # creating a manifest file data_empty = '' self.object_client.create_object(self.container_name, object_name, data_empty, metadata=None) object_prefix = '%s/%s' % (self.container_name, object_name) update_metadata = {'X-Object-Manifest': object_prefix} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, update_metadata, metadata_prefix='') self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn('x-object-manifest', resp) self.assertNotEqual(len(resp['x-object-manifest']), 0) def test_update_object_metadata_with_x_object_metakey(self): # update object metadata with a blenk value of metadata object_name, data = self._create_object() update_metadata = {'X-Object-Meta-test-meta': ''} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, update_metadata, metadata_prefix='') self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], '') @test.attr(type='smoke') def test_update_object_metadata_with_x_remove_object_metakey(self): # update object metadata with a blank value of remove metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() create_metadata = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) update_metadata = {'X-Remove-Object-Meta-test-meta': ''} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, update_metadata, metadata_prefix='') self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertNotIn('x-object-meta-test-meta', resp) @test.attr(type='smoke') def test_list_object_metadata(self): # get object metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() metadata = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=metadata) resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'HEAD') self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], 'Meta') @test.attr(type='smoke') def test_list_no_object_metadata(self): # get empty list of object metadata object_name, data = self._create_object() resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'HEAD') self.assertNotIn('x-object-meta-', str(resp)) @test.attr(type='smoke') def test_list_object_metadata_with_x_object_manifest(self): # get object metadata with x_object_manifest # uploading segments object_name, data_segments = self._upload_segments() # creating a manifest file object_prefix = '%s/%s' % (self.container_name, object_name) metadata = {'X-Object-Manifest': object_prefix} data_empty = '' resp, _ = self.object_client.create_object( self.container_name, object_name, data_empty, metadata=metadata) resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # Check only the existence of common headers with custom matcher self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders( 'Object', 'HEAD')) self.assertIn('x-object-manifest', resp) # Etag value of a large object is enclosed in double-quotations. # This is a special case, therefore the formats of response headers # are checked without a custom matcher. self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) self.assertTrue(resp['etag'].strip('\"').isalnum()) self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp'])) self.assertNotEqual(len(resp['content-type']), 0) self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$", resp['x-trans-id'])) self.assertNotEqual(len(resp['date']), 0) self.assertEqual(resp['accept-ranges'], 'bytes') self.assertEqual(resp['x-object-manifest'], '%s/%s' % (self.container_name, object_name)) @test.attr(type='smoke') def test_get_object(self): # retrieve object's data (in response body) # create object object_name, data = self._create_object() # get object resp, body = self.object_client.get_object(self.container_name, object_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @test.attr(type='smoke') def test_get_object_with_metadata(self): # get object with metadata object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() metadata = {'X-Object-Meta-test-meta': 'Meta'} self.object_client.create_object(self.container_name, object_name, data, metadata=metadata) resp, body = self.object_client.get_object( self.container_name, object_name, metadata=None) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertIn('x-object-meta-test-meta', resp) self.assertEqual(resp['x-object-meta-test-meta'], 'Meta') self.assertEqual(body, data) @test.attr(type='smoke') def test_get_object_with_range(self): # get object with range object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string(100) self.object_client.create_object(self.container_name, object_name, data, metadata=None) rand_num = random.randint(3, len(data) - 1) metadata = {'Range': 'bytes=%s-%s' % (rand_num - 3, rand_num - 1)} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data[rand_num - 3: rand_num]) @test.attr(type='smoke') def test_get_object_with_x_object_manifest(self): # get object with x_object_manifest # uploading segments object_name, data_segments = self._upload_segments() # creating a manifest file object_prefix = '%s/%s' % (self.container_name, object_name) metadata = {'X-Object-Manifest': object_prefix} data_empty = '' resp, body = self.object_client.create_object( self.container_name, object_name, data_empty, metadata=metadata) resp, body = self.object_client.get_object( self.container_name, object_name, metadata=None) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # Check only the existence of common headers with custom matcher self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders( 'Object', 'GET')) self.assertIn('x-object-manifest', resp) # Etag value of a large object is enclosed in double-quotations. # This is a special case, therefore the formats of response headers # are checked without a custom matcher. self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) self.assertTrue(resp['etag'].strip('\"').isalnum()) self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp'])) self.assertNotEqual(len(resp['content-type']), 0) self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$", resp['x-trans-id'])) self.assertNotEqual(len(resp['date']), 0) self.assertEqual(resp['accept-ranges'], 'bytes') self.assertEqual(resp['x-object-manifest'], '%s/%s' % (self.container_name, object_name)) self.assertEqual(''.join(data_segments), body) @test.attr(type='smoke') def test_get_object_with_if_match(self): # get object with if_match object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string(10) create_md5 = hashlib.md5(data).hexdigest() create_metadata = {'Etag': create_md5} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) list_metadata = {'If-Match': create_md5} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @test.attr(type='smoke') def test_get_object_with_if_modified_since(self): # get object with if_modified_since object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() time_now = time.time() self.object_client.create_object(self.container_name, object_name, data, metadata=None) http_date = time.ctime(time_now - 86400) list_metadata = {'If-Modified-Since': http_date} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) def test_get_object_with_if_none_match(self): # get object with if_none_match object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string(10) create_md5 = hashlib.md5(data).hexdigest() create_metadata = {'Etag': create_md5} self.object_client.create_object(self.container_name, object_name, data, metadata=create_metadata) list_data = data_utils.arbitrary_string(15) list_md5 = hashlib.md5(list_data).hexdigest() list_metadata = {'If-None-Match': list_md5} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @test.attr(type='smoke') def test_get_object_with_if_unmodified_since(self): # get object with if_unmodified_since object_name, data = self._create_object() time_now = time.time() http_date = time.ctime(time_now + 86400) list_metadata = {'If-Unmodified-Since': http_date} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @test.attr(type='smoke') def test_get_object_with_x_newest(self): # get object with x_newest object_name, data = self._create_object() list_metadata = {'X-Newest': 'true'} resp, body = self.object_client.get_object( self.container_name, object_name, metadata=list_metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @test.attr(type='smoke') def test_copy_object_in_same_container(self): # create source object src_object_name = data_utils.rand_name(name='SrcObject') src_data = data_utils.arbitrary_string(size=len(src_object_name) * 2, base_text=src_object_name) resp, _ = self.object_client.create_object(self.container_name, src_object_name, src_data) # create destination object dst_object_name = data_utils.rand_name(name='DstObject') dst_data = data_utils.arbitrary_string(size=len(dst_object_name) * 3, base_text=dst_object_name) resp, _ = self.object_client.create_object(self.container_name, dst_object_name, dst_data) # copy source object to destination resp, _ = self.object_client.copy_object_in_same_container( self.container_name, src_object_name, dst_object_name) self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # check data resp, body = self.object_client.get_object(self.container_name, dst_object_name) self.assertEqual(body, src_data) @test.attr(type='smoke') def test_copy_object_to_itself(self): # change the content type of an existing object # create object object_name = data_utils.rand_name(name='TestObject') data = data_utils.arbitrary_string() self.object_client.create_object(self.container_name, object_name, data) # get the old content type resp_tmp, _ = self.object_client.list_object_metadata( self.container_name, object_name) # change the content type of the object metadata = {'content-type': 'text/plain; charset=UTF-8'} self.assertNotEqual(resp_tmp['content-type'], metadata['content-type']) resp, _ = self.object_client.copy_object_in_same_container( self.container_name, object_name, object_name, metadata) self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # check the content type resp, _ = self.object_client.list_object_metadata(self.container_name, object_name) self.assertEqual(resp['content-type'], metadata['content-type']) @test.attr(type='smoke') def test_copy_object_2d_way(self): # create source object src_object_name = data_utils.rand_name(name='SrcObject') src_data = data_utils.arbitrary_string(size=len(src_object_name) * 2, base_text=src_object_name) resp, _ = self.object_client.create_object(self.container_name, src_object_name, src_data) # create destination object dst_object_name = data_utils.rand_name(name='DstObject') dst_data = data_utils.arbitrary_string(size=len(dst_object_name) * 3, base_text=dst_object_name) resp, _ = self.object_client.create_object(self.container_name, dst_object_name, dst_data) # copy source object to destination resp, _ = self.object_client.copy_object_2d_way(self.container_name, src_object_name, dst_object_name) self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'COPY') self.assertIn('last-modified', resp) self.assertIn('x-copied-from', resp) self.assertIn('x-copied-from-last-modified', resp) self.assertNotEqual(len(resp['last-modified']), 0) self.assertEqual( resp['x-copied-from'], self.container_name + "/" + src_object_name) self.assertNotEqual(len(resp['x-copied-from-last-modified']), 0) # check data resp, body = self.object_client.get_object(self.container_name, dst_object_name) self.assertEqual(body, src_data) @test.attr(type='smoke') def test_copy_object_across_containers(self): # create a container to use as asource container src_container_name = data_utils.rand_name(name='TestSourceContainer') self.container_client.create_container(src_container_name) self.containers.append(src_container_name) # create a container to use as a destination container dst_container_name = data_utils.rand_name( name='TestDestinationContainer') self.container_client.create_container(dst_container_name) self.containers.append(dst_container_name) # create object in source container object_name = data_utils.rand_name(name='Object') data = data_utils.arbitrary_string(size=len(object_name) * 2, base_text=object_name) resp, _ = self.object_client.create_object(src_container_name, object_name, data) # set object metadata meta_key = data_utils.rand_name(name='test-') meta_value = data_utils.rand_name(name='MetaValue-') orig_metadata = {meta_key: meta_value} resp, _ = self.object_client.update_object_metadata(src_container_name, object_name, orig_metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'POST') # copy object from source container to destination container resp, _ = self.object_client.copy_object_across_containers( src_container_name, object_name, dst_container_name, object_name) self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # check if object is present in destination container resp, body = self.object_client.get_object(dst_container_name, object_name) self.assertEqual(body, data) actual_meta_key = 'x-object-meta-' + meta_key self.assertIn(actual_meta_key, resp) self.assertEqual(resp[actual_meta_key], meta_value) @test.attr(type='gate') def test_object_upload_in_segments(self): # create object object_name = data_utils.rand_name(name='LObject') data = data_utils.arbitrary_string() segments = 10 data_segments = [data + str(i) for i in moves.xrange(segments)] # uploading segments for i in moves.xrange(segments): resp, _ = self.object_client.create_object_segments( self.container_name, object_name, i, data_segments[i]) self.assertEqual(resp['status'], '201') # creating a manifest file metadata = {'X-Object-Manifest': '%s/%s/' % (self.container_name, object_name)} resp, _ = self.object_client.create_object(self.container_name, object_name, data='') self.assertHeaders(resp, 'Object', 'PUT') resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, metadata, metadata_prefix='') self.assertHeaders(resp, 'Object', 'POST') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) # Etag value of a large object is enclosed in double-quotations. # After etag quotes are checked they are removed and the response is # checked if all common headers are present and well formatted self.assertTrue(resp['etag'].startswith('\"')) self.assertTrue(resp['etag'].endswith('\"')) resp['etag'] = resp['etag'].strip('"') self.assertHeaders(resp, 'Object', 'HEAD') self.assertIn('x-object-manifest', resp) self.assertEqual(resp['x-object-manifest'], '%s/%s/' % (self.container_name, object_name)) # downloading the object resp, body = self.object_client.get_object( self.container_name, object_name) self.assertEqual(''.join(data_segments), body) @test.attr(type='gate') def test_get_object_if_different(self): # http://en.wikipedia.org/wiki/HTTP_ETag # Make a conditional request for an object using the If-None-Match # header, it should get downloaded only if the local file is different, # otherwise the response code should be 304 Not Modified object_name, data = self._create_object() # local copy is identical, no download md5 = hashlib.md5(data).hexdigest() headers = {'If-None-Match': md5} url = "%s/%s" % (self.container_name, object_name) resp, _ = self.object_client.get(url, headers=headers) self.assertEqual(resp['status'], '304') # When the file is not downloaded from Swift server, response does # not contain 'X-Timestamp' header. This is the special case, therefore # the existence of response headers is checked without custom matcher. self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) self.assertIn('accept-ranges', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) # local copy is different, download local_data = "something different" md5 = hashlib.md5(local_data).hexdigest() headers = {'If-None-Match': md5} resp, body = self.object_client.get(url, headers=headers) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Object', 'GET') class PublicObjectTest(base.BaseObjectTest): def setUp(self): super(PublicObjectTest, self).setUp() self.container_name = data_utils.rand_name(name='TestContainer') self.container_client.create_container(self.container_name) def tearDown(self): self.delete_containers([self.container_name]) super(PublicObjectTest, self).tearDown() @test.attr(type='smoke') def test_access_public_container_object_without_using_creds(self): # make container public-readable and access an object in it object # anonymously, without using credentials # update container metadata to make it publicly readable cont_headers = {'X-Container-Read': '.r:*,.rlistings'} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') data = data_utils.arbitrary_string(size=len(object_name), base_text=object_name) resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # list container metadata resp_meta, _ = self.container_client.list_container_metadata( self.container_name) self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'HEAD') self.assertIn('x-container-read', resp_meta) self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings') # trying to get object with empty headers as it is public readable self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=None ) resp, body = self.custom_object_client.get_object( self.container_name, object_name) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) @test.attr(type='smoke') def test_access_public_object_with_another_user_creds(self): # make container public-readable and access an object in it using # another user's credentials cont_headers = {'X-Container-Read': '.r:*,.rlistings'} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertIn(int(resp_meta['status']), test.HTTP_SUCCESS) self.assertHeaders(resp_meta, 'Container', 'POST') # create object object_name = data_utils.rand_name(name='Object') data = data_utils.arbitrary_string(size=len(object_name) * 1, base_text=object_name) resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertEqual(resp['status'], '201') self.assertHeaders(resp, 'Object', 'PUT') # list container metadata resp, _ = self.container_client.list_container_metadata( self.container_name) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Container', 'HEAD') self.assertIn('x-container-read', resp) self.assertEqual(resp['x-container-read'], '.r:*,.rlistings') # get auth token of alternative user alt_auth_data = self.identity_client_alt.auth_provider.auth_data self.custom_object_client.auth_provider.set_alt_auth_data( request_part='headers', auth_data=alt_auth_data ) # access object using alternate user creds resp, body = self.custom_object_client.get_object( self.container_name, object_name) self.assertHeaders(resp, 'Object', 'GET') self.assertEqual(body, data) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/base.py0000664000175000017500000001337512332757070024616 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest import clients from tempest.common import custom_matchers from tempest.common import isolated_creds from tempest import config from tempest import exceptions import tempest.test CONF = config.CONF class BaseObjectTest(tempest.test.BaseTestCase): @classmethod def setUpClass(cls): cls.set_network_resources() super(BaseObjectTest, cls).setUpClass() if not CONF.service_available.swift: skip_msg = ("%s skipped as swift is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.isolated_creds = isolated_creds.IsolatedCreds( cls.__name__, network_resources=cls.network_resources) if CONF.compute.allow_tenant_isolation: # Get isolated creds for normal user cls.os = clients.Manager(cls.isolated_creds.get_primary_creds()) # Get isolated creds for admin user cls.os_admin = clients.Manager( cls.isolated_creds.get_admin_creds()) # Get isolated creds for alt user cls.os_alt = clients.Manager(cls.isolated_creds.get_alt_creds()) # Add isolated users to operator role so that they can create a # container in swift. cls._assign_member_role() else: cls.os = clients.Manager() cls.os_admin = clients.AdminManager() cls.os_alt = clients.AltManager() cls.object_client = cls.os.object_client cls.container_client = cls.os.container_client cls.account_client = cls.os.account_client cls.custom_object_client = cls.os.custom_object_client cls.token_client = cls.os_admin.token_client cls.identity_admin_client = cls.os_admin.identity_client cls.custom_account_client = cls.os.custom_account_client cls.object_client_alt = cls.os_alt.object_client cls.container_client_alt = cls.os_alt.container_client cls.identity_client_alt = cls.os_alt.identity_client # Make sure we get fresh auth data after assigning swift role cls.object_client.auth_provider.clear_auth() cls.container_client.auth_provider.clear_auth() cls.account_client.auth_provider.clear_auth() cls.custom_object_client.auth_provider.clear_auth() cls.custom_account_client.auth_provider.clear_auth() cls.object_client_alt.auth_provider.clear_auth() cls.container_client_alt.auth_provider.clear_auth() cls.data = base.DataGenerator(cls.identity_admin_client) @classmethod def tearDownClass(cls): cls.isolated_creds.clear_isolated_creds() super(BaseObjectTest, cls).tearDownClass() @classmethod def _assign_member_role(cls): primary_creds = cls.isolated_creds.get_primary_creds() alt_creds = cls.isolated_creds.get_alt_creds() swift_role = CONF.object_storage.operator_role try: resp, roles = cls.os_admin.identity_client.list_roles() role = next(r for r in roles if r['name'] == swift_role) except StopIteration: msg = "No role named %s found" % swift_role raise exceptions.NotFound(msg) for creds in [primary_creds, alt_creds]: cls.os_admin.identity_client.assign_user_role(creds.tenant_id, creds.user_id, role['id']) @classmethod def delete_containers(cls, containers, container_client=None, object_client=None): """Remove given containers and all objects in them. The containers should be visible from the container_client given. Will not throw any error if the containers don't exist. Will not check that object and container deletions succeed. :param containers: list of container names to remove :param container_client: if None, use cls.container_client, this means that the default testing user will be used (see 'username' in 'etc/tempest.conf') :param object_client: if None, use cls.object_client """ if container_client is None: container_client = cls.container_client if object_client is None: object_client = cls.object_client for cont in containers: try: objlist = container_client.list_all_container_objects(cont) # delete every object in the container for obj in objlist: try: object_client.delete_object(cont, obj['name']) except exceptions.NotFound: pass container_client.delete_container(cont) except exceptions.NotFound: pass def assertHeaders(self, resp, target, method): """ Common method to check the existence and the format of common response headers """ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders( target, method)) self.assertThat(resp, custom_matchers.AreAllWellFormatted()) tempest-2014.1.dev4108.gf22b6cc/tempest/api/object_storage/test_account_services.py0000664000175000017500000003610112332757070030272 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import random from six import moves from tempest.api.object_storage import base from tempest import clients from tempest.common import custom_matchers from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class AccountTest(base.BaseObjectTest): containers = [] @classmethod @test.safe_setup def setUpClass(cls): super(AccountTest, cls).setUpClass() for i in moves.xrange(ord('a'), ord('f') + 1): name = data_utils.rand_name(name='%s-' % chr(i)) cls.container_client.create_container(name) cls.containers.append(name) cls.containers_count = len(cls.containers) @classmethod def tearDownClass(cls): cls.delete_containers(cls.containers) cls.data.teardown_all() super(AccountTest, cls).tearDownClass() @test.attr(type='smoke') def test_list_containers(self): # list of all containers should not be empty resp, container_list = self.account_client.list_account_containers() self.assertHeaders(resp, 'Account', 'GET') self.assertIsNotNone(container_list) for container_name in self.containers: self.assertIn(container_name, container_list) @test.attr(type='smoke') def test_list_no_containers(self): # List request to empty account # To test listing no containers, create new user other than # the base user of this instance. self.data.setup_test_user() os_test_user = clients.Manager( self.data.test_credentials) # Retrieve the id of an operator role of object storage test_role_id = None swift_role = CONF.object_storage.operator_role try: _, roles = self.os_admin.identity_client.list_roles() test_role_id = next(r['id'] for r in roles if r['name'] == swift_role) except StopIteration: msg = "%s role found" % swift_role raise exceptions.NotFound(msg) # Retrieve the test_user id _, users = self.os_admin.identity_client.get_users() test_user_id = next(usr['id'] for usr in users if usr['name'] == self.data.test_user) # Retrieve the test_tenant id _, tenants = self.os_admin.identity_client.list_tenants() test_tenant_id = next(tnt['id'] for tnt in tenants if tnt['name'] == self.data.test_tenant) # Assign the newly created user the appropriate operator role self.os_admin.identity_client.assign_user_role( test_tenant_id, test_user_id, test_role_id) resp, container_list = \ os_test_user.account_client.list_account_containers() self.assertIn(int(resp['status']), test.HTTP_SUCCESS) # When sending a request to an account which has not received a PUT # container request, the response does not contain 'accept-ranges' # header. This is a special case, therefore the existence of response # headers is checked without custom matcher. self.assertIn('content-length', resp) self.assertIn('x-timestamp', resp) self.assertIn('x-account-bytes-used', resp) self.assertIn('x-account-container-count', resp) self.assertIn('x-account-object-count', resp) self.assertIn('content-type', resp) self.assertIn('x-trans-id', resp) self.assertIn('date', resp) # Check only the format of common headers with custom matcher self.assertThat(resp, custom_matchers.AreAllWellFormatted()) self.assertEqual(len(container_list), 0) @test.attr(type='smoke') def test_list_containers_with_format_json(self): # list containers setting format parameter to 'json' params = {'format': 'json'} resp, container_list = self.account_client.list_account_containers( params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'GET') self.assertIsNotNone(container_list) self.assertTrue([c['name'] for c in container_list]) self.assertTrue([c['count'] for c in container_list]) self.assertTrue([c['bytes'] for c in container_list]) @test.attr(type='smoke') def test_list_containers_with_format_xml(self): # list containers setting format parameter to 'xml' params = {'format': 'xml'} resp, container_list = self.account_client.list_account_containers( params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'GET') self.assertIsNotNone(container_list) self.assertEqual(container_list.tag, 'account') self.assertTrue('name' in container_list.keys()) self.assertEqual(container_list.find(".//container").tag, 'container') self.assertEqual(container_list.find(".//name").tag, 'name') self.assertEqual(container_list.find(".//count").tag, 'count') self.assertEqual(container_list.find(".//bytes").tag, 'bytes') @test.attr(type='smoke') def test_list_extensions(self): resp, extensions = self.account_client.list_extensions() self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertThat(resp, custom_matchers.AreAllWellFormatted()) @test.attr(type='smoke') def test_list_containers_with_limit(self): # list containers one of them, half of them then all of them for limit in (1, self.containers_count / 2, self.containers_count): params = {'limit': limit} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), limit) @test.attr(type='smoke') def test_list_containers_with_marker(self): # list containers using marker param # first expect to get 0 container as we specified last # the container as marker # second expect to get the bottom half of the containers params = {'marker': self.containers[-1]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), 0) params = {'marker': self.containers[self.containers_count / 2]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), self.containers_count / 2 - 1) @test.attr(type='smoke') def test_list_containers_with_end_marker(self): # list containers using end_marker param # first expect to get 0 container as we specified first container as # end_marker # second expect to get the top half of the containers params = {'end_marker': self.containers[0]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), 0) params = {'end_marker': self.containers[self.containers_count / 2]} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), self.containers_count / 2) @test.attr(type='smoke') def test_list_containers_with_marker_and_end_marker(self): # list containers combining marker and end_marker param params = {'marker': self.containers[0], 'end_marker': self.containers[self.containers_count - 1]} resp, container_list = self.account_client.list_account_containers( params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), self.containers_count - 2) @test.attr(type='smoke') def test_list_containers_with_limit_and_marker(self): # list containers combining marker and limit param # result are always limitated by the limit whatever the marker for marker in random.choice(self.containers): limit = random.randint(0, self.containers_count - 1) params = {'marker': marker, 'limit': limit} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertHeaders(resp, 'Account', 'GET') self.assertTrue(len(container_list) <= limit, str(container_list)) @test.attr(type='smoke') def test_list_containers_with_limit_and_end_marker(self): # list containers combining limit and end_marker param limit = random.randint(1, self.containers_count) params = {'limit': limit, 'end_marker': self.containers[self.containers_count / 2]} resp, container_list = self.account_client.list_account_containers( params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), min(limit, self.containers_count / 2)) @test.attr(type='smoke') def test_list_containers_with_limit_and_marker_and_end_marker(self): # list containers combining limit, marker and end_marker param limit = random.randint(1, self.containers_count) params = {'limit': limit, 'marker': self.containers[0], 'end_marker': self.containers[self.containers_count - 1]} resp, container_list = self.account_client.list_account_containers( params=params) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'GET') self.assertEqual(len(container_list), min(limit, self.containers_count - 2)) @test.attr(type='smoke') def test_list_account_metadata(self): # list all account metadata # set metadata to account metadata = {'test-account-meta1': 'Meta1', 'test-account-meta2': 'Meta2'} resp, _ = self.account_client.create_account_metadata(metadata) resp, _ = self.account_client.list_account_metadata() self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'HEAD') self.assertIn('x-account-meta-test-account-meta1', resp) self.assertIn('x-account-meta-test-account-meta2', resp) self.account_client.delete_account_metadata(metadata) @test.attr(type='smoke') def test_list_no_account_metadata(self): # list no account metadata resp, _ = self.account_client.list_account_metadata() self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'HEAD') self.assertNotIn('x-account-meta-', str(resp)) @test.attr(type='smoke') def test_update_account_metadata_with_create_metadata(self): # add metadata to account metadata = {'test-account-meta1': 'Meta1'} resp, _ = self.account_client.create_account_metadata(metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'POST') resp, body = self.account_client.list_account_metadata() self.assertIn('x-account-meta-test-account-meta1', resp) self.assertEqual(resp['x-account-meta-test-account-meta1'], metadata['test-account-meta1']) self.account_client.delete_account_metadata(metadata) @test.attr(type='smoke') def test_update_account_metadata_with_delete_matadata(self): # delete metadata from account metadata = {'test-account-meta1': 'Meta1'} self.account_client.create_account_metadata(metadata) resp, _ = self.account_client.delete_account_metadata(metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) @test.attr(type='smoke') def test_update_account_metadata_with_create_matadata_key(self): # if the value of metadata is not set, the metadata is not # registered at a server metadata = {'test-account-meta1': ''} resp, _ = self.account_client.create_account_metadata(metadata) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) @test.attr(type='smoke') def test_update_account_metadata_with_delete_matadata_key(self): # Although the value of metadata is not set, the feature of # deleting metadata is valid metadata_1 = {'test-account-meta1': 'Meta1'} self.account_client.create_account_metadata(metadata_1) metadata_2 = {'test-account-meta1': ''} resp, _ = self.account_client.delete_account_metadata(metadata_2) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) @test.attr(type='smoke') def test_update_account_metadata_with_create_and_delete_metadata(self): # Send a request adding and deleting metadata requests simultaneously metadata_1 = {'test-account-meta1': 'Meta1'} self.account_client.create_account_metadata(metadata_1) metadata_2 = {'test-account-meta2': 'Meta2'} resp, body = self.account_client.create_and_delete_account_metadata( metadata_2, metadata_1) self.assertIn(int(resp['status']), test.HTTP_SUCCESS) self.assertHeaders(resp, 'Account', 'POST') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-test-account-meta1', resp) self.assertIn('x-account-meta-test-account-meta2', resp) self.assertEqual(resp['x-account-meta-test-account-meta2'], metadata_2['test-account-meta2']) self.account_client.delete_account_metadata(metadata_2) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/0000775000175000017500000000000012332757136023216 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/0000775000175000017500000000000012332757136024506 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/0000775000175000017500000000000012332757136026504 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/swift_basic.yaml0000664000175000017500000000114612332757070031664 0ustar chuckchuck00000000000000heat_template_version: 2013-05-23 description: Template which creates a Swift container resource resources: SwiftContainerWebsite: deletion_policy: "Delete" type: OS::Swift::Container properties: X-Container-Read: ".r:*" X-Container-Meta: web-index: "index.html" web-error: "error.html" SwiftContainer: type: OS::Swift::Container outputs: WebsiteURL: description: "URL for website hosted on S3" value: { get_attr: [SwiftContainer, WebsiteURL] } DomainName: description: "Domain of Swift host" value: { get_attr: [SwiftContainer, DomainName] } tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/nova_keypair.json0000664000175000017500000000255312332757070032070 0ustar chuckchuck00000000000000{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template which create two key pairs.", "Parameters" : { "KeyPairName1": { "Type": "String", "Default": "testkey1" }, "KeyPairName2": { "Type": "String", "Default": "testkey2" } }, "Resources" : { "KeyPairSavePrivate": { "Type": "OS::Nova::KeyPair", "Properties": { "name" : { "Ref" : "KeyPairName1" }, "save_private_key": true } }, "KeyPairDontSavePrivate": { "Type": "OS::Nova::KeyPair", "Properties": { "name" : { "Ref" : "KeyPairName2" }, "save_private_key": false } } }, "Outputs": { "KeyPair_PublicKey": { "Description": "Public Key of generated keypair.", "Value": { "Fn::GetAtt" : ["KeyPairSavePrivate", "public_key"] } }, "KeyPair_PrivateKey": { "Description": "Private Key of generated keypair.", "Value": { "Fn::GetAtt" : ["KeyPairSavePrivate", "private_key"] } }, "KeyPairDontSavePrivate_PublicKey": { "Description": "Public Key of generated keypair.", "Value": { "Fn::GetAtt" : ["KeyPairDontSavePrivate", "public_key"] } }, "KeyPairDontSavePrivate_PrivateKey": { "Description": "Private Key of generated keypair.", "Value": { "Fn::GetAtt" : ["KeyPairDontSavePrivate", "private_key"] } } } } ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yamltempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retai0000664000175000017500000000104712332757070033541 0ustar chuckchuck00000000000000heat_template_version: 2013-05-23 resources: volume: deletion_policy: 'Retain' type: OS::Cinder::Volume properties: size: 1 description: a descriptive description outputs: status: description: status value: { get_attr: ['volume', 'status'] } size: description: size value: { get_attr: ['volume', 'size'] } display_description: description: display_description value: { get_attr: ['volume', 'display_description'] } volume_id: value: { get_resource: volume } tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml0000664000175000017500000000441212332757070032514 0ustar chuckchuck00000000000000HeatTemplateFormatVersion: '2012-12-12' Description: | Template which uses a wait condition to confirm that a minimal cfn-init and cfn-signal has worked Parameters: key_name: Type: String flavor: Type: String image: Type: String network: Type: String timeout: Type: Number Resources: CfnUser: Type: AWS::IAM::User SmokeSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Enable only ping and SSH access SecurityGroupIngress: - {CidrIp: 0.0.0.0/0, FromPort: '-1', IpProtocol: icmp, ToPort: '-1'} - {CidrIp: 0.0.0.0/0, FromPort: '22', IpProtocol: tcp, ToPort: '22'} SmokeKeys: Type: AWS::IAM::AccessKey Properties: UserName: {Ref: CfnUser} SmokeServer: Type: OS::Nova::Server Metadata: AWS::CloudFormation::Init: config: files: /tmp/smoke-status: content: smoke test complete /etc/cfn/cfn-credentials: content: Fn::Replace: - SmokeKeys: {Ref: SmokeKeys} SecretAccessKey: 'Fn::GetAtt': [SmokeKeys, SecretAccessKey] - | AWSAccessKeyId=SmokeKeys AWSSecretKey=SecretAccessKey mode: '000400' owner: root group: root Properties: image: {Ref: image} flavor: {Ref: flavor} key_name: {Ref: key_name} security_groups: - {Ref: SmokeSecurityGroup} networks: - uuid: {Ref: network} user_data: Fn::Replace: - WaitHandle: {Ref: WaitHandle} - | #!/bin/bash -v /opt/aws/bin/cfn-init /opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" \ "WaitHandle" WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: SmokeServer Properties: Handle: {Ref: WaitHandle} Timeout: {Ref: timeout} Outputs: WaitConditionStatus: Description: Contents of /tmp/smoke-status on SmokeServer Value: Fn::GetAtt: [WaitCondition, Data] SmokeServerIp: Description: IP address of server Value: Fn::GetAtt: [SmokeServer, first_address] tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/neutron_basic.yaml0000664000175000017500000000336312332757070032225 0ustar chuckchuck00000000000000heat_template_version: '2013-05-23' description: | Template which creates single EC2 instance parameters: KeyName: type: string InstanceType: type: string ImageId: type: string ExternalRouterId: type: string ExternalNetworkId: type: string timeout: type: number resources: Network: type: OS::Neutron::Net properties: name: NewNetwork Subnet: type: OS::Neutron::Subnet properties: network_id: {Ref: Network} name: NewSubnet ip_version: 4 cidr: 10.0.3.0/24 dns_nameservers: ["8.8.8.8"] allocation_pools: - {end: 10.0.3.150, start: 10.0.3.20} Router: type: OS::Neutron::Router properties: name: NewRouter admin_state_up: false external_gateway_info: network: {get_param: ExternalNetworkId} RouterInterface: type: OS::Neutron::RouterInterface properties: router_id: {get_param: ExternalRouterId} subnet_id: {get_resource: Subnet} Server: type: OS::Nova::Server metadata: Name: SmokeServerNeutron properties: image: {get_param: ImageId} flavor: {get_param: InstanceType} key_name: {get_param: KeyName} networks: - network: {get_resource: Network} user_data: str_replace: template: | #!/bin/bash -v /opt/aws/bin/cfn-signal -e 0 -r "SmokeServerNeutron created" \ 'wait_handle' params: wait_handle: {get_resource: WaitHandleNeutron} WaitHandleNeutron: type: AWS::CloudFormation::WaitConditionHandle WaitCondition: type: AWS::CloudFormation::WaitCondition depends_on: Server properties: Handle: {get_resource: WaitHandleNeutron} Timeout: {get_param: timeout} tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/nova_keypair.yaml0000664000175000017500000000205612332757070032057 0ustar chuckchuck00000000000000heat_template_version: 2013-05-23 description: > Template which creates two key pairs. parameters: KeyPairName1: type: string default: testkey KeyPairName2: type: string default: testkey2 resources: KeyPairSavePrivate: type: OS::Nova::KeyPair properties: name: { get_param: KeyPairName1 } save_private_key: true KeyPairDontSavePrivate: type: OS::Nova::KeyPair properties: name: { get_param: KeyPairName2 } save_private_key: false outputs: KeyPair_PublicKey: description: Public Key of generated keypair value: { get_attr: [KeyPairSavePrivate, public_key] } KeyPair_PrivateKey: description: Private Key of generated keypair value: { get_attr: [KeyPairSavePrivate, private_key] } KeyPairDontSavePrivate_PublicKey: description: Public Key of generated keypair value: { get_attr: [KeyPairDontSavePrivate, public_key] } KeyPairDontSavePrivate_PrivateKey: description: Private Key of generated keypair value: { get_attr: [KeyPairDontSavePrivate, private_key] } tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/cinder_basic.yaml0000664000175000017500000000100512332757070031766 0ustar chuckchuck00000000000000heat_template_version: 2013-05-23 resources: volume: type: OS::Cinder::Volume properties: size: 1 description: a descriptive description outputs: status: description: status value: { get_attr: ['volume', 'status'] } size: description: size value: { get_attr: ['volume', 'size'] } display_description: description: display_description value: { get_attr: ['volume', 'display_description'] } volume_id: value: { get_resource: volume } tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml0000664000175000017500000000122312332757070032560 0ustar chuckchuck00000000000000HeatTemplateFormatVersion: '2012-12-12' Description: | Template which creates some simple resources Parameters: trigger: Type: String Default: not_yet Resources: fluffy: Type: AWS::AutoScaling::LaunchConfiguration Metadata: kittens: - Tom - Stinky Properties: ImageId: not_used InstanceType: not_used UserData: Fn::Replace: - variable_a: {Ref: trigger} variable_b: bee - | A == variable_a B == variable_b Outputs: fluffy: Description: "fluffies irc nick" Value: Fn::Replace: - nick: {Ref: fluffy} - | #nick tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_swift_resources.py0000664000175000017500000000772412332757070031354 0ustar chuckchuck00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2013 eNovance SAS # # Author: Chmouel Boudjnah # # 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 tempest.api.orchestration import base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class SwiftResourcesTestJSON(base.BaseOrchestrationTest): @classmethod @test.safe_setup def setUpClass(cls): super(SwiftResourcesTestJSON, cls).setUpClass() cls.stack_name = data_utils.rand_name('heat') template = cls.load_template('swift_basic') os = clients.Manager() if not CONF.service_available.swift: raise cls.skipException("Swift support is required") cls.account_client = os.account_client cls.container_client = os.container_client # create the stack cls.stack_identifier = cls.create_stack( cls.stack_name, template) cls.stack_id = cls.stack_identifier.split('/')[1] cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE') cls.test_resources = {} _, resources = cls.client.list_resources(cls.stack_identifier) for resource in resources: cls.test_resources[resource['logical_resource_id']] = resource def test_created_resources(self): """Created stack should be in the list of existing stacks.""" resources = [('SwiftContainer', 'OS::Swift::Container'), ('SwiftContainerWebsite', 'OS::Swift::Container')] for resource_name, resource_type in resources: resource = self.test_resources.get(resource_name) self.assertIsInstance(resource, dict) self.assertEqual(resource_type, resource['resource_type']) self.assertEqual(resource_name, resource['logical_resource_id']) self.assertEqual('CREATE_COMPLETE', resource['resource_status']) def test_created_containers(self): params = {'format': 'json'} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertEqual('200', resp['status']) self.assertEqual(2, len(container_list)) for cont in container_list: self.assertTrue(cont['name'].startswith(self.stack_name)) def test_acl(self): acl_headers = ('x-container-meta-web-index', 'x-container-read') swcont = self.test_resources.get( 'SwiftContainer')['physical_resource_id'] swcont_website = self.test_resources.get( 'SwiftContainerWebsite')['physical_resource_id'] headers, _ = self.container_client.list_container_metadata(swcont) for h in acl_headers: self.assertNotIn(h, headers) headers, _ = self.container_client.list_container_metadata( swcont_website) for h in acl_headers: self.assertIn(h, headers) def test_metadata(self): metadatas = { "web-index": "index.html", "web-error": "error.html" } swcont_website = self.test_resources.get( 'SwiftContainerWebsite')['physical_resource_id'] headers, _ = self.container_client.list_container_metadata( swcont_website) for meta in metadatas: header_meta = "x-container-meta-%s" % meta self.assertIn(header_meta, headers) self.assertEqual(headers[header_meta], metadatas[meta]) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_stacks.py0000664000175000017500000000457212332757070027414 0ustar chuckchuck00000000000000# 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 tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest.openstack.common import log as logging from tempest.test import attr LOG = logging.getLogger(__name__) class StacksTestJSON(base.BaseOrchestrationTest): empty_template = "HeatTemplateFormatVersion: '2012-12-12'\n" @classmethod def setUpClass(cls): super(StacksTestJSON, cls).setUpClass() @attr(type='smoke') def test_stack_list_responds(self): resp, stacks = self.client.list_stacks() self.assertEqual('200', resp['status']) self.assertIsInstance(stacks, list) @attr(type='smoke') def test_stack_crud_no_resources(self): stack_name = data_utils.rand_name('heat') # create the stack stack_identifier = self.create_stack( stack_name, self.empty_template) stack_id = stack_identifier.split('/')[1] # wait for create complete (with no resources it should be instant) self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') # check for stack in list resp, stacks = self.client.list_stacks() list_ids = list([stack['id'] for stack in stacks]) self.assertIn(stack_id, list_ids) # fetch the stack resp, stack = self.client.get_stack(stack_identifier) self.assertEqual('CREATE_COMPLETE', stack['stack_status']) # fetch the stack by name resp, stack = self.client.get_stack(stack_name) self.assertEqual('CREATE_COMPLETE', stack['stack_status']) # fetch the stack by id resp, stack = self.client.get_stack(stack_id) self.assertEqual('CREATE_COMPLETE', stack['stack_status']) # delete the stack resp = self.client.delete_stack(stack_identifier) self.assertEqual('204', resp[0]['status']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_templates.py0000664000175000017500000000421212332757070030111 0ustar chuckchuck00000000000000# 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 tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest import test class TemplateYAMLTestJSON(base.BaseOrchestrationTest): template = """ HeatTemplateFormatVersion: '2012-12-12' Description: | Template which creates only a new user Resources: CfnUser: Type: AWS::IAM::User """ @classmethod @test.safe_setup def setUpClass(cls): super(TemplateYAMLTestJSON, cls).setUpClass() cls.stack_name = data_utils.rand_name('heat') cls.stack_identifier = cls.create_stack(cls.stack_name, cls.template) cls.client.wait_for_stack_status(cls.stack_identifier, 'CREATE_COMPLETE') cls.stack_id = cls.stack_identifier.split('/')[1] cls.parameters = {} @test.attr(type='gate') def test_show_template(self): """Getting template used to create the stack.""" resp, template = self.client.show_template(self.stack_identifier) self.assertEqual('200', resp['status']) @test.attr(type='gate') def test_validate_template(self): """Validating template passing it content.""" resp, parameters = self.client.validate_template(self.template, self.parameters) self.assertEqual('200', resp['status']) class TemplateAWSTestJSON(TemplateYAMLTestJSON): template = """ { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template which creates only a new user", "Resources" : { "CfnUser" : { "Type" : "AWS::IAM::User" } } } """ tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/__init__.py0000664000175000017500000000000012332757070026602 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_volumes.py0000664000175000017500000001030012332757070027600 0ustar chuckchuck00000000000000# 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 logging from tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class CinderResourcesTest(base.BaseOrchestrationTest): @classmethod def setUpClass(cls): super(CinderResourcesTest, cls).setUpClass() if not CONF.service_available.cinder: raise cls.skipException('Cinder support is required') def _cinder_verify(self, volume_id): self.assertIsNotNone(volume_id) resp, volume = self.volumes_client.get_volume(volume_id) self.assertEqual(200, resp.status) self.assertEqual('available', volume.get('status')) self.assertEqual(1, volume.get('size')) self.assertEqual('a descriptive description', volume.get('display_description')) def _outputs_verify(self, stack_identifier): self.assertEqual('available', self.get_stack_output(stack_identifier, 'status')) self.assertEqual('1', self.get_stack_output(stack_identifier, 'size')) self.assertEqual('a descriptive description', self.get_stack_output(stack_identifier, 'display_description')) @test.attr(type='gate') def test_cinder_volume_create_delete(self): """Create and delete a volume via OS::Cinder::Volume.""" stack_name = data_utils.rand_name('heat') template = self.load_template('cinder_basic') stack_identifier = self.create_stack(stack_name, template) self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') # Verify with cinder that the volume exists, with matching details volume_id = self.get_stack_output(stack_identifier, 'volume_id') self._cinder_verify(volume_id) # Verify the stack outputs are as expected self._outputs_verify(stack_identifier) # Delete the stack and ensure the volume is gone self.client.delete_stack(stack_identifier) self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE') self.assertRaises(exceptions.NotFound, self.volumes_client.get_volume, volume_id) def _cleanup_volume(self, volume_id): """Cleanup the volume direct with cinder.""" resp = self.volumes_client.delete_volume(volume_id) self.assertEqual(202, resp[0].status) self.volumes_client.wait_for_resource_deletion(volume_id) @test.attr(type='gate') def test_cinder_volume_create_delete_retain(self): """Ensure the 'Retain' deletion policy is respected.""" stack_name = data_utils.rand_name('heat') template = self.load_template('cinder_basic_delete_retain') stack_identifier = self.create_stack(stack_name, template) self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') # Verify with cinder that the volume exists, with matching details volume_id = self.get_stack_output(stack_identifier, 'volume_id') self.addCleanup(self._cleanup_volume, volume_id) self._cinder_verify(volume_id) # Verify the stack outputs are as expected self._outputs_verify(stack_identifier) # Delete the stack and ensure the volume is *not* gone self.client.delete_stack(stack_identifier) self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE') self._cinder_verify(volume_id) # Volume cleanup happens via addCleanup calling _cleanup_volume tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_non_empty_stack.py0000664000175000017500000001632212332757070031315 0ustar chuckchuck00000000000000# 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 logging from tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest.test import attr LOG = logging.getLogger(__name__) class StacksTestJSON(base.BaseOrchestrationTest): @classmethod def setUpClass(cls): super(StacksTestJSON, cls).setUpClass() cls.stack_name = data_utils.rand_name('heat') template = cls.load_template('non_empty_stack') # create the stack cls.stack_identifier = cls.create_stack( cls.stack_name, template, parameters={ 'trigger': 'start' }) cls.stack_id = cls.stack_identifier.split('/')[1] cls.resource_name = 'fluffy' cls.resource_type = 'AWS::AutoScaling::LaunchConfiguration' cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE') def _list_stacks(self, expected_num=None, **filter_kwargs): resp, stacks = self.client.list_stacks(params=filter_kwargs) self.assertEqual('200', resp['status']) self.assertIsInstance(stacks, list) if expected_num is not None: self.assertEqual(expected_num, len(stacks)) return stacks @attr(type='gate') def test_stack_list(self): """Created stack should be in the list of existing stacks.""" stacks = self._list_stacks() stacks_names = map(lambda stack: stack['stack_name'], stacks) self.assertIn(self.stack_name, stacks_names) @attr(type='gate') def test_stack_show(self): """Getting details about created stack should be possible.""" resp, stack = self.client.get_stack(self.stack_name) self.assertEqual('200', resp['status']) self.assertIsInstance(stack, dict) self.assert_fields_in_dict(stack, 'stack_name', 'id', 'links', 'parameters', 'outputs', 'disable_rollback', 'stack_status_reason', 'stack_status', 'creation_time', 'updated_time', 'capabilities', 'notification_topics', 'timeout_mins', 'template_description') self.assert_fields_in_dict(stack['parameters'], 'AWS::StackId', 'trigger', 'AWS::Region', 'AWS::StackName') self.assertEqual(True, stack['disable_rollback'], 'disable_rollback should default to True') self.assertEqual(self.stack_name, stack['stack_name']) self.assertEqual(self.stack_id, stack['id']) self.assertEqual('fluffy', stack['outputs'][0]['output_key']) @attr(type='gate') def test_suspend_resume_stack(self): """Suspend and resume a stack.""" resp, suspend_stack = self.client.suspend_stack(self.stack_identifier) self.assertEqual('200', resp['status']) self.client.wait_for_stack_status(self.stack_identifier, 'SUSPEND_COMPLETE') resp, resume_stack = self.client.resume_stack(self.stack_identifier) self.assertEqual('200', resp['status']) self.client.wait_for_stack_status(self.stack_identifier, 'RESUME_COMPLETE') @attr(type='gate') def test_list_resources(self): """Getting list of created resources for the stack should be possible. """ resources = self.list_resources(self.stack_identifier) self.assertEqual({self.resource_name: self.resource_type}, resources) @attr(type='gate') def test_show_resource(self): """Getting details about created resource should be possible.""" resp, resource = self.client.get_resource(self.stack_identifier, self.resource_name) self.assertIsInstance(resource, dict) self.assert_fields_in_dict(resource, 'resource_name', 'description', 'links', 'logical_resource_id', 'resource_status', 'updated_time', 'required_by', 'resource_status_reason', 'physical_resource_id', 'resource_type') self.assertEqual(self.resource_name, resource['logical_resource_id']) self.assertEqual(self.resource_type, resource['resource_type']) @attr(type='gate') def test_resource_metadata(self): """Getting metadata for created resources should be possible.""" resp, metadata = self.client.show_resource_metadata( self.stack_identifier, self.resource_name) self.assertEqual('200', resp['status']) self.assertIsInstance(metadata, dict) self.assertEqual(['Tom', 'Stinky'], metadata.get('kittens', None)) @attr(type='gate') def test_list_events(self): """Getting list of created events for the stack should be possible.""" resp, events = self.client.list_events(self.stack_identifier) self.assertEqual('200', resp['status']) self.assertIsInstance(events, list) for event in events: self.assert_fields_in_dict(event, 'logical_resource_id', 'id', 'resource_status_reason', 'resource_status', 'event_time') resource_statuses = map(lambda event: event['resource_status'], events) self.assertIn('CREATE_IN_PROGRESS', resource_statuses) self.assertIn('CREATE_COMPLETE', resource_statuses) @attr(type='gate') def test_show_event(self): """Getting details about an event should be possible.""" resp, events = self.client.list_resource_events(self.stack_identifier, self.resource_name) self.assertNotEqual([], events) events.sort(key=lambda event: event['event_time']) event_id = events[0]['id'] resp, event = self.client.show_event(self.stack_identifier, self.resource_name, event_id) self.assertEqual('200', resp['status']) self.assertIsInstance(event, dict) self.assert_fields_in_dict(event, 'resource_name', 'event_time', 'links', 'logical_resource_id', 'resource_status', 'resource_status_reason', 'physical_resource_id', 'id', 'resource_properties', 'resource_type') self.assertEqual(self.resource_name, event['resource_name']) self.assertEqual('state changed', event['resource_status_reason']) self.assertEqual(self.resource_name, event['logical_resource_id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_server_cfn_init.py0000664000175000017500000001166312332757070031302 0ustar chuckchuck00000000000000# 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 json import testtools from tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ServerCfnInitTestJSON(base.BaseOrchestrationTest): existing_keypair = CONF.orchestration.keypair_name is not None @classmethod @test.safe_setup def setUpClass(cls): super(ServerCfnInitTestJSON, cls).setUpClass() if not CONF.orchestration.image_ref: raise cls.skipException("No image available to test") template = cls.load_template('cfn_init_signal') stack_name = data_utils.rand_name('heat') if CONF.orchestration.keypair_name: keypair_name = CONF.orchestration.keypair_name else: cls.keypair = cls._create_keypair() keypair_name = cls.keypair['name'] # create the stack cls.stack_identifier = cls.create_stack( stack_name, template, parameters={ 'key_name': keypair_name, 'flavor': CONF.orchestration.instance_type, 'image': CONF.orchestration.image_ref, 'network': cls._get_default_network()['id'], 'timeout': CONF.orchestration.build_timeout }) @test.attr(type='slow') @testtools.skipIf(existing_keypair, 'Server ssh tests are disabled.') def test_can_log_into_created_server(self): sid = self.stack_identifier rid = 'SmokeServer' # wait for create to complete. self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE') resp, body = self.client.get_resource(sid, rid) self.assertEqual('CREATE_COMPLETE', body['resource_status']) # fetch the IP address from servers client, since we can't get it # from the stack until stack create is complete resp, server = self.servers_client.get_server( body['physical_resource_id']) # Check that the user can authenticate with the generated password linux_client = remote_client.RemoteClient(server, 'ec2-user', pkey=self.keypair[ 'private_key']) linux_client.validate_authentication() @test.attr(type='slow') def test_all_resources_created(self): sid = self.stack_identifier self.client.wait_for_resource_status( sid, 'WaitHandle', 'CREATE_COMPLETE') self.client.wait_for_resource_status( sid, 'SmokeSecurityGroup', 'CREATE_COMPLETE') self.client.wait_for_resource_status( sid, 'SmokeKeys', 'CREATE_COMPLETE') self.client.wait_for_resource_status( sid, 'CfnUser', 'CREATE_COMPLETE') self.client.wait_for_resource_status( sid, 'SmokeServer', 'CREATE_COMPLETE') try: self.client.wait_for_resource_status( sid, 'WaitCondition', 'CREATE_COMPLETE') except (exceptions.StackResourceBuildErrorException, exceptions.TimeoutException) as e: # attempt to log the server console to help with debugging # the cause of the server not signalling the waitcondition # to heat. resp, body = self.client.get_resource(sid, 'SmokeServer') server_id = body['physical_resource_id'] LOG.debug('Console output for %s', server_id) resp, output = self.servers_client.get_console_output( server_id, None) LOG.debug(output) raise e # wait for create to complete. self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE') # This is an assert of great significance, as it means the following # has happened: # - cfn-init read the provided metadata and wrote out a file # - a user was created and credentials written to the server # - a cfn-signal was built which was signed with provided credentials # - the wait condition was fulfilled and the stack has changed state wait_status = json.loads( self.get_stack_output(sid, 'WaitConditionStatus')) self.assertEqual('smoke test complete', wait_status['00000']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_nova_keypair_resources.py0000664000175000017500000000707012332757070032701 0ustar chuckchuck00000000000000# 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 logging from tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest.test import attr LOG = logging.getLogger(__name__) class NovaKeyPairResourcesYAMLTest(base.BaseOrchestrationTest): _tpl_type = 'yaml' @classmethod def setUpClass(cls): super(NovaKeyPairResourcesYAMLTest, cls).setUpClass() cls.stack_name = data_utils.rand_name('heat') template = cls.load_template('nova_keypair', ext=cls._tpl_type) # create the stack, avoid any duplicated key. cls.stack_identifier = cls.create_stack( cls.stack_name, template, parameters={ 'KeyPairName1': cls.stack_name + '_1', 'KeyPairName2': cls.stack_name + '_2' }) cls.stack_id = cls.stack_identifier.split('/')[1] cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE') _, resources = cls.client.list_resources(cls.stack_identifier) cls.test_resources = {} for resource in resources: cls.test_resources[resource['logical_resource_id']] = resource @attr(type='slow') def test_created_resources(self): """Verifies created keypair resource.""" resources = [('KeyPairSavePrivate', 'OS::Nova::KeyPair'), ('KeyPairDontSavePrivate', 'OS::Nova::KeyPair')] for resource_name, resource_type in resources: resource = self.test_resources.get(resource_name, None) self.assertIsInstance(resource, dict) self.assertEqual(resource_name, resource['logical_resource_id']) self.assertEqual(resource_type, resource['resource_type']) self.assertEqual('CREATE_COMPLETE', resource['resource_status']) @attr(type='slow') def test_stack_keypairs_output(self): resp, stack = self.client.get_stack(self.stack_name) self.assertEqual('200', resp['status']) self.assertIsInstance(stack, dict) output_map = {} for outputs in stack['outputs']: output_map[outputs['output_key']] = outputs['output_value'] #Test that first key generated public and private keys self.assertTrue('KeyPair_PublicKey' in output_map) self.assertTrue("Generated" in output_map['KeyPair_PublicKey']) self.assertTrue('KeyPair_PrivateKey' in output_map) self.assertTrue('-----BEGIN' in output_map['KeyPair_PrivateKey']) #Test that second key generated public key, and private key is not #in the output due to save_private_key = false self.assertTrue('KeyPairDontSavePrivate_PublicKey' in output_map) self.assertTrue('Generated' in output_map['KeyPairDontSavePrivate_PublicKey']) self.assertTrue(u'KeyPairDontSavePrivate_PrivateKey' in output_map) private_key = output_map['KeyPairDontSavePrivate_PrivateKey'] self.assertTrue(len(private_key) == 0) class NovaKeyPairResourcesAWSTest(NovaKeyPairResourcesYAMLTest): _tpl_type = 'json' tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_templates_negative.py0000664000175000017500000000352612332757070032002 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.orchestration import base from tempest import exceptions from tempest import test class TemplateYAMLNegativeTestJSON(base.BaseOrchestrationTest): template = """ HeatTemplateFormatVersion: '2012-12-12' Description: | Template which creates only a new user Resources: CfnUser: Type: AWS::IAM::User """ invalid_template_url = 'http://www.example.com/template.yaml' @classmethod def setUpClass(cls): super(TemplateYAMLNegativeTestJSON, cls).setUpClass() cls.parameters = {} @test.attr(type=['gate', 'negative']) def test_validate_template_url(self): """Validating template passing url to it.""" self.assertRaises(exceptions.BadRequest, self.client.validate_template_url, template_url=self.invalid_template_url, parameters=self.parameters) class TemplateAWSNegativeTestJSON(TemplateYAMLNegativeTestJSON): template = """ { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template which creates only a new user", "Resources" : { "CfnUser" : { "Type" : "AWS::IAM::User" } } } """ invalid_template_url = 'http://www.example.com/template.template' tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_update.py0000664000175000017500000000624112332757070027401 0ustar chuckchuck00000000000000# 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 logging from tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest import test LOG = logging.getLogger(__name__) class UpdateStackTestJSON(base.BaseOrchestrationTest): _interface = 'json' template = ''' heat_template_version: 2013-05-23 resources: random1: type: OS::Heat::RandomString ''' update_template = ''' heat_template_version: 2013-05-23 resources: random1: type: OS::Heat::RandomString random2: type: OS::Heat::RandomString ''' def update_stack(self, stack_identifier, template): stack_name = stack_identifier.split('/')[0] resp = self.client.update_stack( stack_identifier=stack_identifier, name=stack_name, template=template) self.assertEqual('202', resp[0]['status']) self.client.wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE') @test.attr(type='gate') def test_stack_update_nochange(self): stack_name = data_utils.rand_name('heat') stack_identifier = self.create_stack(stack_name, self.template) self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') expected_resources = {'random1': 'OS::Heat::RandomString'} self.assertEqual(expected_resources, self.list_resources(stack_identifier)) # Update with no changes, resources should be unchanged self.update_stack(stack_identifier, self.template) self.assertEqual(expected_resources, self.list_resources(stack_identifier)) @test.attr(type='gate') @test.skip_because(bug='1308682') def test_stack_update_add_remove(self): stack_name = data_utils.rand_name('heat') stack_identifier = self.create_stack(stack_name, self.template) self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') initial_resources = {'random1': 'OS::Heat::RandomString'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) # Add one resource via a stack update self.update_stack(stack_identifier, self.update_template) updated_resources = {'random1': 'OS::Heat::RandomString', 'random2': 'OS::Heat::RandomString'} self.assertEqual(updated_resources, self.list_resources(stack_identifier)) # Then remove it by updating with the original template self.update_stack(stack_identifier, self.template) self.assertEqual(initial_resources, self.list_resources(stack_identifier)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_neutron_resources.py0000664000175000017500000001762012332757070031706 0ustar chuckchuck00000000000000# 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 logging from tempest.api.orchestration import base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class NeutronResourcesTestJSON(base.BaseOrchestrationTest): @classmethod @test.safe_setup def setUpClass(cls): super(NeutronResourcesTestJSON, cls).setUpClass() if not CONF.orchestration.image_ref: raise cls.skipException("No image available to test") os = clients.Manager() if not CONF.service_available.neutron: raise cls.skipException("Neutron support is required") cls.network_client = os.network_client cls.stack_name = data_utils.rand_name('heat') template = cls.load_template('neutron_basic') cls.keypair_name = (CONF.orchestration.keypair_name or cls._create_keypair()['name']) cls.external_router_id = cls._get_external_router_id() cls.external_network_id = CONF.network.public_network_id # create the stack cls.stack_identifier = cls.create_stack( cls.stack_name, template, parameters={ 'KeyName': cls.keypair_name, 'InstanceType': CONF.orchestration.instance_type, 'ImageId': CONF.orchestration.image_ref, 'ExternalRouterId': cls.external_router_id, 'ExternalNetworkId': cls.external_network_id, 'timeout': CONF.orchestration.build_timeout }) cls.stack_id = cls.stack_identifier.split('/')[1] try: cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE') _, resources = cls.client.list_resources(cls.stack_identifier) except exceptions.TimeoutException as e: # attempt to log the server console to help with debugging # the cause of the server not signalling the waitcondition # to heat. resp, body = cls.client.get_resource(cls.stack_identifier, 'Server') server_id = body['physical_resource_id'] LOG.debug('Console output for %s', server_id) resp, output = cls.servers_client.get_console_output( server_id, None) LOG.debug(output) raise e cls.test_resources = {} for resource in resources: cls.test_resources[resource['logical_resource_id']] = resource @classmethod def _get_external_router_id(cls): resp, body = cls.network_client.list_ports() ports = body['ports'] router_ports = filter(lambda port: port['device_owner'] == 'network:router_interface', ports) return router_ports[0]['device_id'] @test.attr(type='slow') def test_created_resources(self): """Verifies created neutron resources.""" resources = [('Network', 'OS::Neutron::Net'), ('Subnet', 'OS::Neutron::Subnet'), ('RouterInterface', 'OS::Neutron::RouterInterface'), ('Server', 'OS::Nova::Server')] for resource_name, resource_type in resources: resource = self.test_resources.get(resource_name, None) self.assertIsInstance(resource, dict) self.assertEqual(resource_name, resource['logical_resource_id']) self.assertEqual(resource_type, resource['resource_type']) self.assertEqual('CREATE_COMPLETE', resource['resource_status']) @test.attr(type='slow') def test_created_network(self): """Verifies created network.""" network_id = self.test_resources.get('Network')['physical_resource_id'] resp, body = self.network_client.show_network(network_id) self.assertEqual('200', resp['status']) network = body['network'] self.assertIsInstance(network, dict) self.assertEqual(network_id, network['id']) self.assertEqual('NewNetwork', network['name']) @test.attr(type='slow') def test_created_subnet(self): """Verifies created subnet.""" subnet_id = self.test_resources.get('Subnet')['physical_resource_id'] resp, body = self.network_client.show_subnet(subnet_id) self.assertEqual('200', resp['status']) subnet = body['subnet'] network_id = self.test_resources.get('Network')['physical_resource_id'] self.assertEqual(subnet_id, subnet['id']) self.assertEqual(network_id, subnet['network_id']) self.assertEqual('NewSubnet', subnet['name']) self.assertEqual('8.8.8.8', subnet['dns_nameservers'][0]) self.assertEqual('10.0.3.20', subnet['allocation_pools'][0]['start']) self.assertEqual('10.0.3.150', subnet['allocation_pools'][0]['end']) self.assertEqual(4, subnet['ip_version']) self.assertEqual('10.0.3.0/24', subnet['cidr']) @test.attr(type='slow') def test_created_router(self): """Verifies created router.""" router_id = self.test_resources.get('Router')['physical_resource_id'] resp, body = self.network_client.show_router(router_id) self.assertEqual('200', resp['status']) router = body['router'] self.assertEqual('NewRouter', router['name']) self.assertEqual(self.external_network_id, router['external_gateway_info']['network_id']) self.assertEqual(False, router['admin_state_up']) @test.attr(type='slow') def test_created_router_interface(self): """Verifies created router interface.""" network_id = self.test_resources.get('Network')['physical_resource_id'] subnet_id = self.test_resources.get('Subnet')['physical_resource_id'] resp, body = self.network_client.list_ports() self.assertEqual('200', resp['status']) ports = body['ports'] router_ports = filter(lambda port: port['device_id'] == self.external_router_id, ports) created_network_ports = filter(lambda port: port['network_id'] == network_id, router_ports) self.assertEqual(1, len(created_network_ports)) router_interface = created_network_ports[0] fixed_ips = router_interface['fixed_ips'] subnet_fixed_ips = filter(lambda port: port['subnet_id'] == subnet_id, fixed_ips) self.assertEqual(1, len(subnet_fixed_ips)) router_interface_ip = subnet_fixed_ips[0]['ip_address'] self.assertEqual('10.0.3.1', router_interface_ip) @test.attr(type='slow') def test_created_server(self): """Verifies created sever.""" server_id = self.test_resources.get('Server')['physical_resource_id'] resp, server = self.servers_client.get_server(server_id) self.assertEqual('200', resp['status']) self.assertEqual(self.keypair_name, server['key_name']) self.assertEqual('ACTIVE', server['status']) network = server['addresses']['NewNetwork'][0] self.assertEqual(4, network['version']) ip_addr_prefix = network['addr'][:7] ip_addr_suffix = int(network['addr'].split('.')[3]) self.assertEqual('10.0.3.', ip_addr_prefix) self.assertTrue(ip_addr_suffix >= 20) self.assertTrue(ip_addr_suffix <= 150) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/stacks/test_limits.py0000664000175000017500000000401412332757070027414 0ustar chuckchuck00000000000000# 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 logging from tempest.api.orchestration import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.test import attr CONF = config.CONF LOG = logging.getLogger(__name__) class TestServerStackLimits(base.BaseOrchestrationTest): @attr(type='gate') def test_exceed_max_template_size_fails(self): stack_name = data_utils.rand_name('heat') fill = 'A' * CONF.orchestration.max_template_size template = ''' HeatTemplateFormatVersion: '2012-12-12' Description: '%s' Outputs: Foo: bar''' % fill ex = self.assertRaises(exceptions.BadRequest, self.create_stack, stack_name, template) self.assertIn('Template exceeds maximum allowed size', str(ex)) @attr(type='gate') def test_exceed_max_resources_per_stack(self): stack_name = data_utils.rand_name('heat') # Create a big template, one resource more than the limit template = 'heat_template_version: \'2013-05-23\'\nresources:\n' rsrc_snippet = ' random%s:\n type: \'OS::Heat::RandomString\'\n' num_resources = CONF.orchestration.max_resources_per_stack + 1 for i in range(num_resources): template += rsrc_snippet % i ex = self.assertRaises(exceptions.BadRequest, self.create_stack, stack_name, template) self.assertIn('Maximum resources per stack exceeded', str(ex)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/__init__.py0000664000175000017500000000000012332757070025312 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/orchestration/base.py0000664000175000017500000001210112332757070024472 0ustar chuckchuck00000000000000# 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.path from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseOrchestrationTest(tempest.test.BaseTestCase): """Base test case class for all Orchestration API tests.""" @classmethod def setUpClass(cls): super(BaseOrchestrationTest, cls).setUpClass() cls.os = clients.Manager() if not CONF.service_available.heat: raise cls.skipException("Heat support is required") cls.build_timeout = CONF.orchestration.build_timeout cls.build_interval = CONF.orchestration.build_interval cls.orchestration_client = cls.os.orchestration_client cls.client = cls.orchestration_client cls.servers_client = cls.os.servers_client cls.keypairs_client = cls.os.keypairs_client cls.network_client = cls.os.network_client cls.volumes_client = cls.os.volumes_client cls.stacks = [] cls.keypairs = [] @classmethod def _get_default_network(cls): resp, networks = cls.network_client.list_networks() for net in networks['networks']: if net['name'] == CONF.compute.fixed_network_name: return net @classmethod def _get_identity_admin_client(cls): """Returns an instance of the Identity Admin API client.""" manager = clients.AdminManager(interface=cls._interface) admin_client = manager.identity_client return admin_client @classmethod def create_stack(cls, stack_name, template_data, parameters={}): resp, body = cls.client.create_stack( stack_name, template=template_data, parameters=parameters) stack_id = resp['location'].split('/')[-1] stack_identifier = '%s/%s' % (stack_name, stack_id) cls.stacks.append(stack_identifier) return stack_identifier @classmethod def _clear_stacks(cls): for stack_identifier in cls.stacks: try: cls.client.delete_stack(stack_identifier) except exceptions.NotFound: pass for stack_identifier in cls.stacks: try: cls.client.wait_for_stack_status( stack_identifier, 'DELETE_COMPLETE') except exceptions.NotFound: pass @classmethod def _create_keypair(cls, name_start='keypair-heat-'): kp_name = data_utils.rand_name(name_start) resp, body = cls.keypairs_client.create_keypair(kp_name) cls.keypairs.append(kp_name) return body @classmethod def _clear_keypairs(cls): for kp_name in cls.keypairs: try: cls.keypairs_client.delete_keypair(kp_name) except Exception: pass @classmethod def load_template(cls, name, ext='yaml'): loc = ["stacks", "templates", "%s.%s" % (name, ext)] fullpath = os.path.join(os.path.dirname(__file__), *loc) with open(fullpath, "r") as f: content = f.read() return content @classmethod def tearDownClass(cls): cls._clear_stacks() cls._clear_keypairs() super(BaseOrchestrationTest, cls).tearDownClass() @staticmethod def stack_output(stack, output_key): """Return a stack output value for a given key.""" return next((o['output_value'] for o in stack['outputs'] if o['output_key'] == output_key), None) def assert_fields_in_dict(self, obj, *fields): for field in fields: self.assertIn(field, obj) def list_resources(self, stack_identifier): """Get a dict mapping of resource names to types.""" resp, resources = self.client.list_resources(stack_identifier) self.assertEqual('200', resp['status']) self.assertIsInstance(resources, list) for res in resources: self.assert_fields_in_dict(res, 'logical_resource_id', 'resource_type', 'resource_status', 'updated_time') return dict((r['resource_name'], r['resource_type']) for r in resources) def get_stack_output(self, stack_identifier, output_key): resp, body = self.client.get_stack(stack_identifier) self.assertEqual('200', resp['status']) return self.stack_output(body, output_key) tempest-2014.1.dev4108.gf22b6cc/tempest/api/data_processing/0000775000175000017500000000000012332757136023477 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/data_processing/test_plugins.py0000664000175000017500000000413312332757070026567 0ustar chuckchuck00000000000000# Copyright (c) 2013 Mirantis 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 tempest.api.data_processing import base as dp_base from tempest.test import attr class PluginsTest(dp_base.BaseDataProcessingTest): def _list_all_plugin_names(self): """Returns all enabled plugin names. It ensures response status and main plugins availability. """ resp, plugins = self.client.list_plugins() self.assertEqual(200, resp.status) plugins_names = list([plugin['name'] for plugin in plugins]) self.assertIn('vanilla', plugins_names) self.assertIn('hdp', plugins_names) return plugins_names @attr(type='smoke') def test_plugin_list(self): self._list_all_plugin_names() @attr(type='smoke') def test_plugin_get(self): for plugin_name in self._list_all_plugin_names(): resp, plugin = self.client.get_plugin(plugin_name) self.assertEqual(200, resp.status) self.assertEqual(plugin_name, plugin['name']) for plugin_version in plugin['versions']: resp, detailed_plugin = self.client.get_plugin(plugin_name, plugin_version) self.assertEqual(200, resp.status) self.assertEqual(plugin_name, detailed_plugin['name']) # check that required image tags contains name and version image_tags = detailed_plugin['required_image_tags'] self.assertIn(plugin_name, image_tags) self.assertIn(plugin_version, image_tags) tempest-2014.1.dev4108.gf22b6cc/tempest/api/data_processing/test_node_group_templates.py0000664000175000017500000000742512332757070031334 0ustar chuckchuck00000000000000# Copyright (c) 2013 Mirantis 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 tempest.api.data_processing import base as dp_base from tempest.common.utils import data_utils from tempest.test import attr class NodeGroupTemplateTest(dp_base.BaseDataProcessingTest): @classmethod def setUpClass(cls): super(NodeGroupTemplateTest, cls).setUpClass() cls.node_group_template = { 'description': 'Test node group template', 'plugin_name': 'vanilla', 'hadoop_version': '1.2.1', 'node_processes': [ 'datanode', 'tasktracker' ], 'flavor_id': cls.flavor_ref, 'node_configs': { 'HDFS': { 'Data Node Heap Size': 1024 }, 'MapReduce': { 'Task Tracker Heap Size': 1024 } } } def _create_node_group_template(self, template_name=None): """Creates Node Group Template with optional name specified. It creates template and ensures response status and template name. Returns id and name of created template. """ if template_name is None: # generate random name if it's not specified template_name = data_utils.rand_name('sahara-ng-template') # create node group template resp, body = self.create_node_group_template( template_name, **self.node_group_template) # ensure that template created successfully self.assertEqual(202, resp.status) self.assertEqual(template_name, body['name']) return body['id'], template_name @attr(type='smoke') def test_node_group_template_create(self): template_name = data_utils.rand_name('sahara-ng-template') resp, body = self.create_node_group_template( template_name, **self.node_group_template) # check that template created successfully self.assertEqual(resp.status, 202) self.assertEqual(template_name, body['name']) self.assertDictContainsSubset(self.node_group_template, body) @attr(type='smoke') def test_node_group_template_list(self): template_info = self._create_node_group_template() # check for node group template in list resp, templates = self.client.list_node_group_templates() self.assertEqual(200, resp.status) templates_info = [(template['id'], template['name']) for template in templates] self.assertIn(template_info, templates_info) @attr(type='smoke') def test_node_group_template_get(self): template_id, template_name = self._create_node_group_template() # check node group template fetch by id resp, template = self.client.get_node_group_template(template_id) self.assertEqual(200, resp.status) self.assertEqual(template_name, template['name']) self.assertDictContainsSubset(self.node_group_template, template) @attr(type='smoke') def test_node_group_template_delete(self): template_id = self._create_node_group_template()[0] # delete the node group template by id resp = self.client.delete_node_group_template(template_id)[0] self.assertEqual(204, resp.status) tempest-2014.1.dev4108.gf22b6cc/tempest/api/data_processing/__init__.py0000664000175000017500000000000012332757070025573 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/data_processing/base.py0000664000175000017500000001103312332757070024756 0ustar chuckchuck00000000000000# Copyright (c) 2013 Mirantis 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 tempest import config from tempest import exceptions import tempest.test CONF = config.CONF class BaseDataProcessingTest(tempest.test.BaseTestCase): _interface = 'json' @classmethod def setUpClass(cls): super(BaseDataProcessingTest, cls).setUpClass() if not CONF.service_available.sahara: raise cls.skipException('Sahara support is required') os = cls.get_client_manager() cls.client = os.data_processing_client cls.flavor_ref = CONF.compute.flavor_ref # add lists for watched resources cls._node_group_templates = [] cls._cluster_templates = [] cls._data_sources = [] @classmethod def tearDownClass(cls): cls.cleanup_resources(getattr(cls, '_cluster_templates', []), cls.client.delete_cluster_template) cls.cleanup_resources(getattr(cls, '_node_group_templates', []), cls.client.delete_node_group_template) cls.cleanup_resources(getattr(cls, '_data_sources', []), cls.client.delete_data_source) cls.clear_isolated_creds() super(BaseDataProcessingTest, cls).tearDownClass() @staticmethod def cleanup_resources(resource_id_list, method): for resource_id in resource_id_list: try: method(resource_id) except exceptions.NotFound: # ignore errors while auto removing created resource pass @classmethod def create_node_group_template(cls, name, plugin_name, hadoop_version, node_processes, flavor_id, node_configs=None, **kwargs): """Creates watched node group template with specified params. It supports passing additional params using kwargs and returns created object. All resources created in this method will be automatically removed in tearDownClass method. """ resp, body = cls.client.create_node_group_template(name, plugin_name, hadoop_version, node_processes, flavor_id, node_configs, **kwargs) # store id of created node group template cls._node_group_templates.append(body['id']) return resp, body @classmethod def create_cluster_template(cls, name, plugin_name, hadoop_version, node_groups, cluster_configs=None, **kwargs): """Creates watched cluster template with specified params. It supports passing additional params using kwargs and returns created object. All resources created in this method will be automatically removed in tearDownClass method. """ resp, body = cls.client.create_cluster_template(name, plugin_name, hadoop_version, node_groups, cluster_configs, **kwargs) # store id of created cluster template cls._cluster_templates.append(body['id']) return resp, body @classmethod def create_data_source(cls, name, type, url, **kwargs): """Creates watched data source with specified params. It supports passing additional params using kwargs and returns created object. All resources created in this method will be automatically removed in tearDownClass method. """ resp, body = cls.client.create_data_source(name, type, url, **kwargs) # store id of created data source cls._data_sources.append(body['id']) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/0000775000175000017500000000000012332757136021414 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/__init__.py0000664000175000017500000000000012332757070023510 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/0000775000175000017500000000000012332757136021743 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/test_images.py0000664000175000017500000002366712332757070024634 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import cStringIO as StringIO import random from tempest.api.image import base from tempest.common.utils import data_utils from tempest import test class BasicOperationsImagesTest(base.BaseV2ImageTest): """ Here we test the basic operations of images """ @test.attr(type='gate') def test_register_upload_get_image_file(self): """ Here we test these functionalities - Register image, upload the image file, get image and get image file api's """ image_name = data_utils.rand_name('image') resp, body = self.create_image(name=image_name, container_format='bare', disk_format='raw', visibility='public') self.assertIn('id', body) image_id = body.get('id') self.assertIn('name', body) self.assertEqual(image_name, body['name']) self.assertIn('visibility', body) self.assertEqual('public', body['visibility']) self.assertIn('status', body) self.assertEqual('queued', body['status']) # Now try uploading an image file file_content = '*' * 1024 image_file = StringIO.StringIO(file_content) resp, body = self.client.store_image(image_id, image_file) self.assertEqual(resp.status, 204) # Now try to get image details resp, body = self.client.get_image(image_id) self.assertEqual(200, resp.status) self.assertEqual(image_id, body['id']) self.assertEqual(image_name, body['name']) self.assertIn('size', body) self.assertEqual(1024, body.get('size')) # Now try get image file resp, body = self.client.get_image_file(image_id) self.assertEqual(200, resp.status) self.assertEqual(file_content, body) @test.attr(type='gate') def test_delete_image(self): # Deletes an image by image_id # Create image image_name = data_utils.rand_name('image') resp, body = self.client.create_image(name=image_name, container_format='bare', disk_format='raw', visibility='public') self.assertEqual(201, resp.status) image_id = body['id'] # Delete Image self.client.delete_image(image_id) self.client.wait_for_resource_deletion(image_id) # Verifying deletion resp, images = self.client.image_list() self.assertEqual(resp.status, 200) self.assertNotIn(image_id, images) @test.attr(type='gate') def test_update_image(self): # Updates an image by image_id # Create image image_name = data_utils.rand_name('image') resp, body = self.client.create_image(name=image_name, container_format='bare', disk_format='iso', visibility='public') self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_image, body['id']) self.assertEqual('queued', body['status']) image_id = body['id'] # Now try uploading an image file file_content = '*' * 1024 image_file = StringIO.StringIO(file_content) resp, body = self.client.store_image(image_id, image_file) self.assertEqual(204, resp.status) # Update Image new_image_name = data_utils.rand_name('new-image') new_visibility = 'private' resp, body = self.client.update_image(image_id, [ dict(replace='/name', value=new_image_name), dict(replace='/visibility', value=new_visibility)]) self.assertEqual(200, resp.status) # Verifying updating resp, body = self.client.get_image(image_id) self.assertEqual(200, resp.status) self.assertEqual(image_id, body['id']) self.assertEqual(new_image_name, body['name']) self.assertEqual(new_visibility, body['visibility']) class ListImagesTest(base.BaseV2ImageTest): """ Here we test the listing of image information """ @classmethod @test.safe_setup def setUpClass(cls): super(ListImagesTest, cls).setUpClass() # We add a few images here to test the listing functionality of # the images API cls._create_standard_image('bare', 'raw') cls._create_standard_image('bare', 'raw') cls._create_standard_image('ami', 'raw') # Add some more for listing cls._create_standard_image('ami', 'ami') cls._create_standard_image('ari', 'ari') cls._create_standard_image('aki', 'aki') @classmethod def _create_standard_image(cls, container_format, disk_format): """ Create a new standard image and return the ID of the newly-registered image. Note that the size of the new image is a random number between 1024 and 4096 """ image_file = StringIO.StringIO('*' * random.randint(1024, 4096)) name = data_utils.rand_name('image-') resp, body = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, visibility='public') image_id = body['id'] resp, body = cls.client.store_image(image_id, data=image_file) return image_id def _list_by_param_value_and_assert(self, params): """ Perform list action with given params and validates result. """ resp, images_list = self.client.image_list(params=params) self.assertEqual(200, resp.status) # Validating params of fetched images for image in images_list: for key in params: msg = "Failed to list images by %s" % key self.assertEqual(params[key], image[key], msg) @test.attr(type='gate') def test_index_no_params(self): # Simple test to see all fixture images returned resp, images_list = self.client.image_list() self.assertEqual(resp['status'], '200') image_list = map(lambda x: x['id'], images_list) for image in self.created_images: self.assertIn(image, image_list) @test.attr(type='gate') def test_list_images_param_container_format(self): # Test to get all images with container_format='bare' params = {"container_format": "bare"} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_list_images_param_disk_format(self): # Test to get all images with disk_format = raw params = {"disk_format": "raw"} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_list_images_param_visibility(self): # Test to get all images with visibility = public params = {"visibility": "public"} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_list_images_param_size(self): # Test to get all images by size image_id = self.created_images[1] # Get image metadata resp, image = self.client.get_image(image_id) self.assertEqual(resp['status'], '200') params = {"size": image['size']} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_list_images_param_min_max_size(self): # Test to get all images with size between 2000 to 3000 image_id = self.created_images[1] # Get image metadata resp, image = self.client.get_image(image_id) self.assertEqual(resp['status'], '200') size = image['size'] params = {"size_min": size - 500, "size_max": size + 500} resp, images_list = self.client.image_list(params=params) self.assertEqual(resp['status'], '200') image_size_list = map(lambda x: x['size'], images_list) for image_size in image_size_list: self.assertTrue(image_size >= params['size_min'] and image_size <= params['size_max'], "Failed to get images by size_min and size_max") @test.attr(type='gate') def test_list_images_param_status(self): # Test to get all active images params = {"status": "active"} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_list_images_param_limit(self): # Test to get images by limit params = {"limit": 2} resp, images_list = self.client.image_list(params=params) self.assertEqual(resp['status'], '200') self.assertEqual(len(images_list), params['limit'], "Failed to get images by limit") @test.attr(type='gate') def test_get_image_schema(self): # Test to get image schema schema = "image" resp, body = self.client.get_schema(schema) self.assertEqual(200, resp.status) self.assertEqual("image", body['name']) @test.attr(type='gate') def test_get_images_schema(self): # Test to get images schema schema = "images" resp, body = self.client.get_schema(schema) self.assertEqual(200, resp.status) self.assertEqual("images", body['name']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/test_images_tags.py0000664000175000017500000000322312332757070025634 0ustar chuckchuck00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.image import base from tempest.common.utils import data_utils from tempest import test class ImagesTagsTest(base.BaseV2ImageTest): @test.attr(type='gate') def test_update_delete_tags_for_image(self): resp, body = self.create_image(container_format='bare', disk_format='raw', visibility='public') image_id = body['id'] tag = data_utils.rand_name('tag-') self.addCleanup(self.client.delete_image, image_id) # Creating image tag and verify it. resp, body = self.client.add_image_tag(image_id, tag) self.assertEqual(resp.status, 204) resp, body = self.client.get_image(image_id) self.assertEqual(resp.status, 200) self.assertIn(tag, body['tags']) # Deleting image tag and verify it. resp = self.client.delete_image_tag(image_id, tag) self.assertEqual(resp.status, 204) resp, body = self.client.get_image(image_id) self.assertEqual(resp.status, 200) self.assertNotIn(tag, body['tags']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/test_images_member.py0000664000175000017500000001111512332757070026144 0ustar chuckchuck00000000000000# 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 tempest.api.image import base from tempest import test class ImagesMemberTest(base.BaseV2MemberImageTest): _interface = 'json' @test.attr(type='gate') def test_image_share_accept(self): image_id = self._create_image() resp, member = self.os_img_client.add_member(image_id, self.alt_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(member['member_id'], self.alt_tenant_id) self.assertEqual(member['image_id'], image_id) self.assertEqual(member['status'], 'pending') self.assertNotIn(image_id, self._list_image_ids_as_alt()) self.alt_img_client.update_member_status(image_id, self.alt_tenant_id, 'accepted') self.assertIn(image_id, self._list_image_ids_as_alt()) resp, body = self.os_img_client.get_image_membership(image_id) self.assertEqual(200, resp.status) members = body['members'] member = members[0] self.assertEqual(len(members), 1, str(members)) self.assertEqual(member['member_id'], self.alt_tenant_id) self.assertEqual(member['image_id'], image_id) self.assertEqual(member['status'], 'accepted') @test.attr(type='gate') def test_image_share_reject(self): image_id = self._create_image() resp, member = self.os_img_client.add_member(image_id, self.alt_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(member['member_id'], self.alt_tenant_id) self.assertEqual(member['image_id'], image_id) self.assertEqual(member['status'], 'pending') self.assertNotIn(image_id, self._list_image_ids_as_alt()) resp, _ = self.alt_img_client.update_member_status(image_id, self.alt_tenant_id, 'rejected') self.assertEqual(200, resp.status) self.assertNotIn(image_id, self._list_image_ids_as_alt()) @test.attr(type='gate') def test_get_image_member(self): image_id = self._create_image() self.os_img_client.add_member(image_id, self.alt_tenant_id) self.alt_img_client.update_member_status(image_id, self.alt_tenant_id, 'accepted') self.assertIn(image_id, self._list_image_ids_as_alt()) resp, member = self.os_img_client.get_member(image_id, self.alt_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(self.alt_tenant_id, member['member_id']) self.assertEqual(image_id, member['image_id']) self.assertEqual('accepted', member['status']) @test.attr(type='gate') def test_remove_image_member(self): image_id = self._create_image() self.os_img_client.add_member(image_id, self.alt_tenant_id) self.alt_img_client.update_member_status(image_id, self.alt_tenant_id, 'accepted') self.assertIn(image_id, self._list_image_ids_as_alt()) resp = self.os_img_client.remove_member(image_id, self.alt_tenant_id) self.assertEqual(204, resp.status) self.assertNotIn(image_id, self._list_image_ids_as_alt()) @test.attr(type='gate') def test_get_image_member_schema(self): resp, body = self.os_img_client.get_schema("member") self.assertEqual(200, resp.status) self.assertEqual("member", body['name']) @test.attr(type='gate') def test_get_image_members_schema(self): resp, body = self.os_img_client.get_schema("members") self.assertEqual(200, resp.status) self.assertEqual("members", body['name']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/__init__.py0000664000175000017500000000000012332757070024037 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/test_images_member_negative.py0000664000175000017500000000356112332757070030034 0ustar chuckchuck00000000000000# 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 tempest.api.image import base from tempest import exceptions from tempest import test class ImagesMemberNegativeTest(base.BaseV2MemberImageTest): _interface = 'json' @test.attr(type=['negative', 'gate']) def test_image_share_invalid_status(self): image_id = self._create_image() resp, member = self.os_img_client.add_member(image_id, self.alt_tenant_id) self.assertEqual(member['status'], 'pending') self.assertRaises(exceptions.BadRequest, self.alt_img_client.update_member_status, image_id, self.alt_tenant_id, 'notavalidstatus') @test.attr(type=['negative', 'gate']) def test_image_share_owner_cannot_accept(self): image_id = self._create_image() resp, member = self.os_img_client.add_member(image_id, self.alt_tenant_id) self.assertEqual(member['status'], 'pending') self.assertNotIn(image_id, self._list_image_ids_as_alt()) self.assertRaises(exceptions.Unauthorized, self.os_img_client.update_member_status, image_id, self.alt_tenant_id, 'accepted') self.assertNotIn(image_id, self._list_image_ids_as_alt()) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/test_images_tags_negative.py0000664000175000017500000000327212332757070027522 0ustar chuckchuck00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.image import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ImagesTagsNegativeTest(base.BaseV2ImageTest): @test.attr(type=['negative', 'gate']) def test_update_tags_for_non_existing_image(self): # Update tag with non existing image. tag = data_utils.rand_name('tag-') non_exist_image = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.add_image_tag, non_exist_image, tag) @test.attr(type=['negative', 'gate']) def test_delete_non_existing_tag(self): # Delete non existing tag. resp, body = self.create_image(container_format='bare', disk_format='raw', is_public=True, ) image_id = body['id'] tag = data_utils.rand_name('non-exist-tag-') self.addCleanup(self.client.delete_image, image_id) self.assertRaises(exceptions.NotFound, self.client.delete_image_tag, image_id, tag) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v2/test_images_negative.py0000664000175000017500000000665312332757070026512 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # Copyright 2013 IBM Corp. # # 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 uuid from tempest.api.image import base from tempest import exceptions from tempest import test class ImagesNegativeTest(base.BaseV2ImageTest): """ here we have -ve tests for get_image and delete_image api Tests ** get non-existent image ** get image with image_id=NULL ** get the deleted image ** delete non-existent image ** delete rimage with image_id=NULL ** delete the deleted image """ @test.attr(type=['negative', 'gate']) def test_get_non_existent_image(self): # get the non-existent image non_existent_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.get_image, non_existent_id) @test.attr(type=['negative', 'gate']) def test_get_image_null_id(self): # get image with image_id = NULL image_id = "" self.assertRaises(exceptions.NotFound, self.client.get_image, image_id) @test.attr(type=['negative', 'gate']) def test_get_delete_deleted_image(self): # get and delete the deleted image # create and delete image resp, body = self.client.create_image(name='test', container_format='bare', disk_format='raw') image_id = body['id'] self.assertEqual(201, resp.status) self.client.delete_image(image_id) self.client.wait_for_resource_deletion(image_id) # get the deleted image self.assertRaises(exceptions.NotFound, self.client.get_image, image_id) # delete the deleted image self.assertRaises(exceptions.NotFound, self.client.delete_image, image_id) @test.attr(type=['negative', 'gate']) def test_delete_non_existing_image(self): # delete non-existent image non_existent_image_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.delete_image, non_existent_image_id) @test.attr(type=['negative', 'gate']) def test_delete_image_null_id(self): # delete image with image_id=NULL image_id = "" self.assertRaises(exceptions.NotFound, self.client.delete_image, image_id) @test.attr(type=['negative', 'gate']) def test_register_with_invalid_container_format(self): # Negative tests for invalid data supplied to POST /images self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'wrong', 'vhd') @test.attr(type=['negative', 'gate']) def test_register_with_invalid_disk_format(self): self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'bare', 'wrong') tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v1/0000775000175000017500000000000012332757136021742 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v1/test_images.py0000664000175000017500000004014612332757070024622 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import cStringIO as StringIO from tempest.api.image import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class CreateRegisterImagesTest(base.BaseV1ImageTest): """Here we test the registration and creation of images.""" @test.attr(type='gate') def test_register_then_upload(self): # Register, then upload an image properties = {'prop1': 'val1'} resp, body = self.create_image(name='New Name', container_format='bare', disk_format='raw', is_public=True, properties=properties) self.assertIn('id', body) image_id = body.get('id') self.assertEqual('New Name', body.get('name')) self.assertTrue(body.get('is_public')) self.assertEqual('queued', body.get('status')) for key, val in properties.items(): self.assertEqual(val, body.get('properties')[key]) # Now try uploading an image file image_file = StringIO.StringIO(('*' * 1024)) resp, body = self.client.update_image(image_id, data=image_file) self.assertIn('size', body) self.assertEqual(1024, body.get('size')) @test.attr(type='gate') def test_register_remote_image(self): # Register a new remote image resp, body = self.create_image(name='New Remote Image', container_format='bare', disk_format='raw', is_public=True, location='http://example.com' '/someimage.iso', properties={'key1': 'value1', 'key2': 'value2'}) self.assertIn('id', body) self.assertEqual('New Remote Image', body.get('name')) self.assertTrue(body.get('is_public')) self.assertEqual('active', body.get('status')) properties = body.get('properties') self.assertEqual(properties['key1'], 'value1') self.assertEqual(properties['key2'], 'value2') @test.attr(type='gate') def test_register_http_image(self): resp, body = self.create_image(name='New Http Image', container_format='bare', disk_format='raw', is_public=True, copy_from=CONF.image.http_image) self.assertIn('id', body) image_id = body.get('id') self.assertEqual('New Http Image', body.get('name')) self.assertTrue(body.get('is_public')) self.client.wait_for_image_status(image_id, 'active') resp, body = self.client.get_image(image_id) self.assertEqual(resp['status'], '200') @test.attr(type='gate') def test_register_image_with_min_ram(self): # Register an image with min ram properties = {'prop1': 'val1'} resp, body = self.create_image(name='New_image_with_min_ram', container_format='bare', disk_format='raw', is_public=True, min_ram=40, properties=properties) self.assertIn('id', body) self.assertEqual('New_image_with_min_ram', body.get('name')) self.assertTrue(body.get('is_public')) self.assertEqual('queued', body.get('status')) self.assertEqual(40, body.get('min_ram')) for key, val in properties.items(): self.assertEqual(val, body.get('properties')[key]) resp, body = self.client.delete_image(body['id']) self.assertEqual('200', resp['status']) class ListImagesTest(base.BaseV1ImageTest): """ Here we test the listing of image information """ @classmethod @test.safe_setup def setUpClass(cls): super(ListImagesTest, cls).setUpClass() # We add a few images here to test the listing functionality of # the images API img1 = cls._create_remote_image('one', 'bare', 'raw') img2 = cls._create_remote_image('two', 'ami', 'ami') img3 = cls._create_remote_image('dup', 'bare', 'raw') img4 = cls._create_remote_image('dup', 'bare', 'raw') img5 = cls._create_standard_image('1', 'ami', 'ami', 42) img6 = cls._create_standard_image('2', 'ami', 'ami', 142) img7 = cls._create_standard_image('33', 'bare', 'raw', 142) img8 = cls._create_standard_image('33', 'bare', 'raw', 142) cls.created_set = set(cls.created_images) # 4x-4x remote image cls.remote_set = set((img1, img2, img3, img4)) cls.standard_set = set((img5, img6, img7, img8)) # 5x bare, 3x ami cls.bare_set = set((img1, img3, img4, img7, img8)) cls.ami_set = set((img2, img5, img6)) # 1x with size 42 cls.size42_set = set((img5,)) # 3x with size 142 cls.size142_set = set((img6, img7, img8,)) # dup named cls.dup_set = set((img3, img4)) @classmethod def _create_remote_image(cls, name, container_format, disk_format): """ Create a new remote image and return the ID of the newly-registered image """ name = 'New Remote Image %s' % name location = 'http://example.com/someimage_%s.iso' % name resp, image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=True, location=location) image_id = image['id'] return image_id @classmethod def _create_standard_image(cls, name, container_format, disk_format, size): """ Create a new standard image and return the ID of the newly-registered image. Note that the size of the new image is a random number between 1024 and 4096 """ image_file = StringIO.StringIO('*' * size) name = 'New Standard Image %s' % name resp, image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=True, data=image_file) image_id = image['id'] return image_id @test.attr(type='gate') def test_index_no_params(self): # Simple test to see all fixture images returned resp, images_list = self.client.image_list() self.assertEqual(resp['status'], '200') image_list = map(lambda x: x['id'], images_list) for image_id in self.created_images: self.assertIn(image_id, image_list) @test.attr(type='gate') def test_index_disk_format(self): resp, images_list = self.client.image_list(disk_format='ami') self.assertEqual(resp['status'], '200') for image in images_list: self.assertEqual(image['disk_format'], 'ami') result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.ami_set <= result_set) self.assertFalse(self.created_set - self.ami_set <= result_set) @test.attr(type='gate') def test_index_container_format(self): resp, images_list = self.client.image_list(container_format='bare') self.assertEqual(resp['status'], '200') for image in images_list: self.assertEqual(image['container_format'], 'bare') result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.bare_set <= result_set) self.assertFalse(self.created_set - self.bare_set <= result_set) @test.attr(type='gate') def test_index_max_size(self): resp, images_list = self.client.image_list(size_max=42) self.assertEqual(resp['status'], '200') for image in images_list: self.assertTrue(image['size'] <= 42) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.size42_set <= result_set) self.assertFalse(self.created_set - self.size42_set <= result_set) @test.attr(type='gate') def test_index_min_size(self): resp, images_list = self.client.image_list(size_min=142) self.assertEqual(resp['status'], '200') for image in images_list: self.assertTrue(image['size'] >= 142) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.size142_set <= result_set) self.assertFalse(self.size42_set <= result_set) @test.attr(type='gate') def test_index_status_active_detail(self): resp, images_list = self.client.image_list_detail(status='active', sort_key='size', sort_dir='desc') self.assertEqual(resp['status'], '200') top_size = images_list[0]['size'] # We have non-zero sized images for image in images_list: size = image['size'] self.assertTrue(size <= top_size) top_size = size self.assertEqual(image['status'], 'active') @test.attr(type='gate') def test_index_name(self): resp, images_list = self.client.image_list_detail( name='New Remote Image dup') self.assertEqual(resp['status'], '200') result_set = set(map(lambda x: x['id'], images_list)) for image in images_list: self.assertEqual(image['name'], 'New Remote Image dup') self.assertTrue(self.dup_set <= result_set) self.assertFalse(self.created_set - self.dup_set <= result_set) class ListSnapshotImagesTest(base.BaseV1ImageTest): @classmethod @test.safe_setup def setUpClass(cls): super(ListSnapshotImagesTest, cls).setUpClass() if not CONF.compute_feature_enabled.api_v3: cls.servers_client = cls.os.servers_client else: cls.servers_client = cls.os.servers_v3_client cls.servers = [] # We add a few images here to test the listing functionality of # the images API cls.snapshot = cls._create_snapshot( 'snapshot', CONF.compute.image_ref, CONF.compute.flavor_ref) cls.snapshot_set = set((cls.snapshot,)) image_file = StringIO.StringIO('*' * 42) resp, image = cls.create_image(name="Standard Image", container_format='ami', disk_format='ami', is_public=True, data=image_file) cls.image_id = image['id'] cls.client.wait_for_image_status(image['id'], 'active') @classmethod def tearDownClass(cls): for server in getattr(cls, "servers", []): cls.servers_client.delete_server(server['id']) super(ListSnapshotImagesTest, cls).tearDownClass() @classmethod def _create_snapshot(cls, name, image_id, flavor, **kwargs): resp, server = cls.servers_client.create_server( name, image_id, flavor, **kwargs) cls.servers.append(server) cls.servers_client.wait_for_server_status( server['id'], 'ACTIVE') resp, image = cls.servers_client.create_image( server['id'], name) image_id = data_utils.parse_image_id(resp['location']) cls.created_images.append(image_id) cls.client.wait_for_image_status(image_id, 'active') return image_id @test.attr(type='gate') @test.services('compute') def test_index_server_id(self): # The images should contain images filtered by server id resp, images = self.client.image_list_detail( {'instance_uuid': self.servers[0]['id']}) self.assertEqual(200, resp.status) result_set = set(map(lambda x: x['id'], images)) self.assertEqual(self.snapshot_set, result_set) @test.attr(type='gate') @test.services('compute') def test_index_type(self): # The list of servers should be filtered by image type params = {'image_type': 'snapshot'} resp, images = self.client.image_list_detail(params) self.assertEqual(200, resp.status) result_set = set(map(lambda x: x['id'], images)) self.assertIn(self.snapshot, result_set) @test.attr(type='gate') @test.services('compute') def test_index_limit(self): # Verify only the expected number of results are returned resp, images = self.client.image_list_detail(limit=1) self.assertEqual(200, resp.status) self.assertEqual(1, len(images)) @test.attr(type='gate') @test.services('compute') def test_index_by_change_since(self): # Verify an update image is returned # Becoming ACTIVE will modify the updated time # Filter by the image's created time resp, image = self.client.get_image_meta(self.snapshot) self.assertEqual(200, resp.status) self.assertEqual(self.snapshot, image['id']) resp, images = self.client.image_list_detail( changes_since=image['updated_at']) self.assertEqual(200, resp.status) result_set = set(map(lambda x: x['id'], images)) self.assertIn(self.image_id, result_set) self.assertNotIn(self.snapshot, result_set) class UpdateImageMetaTest(base.BaseV1ImageTest): @classmethod def setUpClass(cls): super(UpdateImageMetaTest, cls).setUpClass() cls.image_id = cls._create_standard_image('1', 'ami', 'ami', 42) @classmethod def _create_standard_image(cls, name, container_format, disk_format, size): """ Create a new standard image and return the ID of the newly-registered image. Note that the size of the new image is a random number between 1024 and 4096 """ image_file = StringIO.StringIO('*' * size) name = 'New Standard Image %s' % name resp, image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=True, data=image_file, properties={'key1': 'value1'}) image_id = image['id'] return image_id @test.attr(type='gate') def test_list_image_metadata(self): # All metadata key/value pairs for an image should be returned resp, resp_metadata = self.client.get_image_meta(self.image_id) expected = {'key1': 'value1'} self.assertEqual(expected, resp_metadata['properties']) @test.attr(type='gate') def test_update_image_metadata(self): # The metadata for the image should match the updated values req_metadata = {'key1': 'alt1', 'key2': 'value2'} resp, metadata = self.client.get_image_meta(self.image_id) self.assertEqual(200, resp.status) self.assertEqual(metadata['properties'], {'key1': 'value1'}) metadata['properties'].update(req_metadata) resp, metadata = self.client.update_image( self.image_id, properties=metadata['properties']) resp, resp_metadata = self.client.get_image_meta(self.image_id) expected = {'key1': 'alt1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata['properties']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v1/test_image_members_negative.py0000664000175000017500000000415312332757070030031 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.image import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ImageMembersNegativeTest(base.BaseV1ImageMembersTest): @test.attr(type=['negative', 'gate']) def test_add_member_with_non_existing_image(self): # Add member with non existing image. non_exist_image = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.add_member, self.alt_tenant_id, non_exist_image) @test.attr(type=['negative', 'gate']) def test_delete_member_with_non_existing_image(self): # Delete member with non existing image. non_exist_image = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.delete_member, self.alt_tenant_id, non_exist_image) @test.attr(type=['negative', 'gate']) def test_delete_member_with_non_existing_tenant(self): # Delete member with non existing tenant. image_id = self._create_image() non_exist_tenant = data_utils.rand_uuid_hex() self.assertRaises(exceptions.NotFound, self.client.delete_member, non_exist_tenant, image_id) @test.attr(type=['negative', 'gate']) def test_get_image_without_membership(self): # Image is hidden from another tenants. image_id = self._create_image() self.assertRaises(exceptions.NotFound, self.alt_img_cli.get_image, image_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v1/__init__.py0000664000175000017500000000000012332757070024036 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v1/test_image_members.py0000664000175000017500000000466612332757070026160 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.image import base from tempest import test class ImageMembersTest(base.BaseV1ImageMembersTest): @test.attr(type='gate') def test_add_image_member(self): image = self._create_image() resp = self.client.add_member(self.alt_tenant_id, image) self.assertEqual(204, resp.status) resp, body = self.client.get_image_membership(image) self.assertEqual(200, resp.status) members = body['members'] members = map(lambda x: x['member_id'], members) self.assertIn(self.alt_tenant_id, members) # get image as alt user resp, body = self.alt_img_cli.get_image(image) self.assertEqual(200, resp.status) @test.attr(type='gate') def test_get_shared_images(self): image = self._create_image() resp = self.client.add_member(self.alt_tenant_id, image) self.assertEqual(204, resp.status) share_image = self._create_image() resp = self.client.add_member(self.alt_tenant_id, share_image) self.assertEqual(204, resp.status) resp, body = self.client.get_shared_images(self.alt_tenant_id) self.assertEqual(200, resp.status) images = body['shared_images'] images = map(lambda x: x['image_id'], images) self.assertIn(share_image, images) self.assertIn(image, images) @test.attr(type='gate') def test_remove_member(self): image_id = self._create_image() resp = self.client.add_member(self.alt_tenant_id, image_id) self.assertEqual(204, resp.status) resp = self.client.delete_member(self.alt_tenant_id, image_id) self.assertEqual(204, resp.status) resp, body = self.client.get_image_membership(image_id) self.assertEqual(200, resp.status) members = body['members'] self.assertEqual(0, len(members), str(members)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/v1/test_images_negative.py0000664000175000017500000000607412332757070026506 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.image import base from tempest import exceptions from tempest import test class CreateDeleteImagesNegativeTest(base.BaseV1ImageTest): """Here are negative tests for the deletion and creation of images.""" @test.attr(type=['negative', 'gate']) def test_register_with_invalid_container_format(self): # Negative tests for invalid data supplied to POST /images self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'wrong', 'vhd') @test.attr(type=['negative', 'gate']) def test_register_with_invalid_disk_format(self): self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'bare', 'wrong') @test.attr(type=['negative', 'gate']) def test_delete_image_with_invalid_image_id(self): # An image should not be deleted with invalid image id self.assertRaises(exceptions.NotFound, self.client.delete_image, '!@$%^&*()') @test.attr(type=['negative', 'gate']) def test_delete_non_existent_image(self): # Return an error while trying to delete a non-existent image non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa' self.assertRaises(exceptions.NotFound, self.client.delete_image, non_existent_image_id) @test.attr(type=['negative', 'gate']) def test_delete_image_blank_id(self): # Return an error while trying to delete an image with blank Id self.assertRaises(exceptions.NotFound, self.client.delete_image, '') @test.attr(type=['negative', 'gate']) def test_delete_image_non_hex_string_id(self): # Return an error while trying to delete an image with non hex id image_id = '11a22b9-120q-5555-cc11-00ab112223gj' self.assertRaises(exceptions.NotFound, self.client.delete_image, image_id) @test.attr(type=['negative', 'gate']) def test_delete_image_negative_image_id(self): # Return an error while trying to delete an image with negative id self.assertRaises(exceptions.NotFound, self.client.delete_image, -1) @test.attr(type=['negative', 'gate']) def test_delete_image_id_is_over_35_character_limit(self): # Return an error while trying to delete image with id over limit self.assertRaises(exceptions.NotFound, self.client.delete_image, '11a22b9-12a9-5555-cc11-00ab112223fa-3fac') tempest-2014.1.dev4108.gf22b6cc/tempest/api/image/base.py0000664000175000017500000001314712332757070022703 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 cStringIO as StringIO from tempest import clients from tempest.common import isolated_creds from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseImageTest(tempest.test.BaseTestCase): """Base test class for Image API tests.""" @classmethod def setUpClass(cls): cls.set_network_resources() super(BaseImageTest, cls).setUpClass() cls.created_images = [] cls._interface = 'json' cls.isolated_creds = isolated_creds.IsolatedCreds( cls.__name__, network_resources=cls.network_resources) if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) if CONF.compute.allow_tenant_isolation: cls.os = clients.Manager(cls.isolated_creds.get_primary_creds()) else: cls.os = clients.Manager() @classmethod def tearDownClass(cls): for image_id in cls.created_images: try: cls.client.delete_image(image_id) except exceptions.NotFound: pass for image_id in cls.created_images: cls.client.wait_for_resource_deletion(image_id) cls.isolated_creds.clear_isolated_creds() super(BaseImageTest, cls).tearDownClass() @classmethod def create_image(cls, **kwargs): """Wrapper that returns a test image.""" name = data_utils.rand_name(cls.__name__ + "-instance") if 'name' in kwargs: name = kwargs.pop('name') container_format = kwargs.pop('container_format') disk_format = kwargs.pop('disk_format') resp, image = cls.client.create_image(name, container_format, disk_format, **kwargs) cls.created_images.append(image['id']) return resp, image class BaseV1ImageTest(BaseImageTest): @classmethod def setUpClass(cls): super(BaseV1ImageTest, cls).setUpClass() cls.client = cls.os.image_client if not CONF.image_feature_enabled.api_v1: msg = "Glance API v1 not supported" raise cls.skipException(msg) class BaseV1ImageMembersTest(BaseV1ImageTest): @classmethod def setUpClass(cls): super(BaseV1ImageMembersTest, cls).setUpClass() if CONF.compute.allow_tenant_isolation: cls.os_alt = clients.Manager(cls.isolated_creds.get_alt_creds()) cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id'] else: cls.os_alt = clients.AltManager() identity_client = cls._get_identity_admin_client() cls.alt_tenant_id = identity_client.get_tenant_by_name( cls.os_alt.credentials['tenant_name'])['id'] cls.alt_img_cli = cls.os_alt.image_client def _create_image(self): image_file = StringIO.StringIO('*' * 1024) resp, image = self.create_image(container_format='bare', disk_format='raw', is_public=False, data=image_file) self.assertEqual(201, resp.status) image_id = image['id'] return image_id class BaseV2ImageTest(BaseImageTest): @classmethod def setUpClass(cls): super(BaseV2ImageTest, cls).setUpClass() cls.client = cls.os.image_client_v2 if not CONF.image_feature_enabled.api_v2: msg = "Glance API v2 not supported" raise cls.skipException(msg) class BaseV2MemberImageTest(BaseV2ImageTest): @classmethod def setUpClass(cls): super(BaseV2MemberImageTest, cls).setUpClass() if CONF.compute.allow_tenant_isolation: creds = cls.isolated_creds.get_alt_creds() cls.os_alt = clients.Manager(creds) cls.alt_tenant_id = cls.isolated_creds.get_alt_creds().tenant_id else: cls.os_alt = clients.AltManager() alt_tenant_name = cls.os_alt.credentials['tenant_name'] identity_client = cls._get_identity_admin_client() cls.alt_tenant_id = identity_client.get_tenant_by_name( alt_tenant_name)['id'] cls.os_img_client = cls.os.image_client_v2 cls.alt_img_client = cls.os_alt.image_client_v2 def _list_image_ids_as_alt(self): _, image_list = self.alt_img_client.image_list() image_ids = map(lambda x: x['id'], image_list) return image_ids def _create_image(self): name = data_utils.rand_name('image') resp, image = self.os_img_client.create_image(name, container_format='bare', disk_format='raw') image_id = image['id'] self.addCleanup(self.os_img_client.delete_image, image_id) return image_id tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/0000775000175000017500000000000012332757136022023 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_routers_negative.py0000664000175000017500000000461512332757070027024 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base_routers as base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class RoutersNegativeTest(base.BaseRouterTest): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(RoutersNegativeTest, cls).setUpClass() if not test.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) cls.router = cls.create_router(data_utils.rand_name('router-')) cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) @test.attr(type=['negative', 'smoke']) def test_router_add_gateway_invalid_network_returns_404(self): self.assertRaises(exceptions.NotFound, self.client.update_router, self.router['id'], external_gateway_info={ 'network_id': self.router['id']}) @test.attr(type=['negative', 'smoke']) def test_router_add_gateway_net_not_external_returns_400(self): self.create_subnet(self.network) self.assertRaises(exceptions.BadRequest, self.client.update_router, self.router['id'], external_gateway_info={ 'network_id': self.network['id']}) @test.attr(type=['negative', 'smoke']) def test_router_remove_interface_in_use_returns_409(self): self.client.add_router_interface_with_subnet_id( self.router['id'], self.subnet['id']) self.assertRaises(exceptions.Conflict, self.client.delete_router, self.router['id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_floating_ips.py0000664000175000017500000002010012332757070026100 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class FloatingIPTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations in the Quantum API using the REST client for Neutron: Create a Floating IP Update a Floating IP Delete a Floating IP List all Floating IPs Show Floating IP details Associate a Floating IP with a port and then delete that port Associate a Floating IP with a port and then with a port on another router v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: public_network_id which is the id for the external network present """ @classmethod @test.safe_setup def setUpClass(cls): super(FloatingIPTestJSON, cls).setUpClass() if not test.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) cls.ext_net_id = CONF.network.public_network_id # Create network, subnet, router and add interface cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) cls.router = cls.create_router(data_utils.rand_name('router-'), external_network_id=cls.ext_net_id) cls.create_router_interface(cls.router['id'], cls.subnet['id']) cls.port = list() # Create two ports one each for Creation and Updating of floatingIP for i in range(2): cls.create_port(cls.network) @test.attr(type='smoke') def test_create_list_show_update_delete_floating_ip(self): # Creates a floating IP resp, body = self.client.create_floatingip( floating_network_id=self.ext_net_id, port_id=self.ports[0]['id']) self.assertEqual('201', resp['status']) created_floating_ip = body['floatingip'] self.addCleanup(self.client.delete_floatingip, created_floating_ip['id']) self.assertIsNotNone(created_floating_ip['id']) self.assertIsNotNone(created_floating_ip['tenant_id']) self.assertIsNotNone(created_floating_ip['floating_ip_address']) self.assertEqual(created_floating_ip['port_id'], self.ports[0]['id']) self.assertEqual(created_floating_ip['floating_network_id'], self.ext_net_id) # Verifies the details of a floating_ip resp, floating_ip = self.client.show_floatingip( created_floating_ip['id']) self.assertEqual('200', resp['status']) shown_floating_ip = floating_ip['floatingip'] self.assertEqual(shown_floating_ip['id'], created_floating_ip['id']) self.assertEqual(shown_floating_ip['floating_network_id'], self.ext_net_id) self.assertEqual(shown_floating_ip['tenant_id'], created_floating_ip['tenant_id']) self.assertEqual(shown_floating_ip['floating_ip_address'], created_floating_ip['floating_ip_address']) self.assertEqual(shown_floating_ip['port_id'], self.ports[0]['id']) # Verify the floating ip exists in the list of all floating_ips resp, floating_ips = self.client.list_floatingips() self.assertEqual('200', resp['status']) floatingip_id_list = list() for f in floating_ips['floatingips']: floatingip_id_list.append(f['id']) self.assertIn(created_floating_ip['id'], floatingip_id_list) # Associate floating IP to the other port resp, floating_ip = self.client.update_floatingip( created_floating_ip['id'], port_id=self.ports[1]['id']) self.assertEqual('200', resp['status']) updated_floating_ip = floating_ip['floatingip'] self.assertEqual(updated_floating_ip['port_id'], self.ports[1]['id']) self.assertEqual(updated_floating_ip['fixed_ip_address'], self.ports[1]['fixed_ips'][0]['ip_address']) self.assertEqual(updated_floating_ip['router_id'], self.router['id']) # Disassociate floating IP from the port resp, floating_ip = self.client.update_floatingip( created_floating_ip['id'], port_id=None) self.assertEqual('200', resp['status']) updated_floating_ip = floating_ip['floatingip'] self.assertIsNone(updated_floating_ip['port_id']) self.assertIsNone(updated_floating_ip['fixed_ip_address']) self.assertIsNone(updated_floating_ip['router_id']) @test.attr(type='smoke') def test_floating_ip_delete_port(self): # Create a floating IP resp, body = self.client.create_floatingip( floating_network_id=self.ext_net_id) self.assertEqual('201', resp['status']) created_floating_ip = body['floatingip'] self.addCleanup(self.client.delete_floatingip, created_floating_ip['id']) # Create a port resp, port = self.client.create_port(network_id=self.network['id']) created_port = port['port'] resp, floating_ip = self.client.update_floatingip( created_floating_ip['id'], port_id=created_port['id']) self.assertEqual('200', resp['status']) # Delete port self.client.delete_port(created_port['id']) # Verifies the details of the floating_ip resp, floating_ip = self.client.show_floatingip( created_floating_ip['id']) self.assertEqual('200', resp['status']) shown_floating_ip = floating_ip['floatingip'] # Confirm the fields are back to None self.assertEqual(shown_floating_ip['id'], created_floating_ip['id']) self.assertIsNone(shown_floating_ip['port_id']) self.assertIsNone(shown_floating_ip['fixed_ip_address']) self.assertIsNone(shown_floating_ip['router_id']) @test.attr(type='smoke') def test_floating_ip_update_different_router(self): # Associate a floating IP to a port on a router resp, body = self.client.create_floatingip( floating_network_id=self.ext_net_id, port_id=self.ports[1]['id']) self.assertEqual('201', resp['status']) created_floating_ip = body['floatingip'] self.addCleanup(self.client.delete_floatingip, created_floating_ip['id']) self.assertEqual(created_floating_ip['router_id'], self.router['id']) network2 = self.create_network() subnet2 = self.create_subnet(network2) router2 = self.create_router(data_utils.rand_name('router-'), external_network_id=self.ext_net_id) self.create_router_interface(router2['id'], subnet2['id']) port_other_router = self.create_port(network2) # Associate floating IP to the other port on another router resp, floating_ip = self.client.update_floatingip( created_floating_ip['id'], port_id=port_other_router['id']) self.assertEqual('200', resp['status']) updated_floating_ip = floating_ip['floatingip'] self.assertEqual(updated_floating_ip['router_id'], router2['id']) self.assertEqual(updated_floating_ip['port_id'], port_other_router['id']) self.assertIsNotNone(updated_floating_ip['fixed_ip_address']) class FloatingIPTestXML(FloatingIPTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_security_groups_negative.py0000664000175000017500000001012712332757070030562 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.network import base_security_groups as base from tempest import exceptions from tempest import test class NegativeSecGroupTest(base.BaseSecGroupTest): _interface = 'json' @classmethod def setUpClass(cls): super(NegativeSecGroupTest, cls).setUpClass() if not test.is_extension_enabled('security-group', 'network'): msg = "security-group extension not enabled." raise cls.skipException(msg) @test.attr(type=['negative', 'gate']) def test_show_non_existent_security_group(self): non_exist_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.show_security_group, non_exist_id) @test.attr(type=['negative', 'gate']) def test_show_non_existent_security_group_rule(self): non_exist_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.show_security_group_rule, non_exist_id) @test.attr(type=['negative', 'gate']) def test_delete_non_existent_security_group(self): non_exist_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.delete_security_group, non_exist_id ) @test.attr(type=['negative', 'gate']) def test_create_security_group_rule_with_bad_protocol(self): group_create_body, _ = self._create_security_group() #Create rule with bad protocol name pname = 'bad_protocol_name' self.assertRaises( exceptions.BadRequest, self.client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol=pname, direction='ingress') @test.attr(type=['negative', 'gate']) def test_create_security_group_rule_with_invalid_ports(self): group_create_body, _ = self._create_security_group() #Create rule with invalid ports states = [(-16, 80, 'Invalid value for port -16'), (80, 79, 'port_range_min must be <= port_range_max'), (80, 65536, 'Invalid value for port 65536'), (-16, 65536, 'Invalid value for port')] for pmin, pmax, msg in states: ex = self.assertRaises( exceptions.BadRequest, self.client.create_security_group_rule, security_group_id=group_create_body['security_group']['id'], protocol='tcp', port_range_min=pmin, port_range_max=pmax, direction='ingress') self.assertIn(msg, str(ex)) @test.attr(type=['negative', 'smoke']) def test_create_additional_default_security_group_fails(self): # Create security group named 'default', it should be failed. name = 'default' self.assertRaises(exceptions.Conflict, self.client.create_security_group, name=name) @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_with_non_existent_security_group(self): # Create security group rules with not existing security group. non_existent_sg = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.create_security_group_rule, security_group_id=non_existent_sg, direction='ingress') class NegativeSecGroupTestXML(NegativeSecGroupTest): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/base_routers.py0000664000175000017500000000401312332757070025065 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base class BaseRouterTest(base.BaseAdminNetworkTest): # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest # as some router operations, such as enabling or disabling SNAT # require admin credentials by default @classmethod def setUpClass(cls): super(BaseRouterTest, cls).setUpClass() def _delete_router(self, router_id): resp, _ = self.client.delete_router(router_id) self.assertEqual(204, resp.status) # Asserting that the router is not found in the list # after deletion resp, list_body = self.client.list_routers() self.assertEqual('200', resp['status']) routers_list = list() for router in list_body['routers']: routers_list.append(router['id']) self.assertNotIn(router_id, routers_list) def _remove_router_interface_with_subnet_id(self, router_id, subnet_id): resp, body = self.client.remove_router_interface_with_subnet_id( router_id, subnet_id) self.assertEqual('200', resp['status']) self.assertEqual(subnet_id, body['subnet_id']) def _remove_router_interface_with_port_id(self, router_id, port_id): resp, body = self.client.remove_router_interface_with_port_id( router_id, port_id) self.assertEqual('200', resp['status']) self.assertEqual(port_id, body['port_id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_routers.py0000664000175000017500000003567112332757070025150 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netaddr from tempest.api.network import base_routers as base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class RoutersTest(base.BaseRouterTest): _interface = 'json' @classmethod def setUpClass(cls): super(RoutersTest, cls).setUpClass() if not test.is_extension_enabled('router', 'network'): msg = "router extension not enabled." raise cls.skipException(msg) admin_manager = clients.AdminManager() cls.identity_admin_client = admin_manager.identity_client def _cleanup_router(self, router): self.delete_router(router) self.routers.remove(router) def _create_router(self, name, admin_state_up=False, external_network_id=None, enable_snat=None): # associate a cleanup with created routers to avoid quota limits router = self.create_router(name, admin_state_up, external_network_id, enable_snat) self.addCleanup(self._cleanup_router, router) return router @test.attr(type='smoke') def test_create_show_list_update_delete_router(self): # Create a router # NOTE(salv-orlando): Do not invoke self.create_router # as we need to check the response code name = data_utils.rand_name('router-') resp, create_body = self.client.create_router( name, external_gateway_info={ "network_id": CONF.network.public_network_id}, admin_state_up=False) self.assertEqual('201', resp['status']) self.addCleanup(self._delete_router, create_body['router']['id']) self.assertEqual(create_body['router']['name'], name) self.assertEqual( create_body['router']['external_gateway_info']['network_id'], CONF.network.public_network_id) self.assertEqual(create_body['router']['admin_state_up'], False) # Show details of the created router resp, show_body = self.client.show_router( create_body['router']['id']) self.assertEqual('200', resp['status']) self.assertEqual(show_body['router']['name'], name) self.assertEqual( show_body['router']['external_gateway_info']['network_id'], CONF.network.public_network_id) self.assertEqual(show_body['router']['admin_state_up'], False) # List routers and verify if created router is there in response resp, list_body = self.client.list_routers() self.assertEqual('200', resp['status']) routers_list = list() for router in list_body['routers']: routers_list.append(router['id']) self.assertIn(create_body['router']['id'], routers_list) # Update the name of router and verify if it is updated updated_name = 'updated ' + name resp, update_body = self.client.update_router( create_body['router']['id'], name=updated_name) self.assertEqual('200', resp['status']) self.assertEqual(update_body['router']['name'], updated_name) resp, show_body = self.client.show_router( create_body['router']['id']) self.assertEqual(show_body['router']['name'], updated_name) @test.attr(type='smoke') def test_create_router_setting_tenant_id(self): # Test creating router from admin user setting tenant_id. test_tenant = data_utils.rand_name('test_tenant_') test_description = data_utils.rand_name('desc_') _, tenant = self.identity_admin_client.create_tenant( name=test_tenant, description=test_description) tenant_id = tenant['id'] self.addCleanup(self.identity_admin_client.delete_tenant, tenant_id) name = data_utils.rand_name('router-') resp, create_body = self.admin_client.create_router( name, tenant_id=tenant_id) self.assertEqual('201', resp['status']) self.addCleanup(self.admin_client.delete_router, create_body['router']['id']) self.assertEqual(tenant_id, create_body['router']['tenant_id']) @test.attr(type='smoke') def test_add_remove_router_interface_with_subnet_id(self): network = self.create_network() subnet = self.create_subnet(network) router = self._create_router(data_utils.rand_name('router-')) # Add router interface with subnet id resp, interface = self.client.add_router_interface_with_subnet_id( router['id'], subnet['id']) self.assertEqual('200', resp['status']) self.addCleanup(self._remove_router_interface_with_subnet_id, router['id'], subnet['id']) self.assertIn('subnet_id', interface.keys()) self.assertIn('port_id', interface.keys()) # Verify router id is equal to device id in port details resp, show_port_body = self.client.show_port( interface['port_id']) self.assertEqual(show_port_body['port']['device_id'], router['id']) @test.attr(type='smoke') def test_add_remove_router_interface_with_port_id(self): network = self.create_network() self.create_subnet(network) router = self._create_router(data_utils.rand_name('router-')) resp, port_body = self.client.create_port( network_id=network['id']) # add router interface to port created above resp, interface = self.client.add_router_interface_with_port_id( router['id'], port_body['port']['id']) self.assertEqual('200', resp['status']) self.addCleanup(self._remove_router_interface_with_port_id, router['id'], port_body['port']['id']) self.assertIn('subnet_id', interface.keys()) self.assertIn('port_id', interface.keys()) # Verify router id is equal to device id in port details resp, show_port_body = self.client.show_port( interface['port_id']) self.assertEqual(show_port_body['port']['device_id'], router['id']) def _verify_router_gateway(self, router_id, exp_ext_gw_info=None): resp, show_body = self.client.show_router(router_id) self.assertEqual('200', resp['status']) actual_ext_gw_info = show_body['router']['external_gateway_info'] if exp_ext_gw_info is None: self.assertIsNone(actual_ext_gw_info) return # Verify only keys passed in exp_ext_gw_info for k, v in exp_ext_gw_info.iteritems(): self.assertEqual(v, actual_ext_gw_info[k]) def _verify_gateway_port(self, router_id): resp, list_body = self.admin_client.list_ports( network_id=CONF.network.public_network_id, device_id=router_id) self.assertEqual(len(list_body['ports']), 1) gw_port = list_body['ports'][0] fixed_ips = gw_port['fixed_ips'] self.assertEqual(len(fixed_ips), 1) resp, public_net_body = self.admin_client.show_network( CONF.network.public_network_id) public_subnet_id = public_net_body['network']['subnets'][0] self.assertEqual(fixed_ips[0]['subnet_id'], public_subnet_id) @test.attr(type='smoke') def test_update_router_set_gateway(self): router = self._create_router(data_utils.rand_name('router-')) self.client.update_router( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id}) # Verify operation - router resp, show_body = self.client.show_router(router['id']) self.assertEqual('200', resp['status']) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id}) self._verify_gateway_port(router['id']) @test.requires_ext(extension='ext-gw-mode', service='network') @test.attr(type='smoke') def test_update_router_set_gateway_with_snat_explicit(self): router = self._create_router(data_utils.rand_name('router-')) self.admin_client.update_router_with_snat_gw_info( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id, 'enable_snat': True}) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id, 'enable_snat': True}) self._verify_gateway_port(router['id']) @test.requires_ext(extension='ext-gw-mode', service='network') @test.attr(type='smoke') def test_update_router_set_gateway_without_snat(self): router = self._create_router(data_utils.rand_name('router-')) self.admin_client.update_router_with_snat_gw_info( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_gateway_port(router['id']) @test.attr(type='smoke') def test_update_router_unset_gateway(self): router = self._create_router( data_utils.rand_name('router-'), external_network_id=CONF.network.public_network_id) self.client.update_router(router['id'], external_gateway_info={}) self._verify_router_gateway(router['id']) # No gateway port expected resp, list_body = self.admin_client.list_ports( network_id=CONF.network.public_network_id, device_id=router['id']) self.assertFalse(list_body['ports']) @test.requires_ext(extension='ext-gw-mode', service='network') @test.attr(type='smoke') def test_update_router_reset_gateway_without_snat(self): router = self._create_router( data_utils.rand_name('router-'), external_network_id=CONF.network.public_network_id) self.admin_client.update_router_with_snat_gw_info( router['id'], external_gateway_info={ 'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_router_gateway( router['id'], {'network_id': CONF.network.public_network_id, 'enable_snat': False}) self._verify_gateway_port(router['id']) @test.requires_ext(extension='extraroute', service='network') @test.attr(type='smoke') def test_update_extra_route(self): self.network = self.create_network() self.name = self.network['name'] self.subnet = self.create_subnet(self.network) # Add router interface with subnet id self.router = self._create_router( data_utils.rand_name('router-'), True) self.create_router_interface(self.router['id'], self.subnet['id']) self.addCleanup( self._delete_extra_routes, self.router['id']) # Update router extra route, second ip of the range is # used as next hop cidr = netaddr.IPNetwork(self.subnet['cidr']) next_hop = str(cidr[2]) destination = str(self.subnet['cidr']) resp, extra_route = self.client.update_extra_routes( self.router['id'], next_hop, destination) self.assertEqual('200', resp['status']) self.assertEqual(1, len(extra_route['router']['routes'])) self.assertEqual(destination, extra_route['router']['routes'][0]['destination']) self.assertEqual(next_hop, extra_route['router']['routes'][0]['nexthop']) resp, show_body = self.client.show_router(self.router['id']) self.assertEqual('200', resp['status']) self.assertEqual(destination, show_body['router']['routes'][0]['destination']) self.assertEqual(next_hop, show_body['router']['routes'][0]['nexthop']) def _delete_extra_routes(self, router_id): resp, _ = self.client.delete_extra_routes(router_id) @test.attr(type='smoke') def test_update_router_admin_state(self): self.router = self._create_router(data_utils.rand_name('router-')) self.assertFalse(self.router['admin_state_up']) # Update router admin state resp, update_body = self.client.update_router(self.router['id'], admin_state_up=True) self.assertEqual('200', resp['status']) self.assertTrue(update_body['router']['admin_state_up']) resp, show_body = self.client.show_router(self.router['id']) self.assertEqual('200', resp['status']) self.assertTrue(show_body['router']['admin_state_up']) @test.attr(type='smoke') def test_add_multiple_router_interfaces(self): network = self.create_network() subnet01 = self.create_subnet(network) subnet02 = self.create_subnet(network) router = self._create_router(data_utils.rand_name('router-')) interface01 = self._add_router_interface_with_subnet_id(router['id'], subnet01['id']) self._verify_router_interface(router['id'], subnet01['id'], interface01['port_id']) interface02 = self._add_router_interface_with_subnet_id(router['id'], subnet02['id']) self._verify_router_interface(router['id'], subnet02['id'], interface02['port_id']) def _add_router_interface_with_subnet_id(self, router_id, subnet_id): resp, interface = self.client.add_router_interface_with_subnet_id( router_id, subnet_id) self.assertEqual('200', resp['status']) self.addCleanup(self._remove_router_interface_with_subnet_id, router_id, subnet_id) self.assertEqual(subnet_id, interface['subnet_id']) return interface def _verify_router_interface(self, router_id, subnet_id, port_id): resp, show_port_body = self.client.show_port(port_id) self.assertEqual('200', resp['status']) interface_port = show_port_body['port'] self.assertEqual(router_id, interface_port['device_id']) self.assertEqual(subnet_id, interface_port['fixed_ips'][0]['subnet_id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_vpnaas_extensions.py0000664000175000017500000001767412332757070027217 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class VPNaaSTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: List VPN Services Show VPN Services Create VPN Services Update VPN Services Delete VPN Services List, Show, Create, Delete, and Update IKE policy """ @classmethod @test.safe_setup def setUpClass(cls): if not test.is_extension_enabled('vpnaas', 'network'): msg = "vpnaas extension not enabled." raise cls.skipException(msg) super(VPNaaSTestJSON, cls).setUpClass() cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) cls.router = cls.create_router( data_utils.rand_name("router-"), external_network_id=CONF.network.public_network_id) cls.create_router_interface(cls.router['id'], cls.subnet['id']) cls.vpnservice = cls.create_vpnservice(cls.subnet['id'], cls.router['id']) cls.ikepolicy = cls.create_ikepolicy( data_utils.rand_name("ike-policy-")) def _delete_ike_policy(self, ike_policy_id): # Deletes a ike policy and verifies if it is deleted or not ike_list = list() resp, all_ike = self.client.list_ikepolicies() for ike in all_ike['ikepolicies']: ike_list.append(ike['id']) if ike_policy_id in ike_list: resp, _ = self.client.delete_ikepolicy(ike_policy_id) self.assertEqual(204, resp.status) # Asserting that the policy is not found in list after deletion resp, ikepolicies = self.client.list_ikepolicies() ike_id_list = list() for i in ikepolicies['ikepolicies']: ike_id_list.append(i['id']) self.assertNotIn(ike_policy_id, ike_id_list) @test.attr(type='smoke') def test_list_vpn_services(self): # Verify the VPN service exists in the list of all VPN services resp, body = self.client.list_vpnservices() self.assertEqual('200', resp['status']) vpnservices = body['vpnservices'] self.assertIn(self.vpnservice['id'], [v['id'] for v in vpnservices]) @test.attr(type='smoke') def test_create_update_delete_vpn_service(self): # Creates a VPN service name = data_utils.rand_name('vpn-service-') resp, body = self.client.create_vpnservice(subnet_id=self.subnet['id'], router_id=self.router['id'], name=name, admin_state_up=True) self.assertEqual('201', resp['status']) vpnservice = body['vpnservice'] # Assert if created vpnservices are not found in vpnservices list resp, body = self.client.list_vpnservices() vpn_services = [vs['id'] for vs in body['vpnservices']] self.assertIsNotNone(vpnservice['id']) self.assertIn(vpnservice['id'], vpn_services) # TODO(raies): implement logic to update vpnservice # VPNaaS client function to update is implemented. # But precondition is that current state of vpnservice # should be "ACTIVE" not "PENDING*" # Verification of vpn service delete resp, body = self.client.delete_vpnservice(vpnservice['id']) self.assertEqual('204', resp['status']) # Asserting if vpn service is found in the list after deletion resp, body = self.client.list_vpnservices() vpn_services = [vs['id'] for vs in body['vpnservices']] self.assertNotIn(vpnservice['id'], vpn_services) @test.attr(type='smoke') def test_show_vpn_service(self): # Verifies the details of a vpn service resp, body = self.client.show_vpnservice(self.vpnservice['id']) self.assertEqual('200', resp['status']) vpnservice = body['vpnservice'] self.assertEqual(self.vpnservice['id'], vpnservice['id']) self.assertEqual(self.vpnservice['name'], vpnservice['name']) self.assertEqual(self.vpnservice['description'], vpnservice['description']) self.assertEqual(self.vpnservice['router_id'], vpnservice['router_id']) self.assertEqual(self.vpnservice['subnet_id'], vpnservice['subnet_id']) self.assertEqual(self.vpnservice['tenant_id'], vpnservice['tenant_id']) @test.attr(type='smoke') def test_list_ike_policies(self): # Verify the ike policy exists in the list of all IKE policies resp, body = self.client.list_ikepolicies() self.assertEqual('200', resp['status']) ikepolicies = body['ikepolicies'] self.assertIn(self.ikepolicy['id'], [i['id'] for i in ikepolicies]) @test.attr(type='smoke') def test_create_update_delete_ike_policy(self): # Creates a IKE policy name = data_utils.rand_name('ike-policy-') resp, body = (self.client.create_ikepolicy( name=name, ike_version="v1", encryption_algorithm="aes-128", auth_algorithm="sha1")) self.assertEqual('201', resp['status']) ikepolicy = body['ikepolicy'] self.addCleanup(self._delete_ike_policy, ikepolicy['id']) # Verification of ike policy update description = "Updated ike policy" new_ike = {'description': description, 'pfs': 'group5', 'name': data_utils.rand_name("New-IKE-")} resp, body = self.client.update_ikepolicy(ikepolicy['id'], **new_ike) self.assertEqual('200', resp['status']) updated_ike_policy = body['ikepolicy'] self.assertEqual(updated_ike_policy['description'], description) # Verification of ike policy delete resp, body = self.client.delete_ikepolicy(ikepolicy['id']) self.assertEqual('204', resp['status']) @test.attr(type='smoke') def test_show_ike_policy(self): # Verifies the details of a ike policy resp, body = self.client.show_ikepolicy(self.ikepolicy['id']) self.assertEqual('200', resp['status']) ikepolicy = body['ikepolicy'] self.assertEqual(self.ikepolicy['id'], ikepolicy['id']) self.assertEqual(self.ikepolicy['name'], ikepolicy['name']) self.assertEqual(self.ikepolicy['description'], ikepolicy['description']) self.assertEqual(self.ikepolicy['encryption_algorithm'], ikepolicy['encryption_algorithm']) self.assertEqual(self.ikepolicy['auth_algorithm'], ikepolicy['auth_algorithm']) self.assertEqual(self.ikepolicy['tenant_id'], ikepolicy['tenant_id']) self.assertEqual(self.ikepolicy['pfs'], ikepolicy['pfs']) self.assertEqual(self.ikepolicy['phase1_negotiation_mode'], ikepolicy['phase1_negotiation_mode']) self.assertEqual(self.ikepolicy['ike_version'], ikepolicy['ike_version']) class VPNaaSTestXML(VPNaaSTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_networks_negative.py0000664000175000017500000000433012332757070027167 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class NetworksNegativeTestJSON(base.BaseNetworkTest): _interface = 'json' @attr(type=['negative', 'smoke']) def test_show_non_existent_network(self): non_exist_id = data_utils.rand_name('network') self.assertRaises(exceptions.NotFound, self.client.show_network, non_exist_id) @attr(type=['negative', 'smoke']) def test_show_non_existent_subnet(self): non_exist_id = data_utils.rand_name('subnet') self.assertRaises(exceptions.NotFound, self.client.show_subnet, non_exist_id) @attr(type=['negative', 'smoke']) def test_show_non_existent_port(self): non_exist_id = data_utils.rand_name('port') self.assertRaises(exceptions.NotFound, self.client.show_port, non_exist_id) @attr(type=['negative', 'smoke']) def test_update_non_existent_network(self): non_exist_id = data_utils.rand_name('network') self.assertRaises(exceptions.NotFound, self.client.update_network, non_exist_id, name="new_name") @attr(type=['negative', 'smoke']) def test_delete_non_existent_network(self): non_exist_id = data_utils.rand_name('network') self.assertRaises(exceptions.NotFound, self.client.delete_network, non_exist_id) class NetworksNegativeTestXML(NetworksNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_extra_dhcp_options.py0000664000175000017500000000773112332757070027335 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import test class ExtraDHCPOptionsTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations with the Extra DHCP Options Neutron API extension: port create port list port show port update v2.0 of the Neutron API is assumed. It is also assumed that the Extra DHCP Options extension is enabled in the [network-feature-enabled] section of etc/tempest.conf """ @classmethod @test.safe_setup def setUpClass(cls): super(ExtraDHCPOptionsTestJSON, cls).setUpClass() if not test.is_extension_enabled('extra_dhcp_opt', 'network'): msg = "Extra DHCP Options extension not enabled." raise cls.skipException(msg) cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) cls.port = cls.create_port(cls.network) @test.attr(type='smoke') def test_create_list_port_with_extra_dhcp_options(self): # Create a port with Extra DHCP Options extra_dhcp_opts = [ {'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'}, {'opt_value': '123.123.123.123', 'opt_name': 'tftp-server'}, {'opt_value': '123.123.123.45', 'opt_name': 'server-ip-address'} ] resp, body = self.client.create_port( network_id=self.network['id'], extra_dhcp_opts=extra_dhcp_opts) self.assertEqual('201', resp['status']) port_id = body['port']['id'] self.addCleanup(self.client.delete_port, port_id) # Confirm port created has Extra DHCP Options resp, body = self.client.list_ports() self.assertEqual('200', resp['status']) ports = body['ports'] port = [p for p in ports if p['id'] == port_id] self.assertTrue(port) self._confirm_extra_dhcp_options(port[0], extra_dhcp_opts) @test.attr(type='smoke') def test_update_show_port_with_extra_dhcp_options(self): # Update port with extra dhcp options extra_dhcp_opts = [ {'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'}, {'opt_value': '123.123.123.123', 'opt_name': 'tftp-server'}, {'opt_value': '123.123.123.45', 'opt_name': 'server-ip-address'} ] name = data_utils.rand_name('new-port-name') resp, body = self.client.update_port( self.port['id'], name=name, extra_dhcp_opts=extra_dhcp_opts) self.assertEqual('200', resp['status']) # Confirm extra dhcp options were added to the port resp, body = self.client.show_port(self.port['id']) self.assertEqual('200', resp['status']) self._confirm_extra_dhcp_options(body['port'], extra_dhcp_opts) def _confirm_extra_dhcp_options(self, port, extra_dhcp_opts): retrieved = port['extra_dhcp_opts'] self.assertEqual(len(retrieved), len(extra_dhcp_opts)) for retrieved_option in retrieved: for option in extra_dhcp_opts: if (retrieved_option['opt_value'] == option['opt_value'] and retrieved_option['opt_name'] == option['opt_name']): break else: self.fail('Extra DHCP option not found in port %s' % str(retrieved_option)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/common.py0000664000175000017500000001064312332757070023666 0ustar chuckchuck00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. class AttributeDict(dict): """ Provide attribute access (dict.key) to dictionary values. """ def __getattr__(self, name): """Allow attribute access for all keys in the dict.""" if name in self: return self[name] return super(AttributeDict, self).__getattribute__(name) class DeletableResource(AttributeDict): """ Support deletion of neutron resources (networks, subnets) via a delete() method, as is supported by keystone and nova resources. """ def __init__(self, *args, **kwargs): self.client = kwargs.pop('client', None) super(DeletableResource, self).__init__(*args, **kwargs) def __str__(self): return '<%s id="%s" name="%s">' % (self.__class__.__name__, self.id, self.name) def delete(self): raise NotImplemented() def __hash__(self): return id(self) class DeletableNetwork(DeletableResource): def delete(self): self.client.delete_network(self.id) class DeletableSubnet(DeletableResource): def __init__(self, *args, **kwargs): super(DeletableSubnet, self).__init__(*args, **kwargs) self._router_ids = set() def update(self, *args, **kwargs): body = dict(subnet=dict(*args, **kwargs)) result = self.client.update_subnet(subnet=self.id, body=body) super(DeletableSubnet, self).update(**result['subnet']) def add_to_router(self, router_id): self._router_ids.add(router_id) body = dict(subnet_id=self.id) self.client.add_interface_router(router_id, body=body) def delete(self): for router_id in self._router_ids.copy(): body = dict(subnet_id=self.id) self.client.remove_interface_router(router_id, body=body) self._router_ids.remove(router_id) self.client.delete_subnet(self.id) class DeletableRouter(DeletableResource): def add_gateway(self, network_id): body = dict(network_id=network_id) self.client.add_gateway_router(self.id, body=body) def delete(self): self.client.remove_gateway_router(self.id) self.client.delete_router(self.id) class DeletableFloatingIp(DeletableResource): def update(self, *args, **kwargs): result = self.client.update_floatingip(floatingip=self.id, body=dict( floatingip=dict(*args, **kwargs) )) super(DeletableFloatingIp, self).update(**result['floatingip']) def __repr__(self): return '<%s addr="%s">' % (self.__class__.__name__, self.floating_ip_address) def __str__(self): return '<"FloatingIP" addr="%s" id="%s">' % (self.floating_ip_address, self.id) def delete(self): self.client.delete_floatingip(self.id) class DeletablePort(DeletableResource): def delete(self): self.client.delete_port(self.id) class DeletableSecurityGroup(DeletableResource): def delete(self): self.client.delete_security_group(self.id) class DeletableSecurityGroupRule(DeletableResource): def __repr__(self): return '<%s id="%s">' % (self.__class__.__name__, self.id) def delete(self): self.client.delete_security_group_rule(self.id) class DeletablePool(DeletableResource): def delete(self): self.client.delete_pool(self.id) class DeletableMember(DeletableResource): def delete(self): self.client.delete_member(self.id) class DeletableVip(DeletableResource): def delete(self): self.client.delete_vip(self.id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_service_type_management.py0000664000175000017500000000246612332757070030336 0ustar chuckchuck00000000000000# 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 tempest.api.network import base from tempest import test class ServiceTypeManagementTestJSON(base.BaseNetworkTest): _interface = 'json' @classmethod def setUpClass(cls): super(ServiceTypeManagementTestJSON, cls).setUpClass() if not test.is_extension_enabled('service-type', 'network'): msg = "Neutron Service Type Management not enabled." raise cls.skipException(msg) @test.attr(type='smoke') def test_service_provider_list(self): resp, body = self.client.list_service_providers() self.assertEqual(resp['status'], '200') self.assertIsInstance(body['service_providers'], list) class ServiceTypeManagementTestXML(ServiceTypeManagementTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_ports.py0000664000175000017500000002710112332757070024601 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import socket from tempest.api.network import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class PortsTestJSON(base.BaseNetworkTest): _interface = 'json' """ Test the following operations for ports: port create port delete port list port show port update """ @classmethod @test.safe_setup def setUpClass(cls): super(PortsTestJSON, cls).setUpClass() cls.network = cls.create_network() cls.port = cls.create_port(cls.network) def _delete_port(self, port_id): resp, body = self.client.delete_port(port_id) self.assertEqual('204', resp['status']) resp, body = self.client.list_ports() self.assertEqual('200', resp['status']) ports_list = body['ports'] self.assertFalse(port_id in [n['id'] for n in ports_list]) @test.attr(type='smoke') def test_create_update_delete_port(self): # Verify port creation resp, body = self.client.create_port(network_id=self.network['id']) self.assertEqual('201', resp['status']) port = body['port'] # Schedule port deletion with verification upon test completion self.addCleanup(self._delete_port, port['id']) self.assertTrue(port['admin_state_up']) # Verify port update new_name = "New_Port" resp, body = self.client.update_port( port['id'], name=new_name, admin_state_up=False) self.assertEqual('200', resp['status']) updated_port = body['port'] self.assertEqual(updated_port['name'], new_name) self.assertFalse(updated_port['admin_state_up']) @test.attr(type='smoke') def test_show_port(self): # Verify the details of port resp, body = self.client.show_port(self.port['id']) self.assertEqual('200', resp['status']) port = body['port'] self.assertIn('id', port) self.assertEqual(port['id'], self.port['id']) self.assertEqual(self.port['admin_state_up'], port['admin_state_up']) self.assertEqual(self.port['device_id'], port['device_id']) self.assertEqual(self.port['device_owner'], port['device_owner']) self.assertEqual(self.port['mac_address'], port['mac_address']) self.assertEqual(self.port['name'], port['name']) self.assertEqual(self.port['security_groups'], port['security_groups']) self.assertEqual(self.port['network_id'], port['network_id']) self.assertEqual(self.port['security_groups'], port['security_groups']) self.assertEqual(port['fixed_ips'], []) @test.attr(type='smoke') def test_show_port_fields(self): # Verify specific fields of a port fields = ['id', 'mac_address'] resp, body = self.client.show_port(self.port['id'], fields=fields) self.assertEqual('200', resp['status']) port = body['port'] self.assertEqual(sorted(port.keys()), sorted(fields)) for field_name in fields: self.assertEqual(port[field_name], self.port[field_name]) @test.attr(type='smoke') def test_list_ports(self): # Verify the port exists in the list of all ports resp, body = self.client.list_ports() self.assertEqual('200', resp['status']) ports = [port['id'] for port in body['ports'] if port['id'] == self.port['id']] self.assertNotEmpty(ports, "Created port not found in the list") @test.attr(type='smoke') def test_port_list_filter_by_router_id(self): # Create a router network = self.create_network() self.create_subnet(network) router = self.create_router(data_utils.rand_name('router-')) resp, port = self.client.create_port(network_id=network['id']) # Add router interface to port created above resp, interface = self.client.add_router_interface_with_port_id( router['id'], port['port']['id']) self.addCleanup(self.client.remove_router_interface_with_port_id, router['id'], port['port']['id']) # List ports filtered by router_id resp, port_list = self.client.list_ports( device_id=router['id']) self.assertEqual('200', resp['status']) ports = port_list['ports'] self.assertEqual(len(ports), 1) self.assertEqual(ports[0]['id'], port['port']['id']) self.assertEqual(ports[0]['device_id'], router['id']) @test.attr(type='smoke') def test_list_ports_fields(self): # Verify specific fields of ports fields = ['id', 'mac_address'] resp, body = self.client.list_ports(fields=fields) self.assertEqual('200', resp['status']) ports = body['ports'] self.assertNotEmpty(ports, "Port list returned is empty") # Asserting the fields returned are correct for port in ports: self.assertEqual(sorted(fields), sorted(port.keys())) @test.attr(type='smoke') def test_update_port_with_second_ip(self): # Create a network with two subnets network = self.create_network() subnet_1 = self.create_subnet(network) subnet_2 = self.create_subnet(network) fixed_ip_1 = [{'subnet_id': subnet_1['id']}] fixed_ip_2 = [{'subnet_id': subnet_2['id']}] # Create a port with a single IP address from first subnet port = self.create_port(network, fixed_ips=fixed_ip_1) self.assertEqual(1, len(port['fixed_ips'])) # Update the port with a second IP address from second subnet fixed_ips = fixed_ip_1 + fixed_ip_2 port = self.update_port(port, fixed_ips=fixed_ips) self.assertEqual(2, len(port['fixed_ips'])) # Update the port to return to a single IP address port = self.update_port(port, fixed_ips=fixed_ip_1) self.assertEqual(1, len(port['fixed_ips'])) class PortsTestXML(PortsTestJSON): _interface = 'xml' class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(PortsAdminExtendedAttrsTestJSON, cls).setUpClass() cls.identity_client = cls._get_identity_admin_client() cls.tenant = cls.identity_client.get_tenant_by_name( CONF.identity.tenant_name) cls.network = cls.create_network() cls.host_id = socket.gethostname() @test.attr(type='smoke') def test_create_port_binding_ext_attr(self): post_body = {"network_id": self.network['id'], "binding:host_id": self.host_id} resp, body = self.admin_client.create_port(**post_body) self.assertEqual('201', resp['status']) port = body['port'] self.addCleanup(self.admin_client.delete_port, port['id']) host_id = port['binding:host_id'] self.assertIsNotNone(host_id) self.assertEqual(self.host_id, host_id) @test.attr(type='smoke') def test_update_port_binding_ext_attr(self): post_body = {"network_id": self.network['id']} resp, body = self.admin_client.create_port(**post_body) self.assertEqual('201', resp['status']) port = body['port'] self.addCleanup(self.admin_client.delete_port, port['id']) update_body = {"binding:host_id": self.host_id} resp, body = self.admin_client.update_port(port['id'], **update_body) self.assertEqual('200', resp['status']) updated_port = body['port'] host_id = updated_port['binding:host_id'] self.assertIsNotNone(host_id) self.assertEqual(self.host_id, host_id) @test.attr(type='smoke') def test_list_ports_binding_ext_attr(self): # Create a new port post_body = {"network_id": self.network['id']} resp, body = self.admin_client.create_port(**post_body) self.assertEqual('201', resp['status']) port = body['port'] self.addCleanup(self.admin_client.delete_port, port['id']) # Update the port's binding attributes so that is now 'bound' # to a host update_body = {"binding:host_id": self.host_id} resp, _ = self.admin_client.update_port(port['id'], **update_body) self.assertEqual('200', resp['status']) # List all ports, ensure new port is part of list and its binding # attributes are set and accurate resp, body = self.admin_client.list_ports() self.assertEqual('200', resp['status']) ports_list = body['ports'] pids_list = [p['id'] for p in ports_list] self.assertIn(port['id'], pids_list) listed_port = [p for p in ports_list if p['id'] == port['id']] self.assertEqual(1, len(listed_port), 'Multiple ports listed with id %s in ports listing: ' '%s' % (port['id'], ports_list)) self.assertEqual(self.host_id, listed_port[0]['binding:host_id']) @test.attr(type='smoke') def test_show_port_binding_ext_attr(self): resp, body = self.admin_client.create_port( network_id=self.network['id']) self.assertEqual('201', resp['status']) port = body['port'] self.addCleanup(self.admin_client.delete_port, port['id']) resp, body = self.admin_client.show_port(port['id']) self.assertEqual('200', resp['status']) show_port = body['port'] self.assertEqual(port['binding:host_id'], show_port['binding:host_id']) self.assertEqual(port['binding:vif_type'], show_port['binding:vif_type']) self.assertEqual(port['binding:vif_details'], show_port['binding:vif_details']) class PortsAdminExtendedAttrsTestXML(PortsAdminExtendedAttrsTestJSON): _interface = 'xml' class PortsIpV6TestJSON(PortsTestJSON): _ip_version = 6 _tenant_network_cidr = CONF.network.tenant_network_v6_cidr _tenant_network_mask_bits = CONF.network.tenant_network_v6_mask_bits @classmethod def setUpClass(cls): super(PortsIpV6TestJSON, cls).setUpClass() if not CONF.network_feature_enabled.ipv6: cls.tearDownClass() skip_msg = "IPv6 Tests are disabled." raise cls.skipException(skip_msg) class PortsIpV6TestXML(PortsIpV6TestJSON): _interface = 'xml' class PortsAdminExtendedAttrsIpV6TestJSON(PortsAdminExtendedAttrsTestJSON): _ip_version = 6 _tenant_network_cidr = CONF.network.tenant_network_v6_cidr _tenant_network_mask_bits = CONF.network.tenant_network_v6_mask_bits @classmethod def setUpClass(cls): if not CONF.network_feature_enabled.ipv6: skip_msg = "IPv6 Tests are disabled." raise cls.skipException(skip_msg) super(PortsAdminExtendedAttrsIpV6TestJSON, cls).setUpClass() class PortsAdminExtendedAttrsIpV6TestXML( PortsAdminExtendedAttrsIpV6TestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/__init__.py0000664000175000017500000000000012332757070024117 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_extensions.py0000664000175000017500000000630512332757070025634 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack, Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest import test class ExtensionsTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: List all available extensions v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: """ @classmethod def setUpClass(cls): super(ExtensionsTestJSON, cls).setUpClass() @test.attr(type='smoke') def test_list_show_extensions(self): # List available extensions for the tenant expected_alias = ['security-group', 'l3_agent_scheduler', 'ext-gw-mode', 'binding', 'quotas', 'agent', 'dhcp_agent_scheduler', 'provider', 'router', 'extraroute', 'external-net', 'allowed-address-pairs', 'extra_dhcp_opt'] expected_alias = [ext for ext in expected_alias if test.is_extension_enabled(ext, 'network')] actual_alias = list() resp, extensions = self.client.list_extensions() self.assertEqual('200', resp['status']) list_extensions = extensions['extensions'] # Show and verify the details of the available extensions for ext in list_extensions: ext_name = ext['name'] ext_alias = ext['alias'] actual_alias.append(ext['alias']) resp, ext_details = self.client.show_extension(ext_alias) self.assertEqual('200', resp['status']) ext_details = ext_details['extension'] self.assertIsNotNone(ext_details) self.assertIn('updated', ext_details.keys()) self.assertIn('name', ext_details.keys()) self.assertIn('description', ext_details.keys()) self.assertIn('namespace', ext_details.keys()) self.assertIn('links', ext_details.keys()) self.assertIn('alias', ext_details.keys()) self.assertEqual(ext_details['name'], ext_name) self.assertEqual(ext_details['alias'], ext_alias) self.assertEqual(ext_details, ext) # Verify if expected extensions are present in the actual list # of extensions returned, but only for those that have been # enabled via configuration for e in expected_alias: if test.is_extension_enabled(e, 'network'): self.assertIn(e, actual_alias) class ExtensionsTestXML(ExtensionsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_metering_extensions.py0000664000175000017500000001562112332757070027527 0ustar chuckchuck00000000000000# Copyright (C) 2014 eNovance SAS # # Author: Emilien Macchi # # 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 tempest.api.network import base from tempest.common.utils import data_utils from tempest.openstack.common import log as logging from tempest import test LOG = logging.getLogger(__name__) class MeteringJSON(base.BaseAdminNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: List, Show, Create, Delete Metering labels List, Show, Create, Delete Metering labels rules """ @classmethod def setUpClass(cls): super(MeteringJSON, cls).setUpClass() if not test.is_extension_enabled('metering', 'network'): msg = "metering extension not enabled." raise cls.skipException(msg) description = "metering label created by tempest" name = data_utils.rand_name("metering-label") try: cls.metering_label = cls.create_metering_label(name, description) remote_ip_prefix = "10.0.0.0/24" direction = "ingress" cls.metering_label_rule = cls.create_metering_label_rule( remote_ip_prefix, direction, metering_label_id=cls.metering_label['id']) except Exception: LOG.exception('setUpClass failed') cls.tearDownClass() raise def _delete_metering_label(self, metering_label_id): # Deletes a label and verifies if it is deleted or not resp, body = self.admin_client.delete_metering_label(metering_label_id) self.assertEqual(204, resp.status) # Asserting that the label is not found in list after deletion resp, labels = (self.admin_client.list_metering_labels( id=metering_label_id)) self.assertEqual(len(labels['metering_labels']), 0) def _delete_metering_label_rule(self, metering_label_rule_id): # Deletes a rule and verifies if it is deleted or not resp, body = (self.admin_client.delete_metering_label_rule( metering_label_rule_id)) self.assertEqual(204, resp.status) # Asserting that the rule is not found in list after deletion resp, rules = (self.admin_client.list_metering_label_rules( id=metering_label_rule_id)) self.assertEqual(len(rules['metering_label_rules']), 0) @test.attr(type='smoke') def test_list_metering_labels(self): # Verify label filtering resp, body = self.admin_client.list_metering_labels(id=33) self.assertEqual('200', resp['status']) metering_labels = body['metering_labels'] self.assertEqual(0, len(metering_labels)) @test.attr(type='smoke') def test_create_delete_metering_label_with_filters(self): # Creates a label name = data_utils.rand_name('metering-label-') description = "label created by tempest" resp, body = (self.admin_client.create_metering_label(name=name, description=description)) self.assertEqual('201', resp['status']) metering_label = body['metering_label'] self.addCleanup(self._delete_metering_label, metering_label['id']) # Assert whether created labels are found in labels list or fail # if created labels are not found in labels list resp, labels = (self.admin_client.list_metering_labels( id=metering_label['id'])) self.assertEqual(len(labels['metering_labels']), 1) @test.attr(type='smoke') def test_show_metering_label(self): # Verifies the details of a label resp, body = (self.admin_client.show_metering_label( self.metering_label['id'])) self.assertEqual('200', resp['status']) metering_label = body['metering_label'] self.assertEqual(self.metering_label['id'], metering_label['id']) self.assertEqual(self.metering_label['tenant_id'], metering_label['tenant_id']) self.assertEqual(self.metering_label['name'], metering_label['name']) self.assertEqual(self.metering_label['description'], metering_label['description']) @test.attr(type='smoke') def test_list_metering_label_rules(self): # Verify rule filtering resp, body = self.admin_client.list_metering_label_rules(id=33) self.assertEqual('200', resp['status']) metering_label_rules = body['metering_label_rules'] self.assertEqual(0, len(metering_label_rules)) @test.attr(type='smoke') def test_create_delete_metering_label_rule_with_filters(self): # Creates a rule resp, body = (self.admin_client.create_metering_label_rule( remote_ip_prefix="10.0.1.0/24", direction="ingress", metering_label_id=self.metering_label['id'])) self.assertEqual('201', resp['status']) metering_label_rule = body['metering_label_rule'] self.addCleanup(self._delete_metering_label_rule, metering_label_rule['id']) # Assert whether created rules are found in rules list or fail # if created rules are not found in rules list resp, rules = (self.admin_client.list_metering_label_rules( id=metering_label_rule['id'])) self.assertEqual(len(rules['metering_label_rules']), 1) @test.attr(type='smoke') def test_show_metering_label_rule(self): # Verifies the details of a rule resp, body = (self.admin_client.show_metering_label_rule( self.metering_label_rule['id'])) self.assertEqual('200', resp['status']) metering_label_rule = body['metering_label_rule'] self.assertEqual(self.metering_label_rule['id'], metering_label_rule['id']) self.assertEqual(self.metering_label_rule['remote_ip_prefix'], metering_label_rule['remote_ip_prefix']) self.assertEqual(self.metering_label_rule['direction'], metering_label_rule['direction']) self.assertEqual(self.metering_label_rule['metering_label_id'], metering_label_rule['metering_label_id']) self.assertFalse(metering_label_rule['excluded']) class MeteringXML(MeteringJSON): interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/base_security_groups.py0000664000175000017500000000460112332757070026633 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils class BaseSecGroupTest(base.BaseNetworkTest): @classmethod def setUpClass(cls): super(BaseSecGroupTest, cls).setUpClass() def _create_security_group(self): # Create a security group name = data_utils.rand_name('secgroup-') resp, group_create_body = self.client.create_security_group(name=name) self.assertEqual('201', resp['status']) self.addCleanup(self._delete_security_group, group_create_body['security_group']['id']) self.assertEqual(group_create_body['security_group']['name'], name) return group_create_body, name def _delete_security_group(self, secgroup_id): resp, _ = self.client.delete_security_group(secgroup_id) self.assertEqual(204, resp.status) # Asserting that the security group is not found in the list # after deletion resp, list_body = self.client.list_security_groups() self.assertEqual('200', resp['status']) secgroup_list = list() for secgroup in list_body['security_groups']: secgroup_list.append(secgroup['id']) self.assertNotIn(secgroup_id, secgroup_list) def _delete_security_group_rule(self, rule_id): resp, _ = self.client.delete_security_group_rule(rule_id) self.assertEqual(204, resp.status) # Asserting that the security group is not found in the list # after deletion resp, list_body = self.client.list_security_group_rules() self.assertEqual('200', resp['status']) rules_list = list() for rule in list_body['security_group_rules']: rules_list.append(rule['id']) self.assertNotIn(rule_id, rules_list) tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_networks.py0000664000175000017500000003461012332757070025311 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netaddr from tempest.api.network import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class NetworksTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: create a network for a tenant list tenant's networks show a tenant network details create a subnet for a tenant list tenant's subnets show a tenant subnet details network update subnet update delete a network also deletes its subnets All subnet tests are run once with ipv4 and once with ipv6. v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: tenant_network_cidr with a block of cidr's from which smaller blocks can be allocated for tenant ipv4 subnets tenant_network_v6_cidr is the equivalent for ipv6 subnets tenant_network_mask_bits with the mask bits to be used to partition the block defined by tenant_network_cidr tenant_network_v6_mask_bits is the equivalent for ipv6 subnets """ @classmethod @test.safe_setup def setUpClass(cls): super(NetworksTestJSON, cls).setUpClass() cls.network = cls.create_network() cls.name = cls.network['name'] cls.subnet = cls.create_subnet(cls.network) cls.cidr = cls.subnet['cidr'] @test.attr(type='smoke') def test_create_update_delete_network_subnet(self): # Create a network name = data_utils.rand_name('network-') resp, body = self.client.create_network(name=name) self.assertEqual('201', resp['status']) network = body['network'] net_id = network['id'] # Verify network update new_name = "New_network" resp, body = self.client.update_network(net_id, name=new_name) self.assertEqual('200', resp['status']) updated_net = body['network'] self.assertEqual(updated_net['name'], new_name) # Find a cidr that is not in use yet and create a subnet with it subnet = self.create_subnet(network) subnet_id = subnet['id'] # Verify subnet update new_name = "New_subnet" resp, body = self.client.update_subnet(subnet_id, name=new_name) self.assertEqual('200', resp['status']) updated_subnet = body['subnet'] self.assertEqual(updated_subnet['name'], new_name) # Delete subnet and network resp, body = self.client.delete_subnet(subnet_id) self.assertEqual('204', resp['status']) # Remove subnet from cleanup list self.subnets.pop() resp, body = self.client.delete_network(net_id) self.assertEqual('204', resp['status']) @test.attr(type='smoke') def test_show_network(self): # Verify the details of a network resp, body = self.client.show_network(self.network['id']) self.assertEqual('200', resp['status']) network = body['network'] for key in ['id', 'name']: self.assertEqual(network[key], self.network[key]) @test.attr(type='smoke') def test_show_network_fields(self): # Verify specific fields of a network fields = ['id', 'name'] resp, body = self.client.show_network(self.network['id'], fields=fields) self.assertEqual('200', resp['status']) network = body['network'] self.assertEqual(sorted(network.keys()), sorted(fields)) for field_name in fields: self.assertEqual(network[field_name], self.network[field_name]) @test.attr(type='smoke') def test_list_networks(self): # Verify the network exists in the list of all networks resp, body = self.client.list_networks() self.assertEqual('200', resp['status']) networks = [network['id'] for network in body['networks'] if network['id'] == self.network['id']] self.assertNotEmpty(networks, "Created network not found in the list") @test.attr(type='smoke') def test_list_networks_fields(self): # Verify specific fields of the networks fields = ['id', 'name'] resp, body = self.client.list_networks(fields=fields) self.assertEqual('200', resp['status']) networks = body['networks'] self.assertNotEmpty(networks, "Network list returned is empty") for network in networks: self.assertEqual(sorted(network.keys()), sorted(fields)) @test.attr(type='smoke') def test_show_subnet(self): # Verify the details of a subnet resp, body = self.client.show_subnet(self.subnet['id']) self.assertEqual('200', resp['status']) subnet = body['subnet'] self.assertNotEmpty(subnet, "Subnet returned has no fields") for key in ['id', 'cidr']: self.assertIn(key, subnet) self.assertEqual(subnet[key], self.subnet[key]) @test.attr(type='smoke') def test_show_subnet_fields(self): # Verify specific fields of a subnet fields = ['id', 'network_id'] resp, body = self.client.show_subnet(self.subnet['id'], fields=fields) self.assertEqual('200', resp['status']) subnet = body['subnet'] self.assertEqual(sorted(subnet.keys()), sorted(fields)) for field_name in fields: self.assertEqual(subnet[field_name], self.subnet[field_name]) @test.attr(type='smoke') def test_list_subnets(self): # Verify the subnet exists in the list of all subnets resp, body = self.client.list_subnets() self.assertEqual('200', resp['status']) subnets = [subnet['id'] for subnet in body['subnets'] if subnet['id'] == self.subnet['id']] self.assertNotEmpty(subnets, "Created subnet not found in the list") @test.attr(type='smoke') def test_list_subnets_fields(self): # Verify specific fields of subnets fields = ['id', 'network_id'] resp, body = self.client.list_subnets(fields=fields) self.assertEqual('200', resp['status']) subnets = body['subnets'] self.assertNotEmpty(subnets, "Subnet list returned is empty") for subnet in subnets: self.assertEqual(sorted(subnet.keys()), sorted(fields)) def _try_delete_network(self, net_id): # delete network, if it exists try: self.client.delete_network(net_id) # if network is not found, this means it was deleted in the test except exceptions.NotFound: pass @test.attr(type='smoke') def test_delete_network_with_subnet(self): # Creates a network name = data_utils.rand_name('network-') resp, body = self.client.create_network(name=name) self.assertEqual('201', resp['status']) network = body['network'] net_id = network['id'] self.addCleanup(self._try_delete_network, net_id) # Find a cidr that is not in use yet and create a subnet with it subnet = self.create_subnet(network) subnet_id = subnet['id'] # Delete network while the subnet still exists resp, body = self.client.delete_network(net_id) self.assertEqual('204', resp['status']) # Verify that the subnet got automatically deleted. self.assertRaises(exceptions.NotFound, self.client.show_subnet, subnet_id) # Since create_subnet adds the subnet to the delete list, and it is # is actually deleted here - this will create and issue, hence remove # it from the list. self.subnets.pop() class NetworksTestXML(NetworksTestJSON): _interface = 'xml' class BulkNetworkOpsTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: bulk network creation bulk subnet creation bulk port creation list tenant's networks v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: tenant_network_cidr with a block of cidr's from which smaller blocks can be allocated for tenant networks tenant_network_mask_bits with the mask bits to be used to partition the block defined by tenant-network_cidr """ @classmethod @test.safe_setup def setUpClass(cls): super(BulkNetworkOpsTestJSON, cls).setUpClass() cls.network1 = cls.create_network() cls.network2 = cls.create_network() def _delete_networks(self, created_networks): for n in created_networks: resp, body = self.client.delete_network(n['id']) self.assertEqual(204, resp.status) # Asserting that the networks are not found in the list after deletion resp, body = self.client.list_networks() networks_list = [network['id'] for network in body['networks']] for n in created_networks: self.assertNotIn(n['id'], networks_list) def _delete_subnets(self, created_subnets): for n in created_subnets: resp, body = self.client.delete_subnet(n['id']) self.assertEqual(204, resp.status) # Asserting that the subnets are not found in the list after deletion resp, body = self.client.list_subnets() subnets_list = [subnet['id'] for subnet in body['subnets']] for n in created_subnets: self.assertNotIn(n['id'], subnets_list) def _delete_ports(self, created_ports): for n in created_ports: resp, body = self.client.delete_port(n['id']) self.assertEqual(204, resp.status) # Asserting that the ports are not found in the list after deletion resp, body = self.client.list_ports() ports_list = [port['id'] for port in body['ports']] for n in created_ports: self.assertNotIn(n['id'], ports_list) @test.attr(type='smoke') def test_bulk_create_delete_network(self): # Creates 2 networks in one request network_names = [data_utils.rand_name('network-'), data_utils.rand_name('network-')] resp, body = self.client.create_bulk_network(2, network_names) created_networks = body['networks'] self.assertEqual('201', resp['status']) self.addCleanup(self._delete_networks, created_networks) # Asserting that the networks are found in the list after creation resp, body = self.client.list_networks() networks_list = [network['id'] for network in body['networks']] for n in created_networks: self.assertIsNotNone(n['id']) self.assertIn(n['id'], networks_list) @test.attr(type='smoke') def test_bulk_create_delete_subnet(self): # Creates 2 subnets in one request cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr) mask_bits = CONF.network.tenant_network_mask_bits cidrs = [subnet_cidr for subnet_cidr in cidr.subnet(mask_bits)] networks = [self.network1['id'], self.network2['id']] names = [data_utils.rand_name('subnet-') for i in range(len(networks))] subnets_list = [] # TODO(raies): "for IPv6, version list [4, 6] will be used. # and cidr for IPv6 will be of IPv6" ip_version = [4, 4] for i in range(len(names)): p1 = { 'network_id': networks[i], 'cidr': str(cidrs[(i)]), 'name': names[i], 'ip_version': ip_version[i] } subnets_list.append(p1) del subnets_list[1]['name'] resp, body = self.client.create_bulk_subnet(subnets_list) created_subnets = body['subnets'] self.addCleanup(self._delete_subnets, created_subnets) self.assertEqual('201', resp['status']) # Asserting that the subnets are found in the list after creation resp, body = self.client.list_subnets() subnets_list = [subnet['id'] for subnet in body['subnets']] for n in created_subnets: self.assertIsNotNone(n['id']) self.assertIn(n['id'], subnets_list) @test.attr(type='smoke') def test_bulk_create_delete_port(self): # Creates 2 ports in one request networks = [self.network1['id'], self.network2['id']] names = [data_utils.rand_name('port-') for i in range(len(networks))] port_list = [] state = [True, False] for i in range(len(names)): p1 = { 'network_id': networks[i], 'name': names[i], 'admin_state_up': state[i], } port_list.append(p1) del port_list[1]['name'] resp, body = self.client.create_bulk_port(port_list) created_ports = body['ports'] self.addCleanup(self._delete_ports, created_ports) self.assertEqual('201', resp['status']) # Asserting that the ports are found in the list after creation resp, body = self.client.list_ports() ports_list = [port['id'] for port in body['ports']] for n in created_ports: self.assertIsNotNone(n['id']) self.assertIn(n['id'], ports_list) class BulkNetworkOpsTestXML(BulkNetworkOpsTestJSON): _interface = 'xml' class NetworksIpV6TestJSON(NetworksTestJSON): _ip_version = 6 @classmethod def setUpClass(cls): if not CONF.network_feature_enabled.ipv6: skip_msg = "IPv6 Tests are disabled." raise cls.skipException(skip_msg) super(NetworksIpV6TestJSON, cls).setUpClass() class NetworksIpV6TestXML(NetworksIpV6TestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/0000775000175000017500000000000012332757136023113 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/test_dhcp_agent_scheduler.py0000664000175000017500000001021512332757070030652 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.network import base from tempest import test class DHCPAgentSchedulersTestJSON(base.BaseAdminNetworkTest): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(DHCPAgentSchedulersTestJSON, cls).setUpClass() if not test.is_extension_enabled('dhcp_agent_scheduler', 'network'): msg = "dhcp_agent_scheduler extension not enabled." raise cls.skipException(msg) # Create a network and make sure it will be hosted by a # dhcp agent: this is done by creating a regular port cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) cls.cidr = cls.subnet['cidr'] cls.port = cls.create_port(cls.network) @test.attr(type='smoke') def test_list_dhcp_agent_hosting_network(self): resp, body = self.admin_client.list_dhcp_agent_hosting_network( self.network['id']) self.assertEqual(resp['status'], '200') @test.attr(type='smoke') def test_list_networks_hosted_by_one_dhcp(self): resp, body = self.admin_client.list_dhcp_agent_hosting_network( self.network['id']) agents = body['agents'] self.assertIsNotNone(agents) agent = agents[0] self.assertTrue(self._check_network_in_dhcp_agent( self.network['id'], agent)) def _check_network_in_dhcp_agent(self, network_id, agent): network_ids = [] resp, body = self.admin_client.list_networks_hosted_by_one_dhcp_agent( agent['id']) self.assertEqual(resp['status'], '200') networks = body['networks'] for network in networks: network_ids.append(network['id']) return network_id in network_ids @test.attr(type='smoke') def test_add_remove_network_from_dhcp_agent(self): # The agent is now bound to the network, we can free the port self.client.delete_port(self.port['id']) self.ports.remove(self.port) agent = dict() agent['agent_type'] = None resp, body = self.admin_client.list_agents() agents = body['agents'] for a in agents: if a['agent_type'] == 'DHCP agent': agent = a break self.assertEqual(agent['agent_type'], 'DHCP agent', 'Could not find ' 'DHCP agent in agent list though dhcp_agent_scheduler' ' is enabled.') network = self.create_network() network_id = network['id'] if self._check_network_in_dhcp_agent(network_id, agent): self._remove_network_from_dhcp_agent(network_id, agent) self._add_dhcp_agent_to_network(network_id, agent) else: self._add_dhcp_agent_to_network(network_id, agent) self._remove_network_from_dhcp_agent(network_id, agent) def _remove_network_from_dhcp_agent(self, network_id, agent): resp, body = self.admin_client.remove_network_from_dhcp_agent( agent_id=agent['id'], network_id=network_id) self.assertEqual(resp['status'], '204') self.assertFalse(self._check_network_in_dhcp_agent( network_id, agent)) def _add_dhcp_agent_to_network(self, network_id, agent): resp, body = self.admin_client.add_dhcp_agent_to_network( agent['id'], network_id) self.assertEqual(resp['status'], '201') self.assertTrue(self._check_network_in_dhcp_agent( network_id, agent)) class DHCPAgentSchedulersTestXML(DHCPAgentSchedulersTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/test_lbaas_agent_scheduler.py0000664000175000017500000000566012332757070031026 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.network import base from tempest.common.utils import data_utils from tempest import test class LBaaSAgentSchedulerTestJSON(base.BaseAdminNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: List pools the given LBaaS agent is hosting. Show a LBaaS agent hosting the given pool. v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [networki-feature-enabled] section of etc/tempest.conf: api_extensions """ @classmethod @test.safe_setup def setUpClass(cls): super(LBaaSAgentSchedulerTestJSON, cls).setUpClass() if not test.is_extension_enabled('lbaas_agent_scheduler', 'network'): msg = "LBaaS Agent Scheduler Extension not enabled." raise cls.skipException(msg) cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) pool_name = data_utils.rand_name('pool-') cls.pool = cls.create_pool(pool_name, "ROUND_ROBIN", "HTTP", cls.subnet) @test.attr(type='smoke') def test_list_pools_on_lbaas_agent(self): found = False resp, body = self.admin_client.list_agents( agent_type="Loadbalancer agent") self.assertEqual('200', resp['status']) agents = body['agents'] for a in agents: msg = 'Load Balancer agent expected' self.assertEqual(a['agent_type'], 'Loadbalancer agent', msg) resp, body = ( self.admin_client.list_pools_hosted_by_one_lbaas_agent( a['id'])) self.assertEqual('200', resp['status']) pools = body['pools'] if self.pool['id'] in [p['id'] for p in pools]: found = True msg = 'Unable to find Load Balancer agent hosting pool' self.assertTrue(found, msg) @test.attr(type='smoke') def test_show_lbaas_agent_hosting_pool(self): resp, body = self.admin_client.show_lbaas_agent_hosting_pool( self.pool['id']) self.assertEqual('200', resp['status']) self.assertEqual('Loadbalancer agent', body['agent']['agent_type']) class LBaaSAgentSchedulerTestXML(LBaaSAgentSchedulerTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/test_external_network_extension.py0000664000175000017500000001023212332757070032206 0ustar chuckchuck00000000000000# 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 tempest.api.network import base from tempest.common.utils import data_utils class ExternalNetworksTestJSON(base.BaseAdminNetworkTest): _interface = 'json' @classmethod def setUpClass(cls): super(ExternalNetworksTestJSON, cls).setUpClass() cls.network = cls.create_network() def _create_network(self, external=True): post_body = {'name': data_utils.rand_name('network-')} if external: post_body['router:external'] = external resp, body = self.admin_client.create_network(**post_body) network = body['network'] self.assertEqual('201', resp['status']) self.addCleanup(self.admin_client.delete_network, network['id']) return network def test_create_external_network(self): # Create a network as an admin user specifying the # external network extension attribute ext_network = self._create_network() # Verifies router:external parameter self.assertIsNotNone(ext_network['id']) self.assertTrue(ext_network['router:external']) def test_update_external_network(self): # Update a network as an admin user specifying the # external network extension attribute network = self._create_network(external=False) self.assertFalse(network.get('router:external', False)) update_body = {'router:external': True} resp, body = self.admin_client.update_network(network['id'], **update_body) self.assertEqual('200', resp['status']) updated_network = body['network'] # Verify that router:external parameter was updated self.assertTrue(updated_network['router:external']) def test_list_external_networks(self): # Create external_net external_network = self._create_network() # List networks as a normal user and confirm the external # network extension attribute is returned for those networks # that were created as external resp, body = self.client.list_networks() self.assertEqual('200', resp['status']) networks_list = [net['id'] for net in body['networks']] self.assertIn(external_network['id'], networks_list) self.assertIn(self.network['id'], networks_list) for net in body['networks']: if net['id'] == self.network['id']: self.assertFalse(net['router:external']) elif net['id'] == external_network['id']: self.assertTrue(net['router:external']) def test_show_external_networks_attribute(self): # Create external_net external_network = self._create_network() # Show an external network as a normal user and confirm the # external network extension attribute is returned. resp, body = self.client.show_network(external_network['id']) self.assertEqual('200', resp['status']) show_ext_net = body['network'] self.assertEqual(external_network['name'], show_ext_net['name']) self.assertEqual(external_network['id'], show_ext_net['id']) self.assertTrue(show_ext_net['router:external']) resp, body = self.client.show_network(self.network['id']) self.assertEqual('200', resp['status']) show_net = body['network'] # Verify with show that router:external is False for network self.assertEqual(self.network['name'], show_net['name']) self.assertEqual(self.network['id'], show_net['id']) self.assertFalse(show_net['router:external']) class ExternalNetworksTestXML(ExternalNetworksTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/__init__.py0000664000175000017500000000000012332757070025207 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/test_quotas.py0000664000175000017500000000710112332757070026034 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import test class QuotasTest(base.BaseAdminNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: list quotas for tenants who have non-default quota values show quotas for a specified tenant update quotas for a specified tenant reset quotas to default values for a specified tenant v2.0 of the API is assumed. It is also assumed that the per-tenant quota extension API is configured in /etc/neutron/neutron.conf as follows: quota_driver = neutron.db.quota_db.DbQuotaDriver """ @classmethod def setUpClass(cls): super(QuotasTest, cls).setUpClass() if not test.is_extension_enabled('quotas', 'network'): msg = "quotas extension not enabled." raise cls.skipException(msg) cls.identity_admin_client = cls.os_adm.identity_client @test.attr(type='gate') def test_quotas(self): # Add a tenant to conduct the test test_tenant = data_utils.rand_name('test_tenant_') test_description = data_utils.rand_name('desc_') _, tenant = self.identity_admin_client.create_tenant( name=test_tenant, description=test_description) tenant_id = tenant['id'] self.addCleanup(self.identity_admin_client.delete_tenant, tenant_id) # Change quotas for tenant new_quotas = {'network': 0, 'security_group': 0} resp, quota_set = self.admin_client.update_quotas(tenant_id, **new_quotas) self.assertEqual('200', resp['status']) self.addCleanup(self.admin_client.reset_quotas, tenant_id) self.assertEqual(0, quota_set['network']) self.assertEqual(0, quota_set['security_group']) # Confirm our tenant is listed among tenants with non default quotas resp, non_default_quotas = self.admin_client.list_quotas() self.assertEqual('200', resp['status']) found = False for qs in non_default_quotas['quotas']: if qs['tenant_id'] == tenant_id: found = True self.assertTrue(found) # Confirm from APi quotas were changed as requested for tenant resp, quota_set = self.admin_client.show_quotas(tenant_id) quota_set = quota_set['quota'] self.assertEqual('200', resp['status']) self.assertEqual(0, quota_set['network']) self.assertEqual(0, quota_set['security_group']) # Reset quotas to default and confirm resp, body = self.admin_client.reset_quotas(tenant_id) self.assertEqual('204', resp['status']) resp, non_default_quotas = self.admin_client.list_quotas() self.assertEqual('200', resp['status']) for q in non_default_quotas['quotas']: self.assertNotEqual(tenant_id, q['tenant_id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/test_agent_management.py0000664000175000017500000000732312332757070030020 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.network import base from tempest.common import tempest_fixtures as fixtures from tempest import test class AgentManagementTestJSON(base.BaseAdminNetworkTest): _interface = 'json' @classmethod def setUpClass(cls): super(AgentManagementTestJSON, cls).setUpClass() if not test.is_extension_enabled('agent', 'network'): msg = "agent extension not enabled." raise cls.skipException(msg) resp, body = cls.admin_client.list_agents() agents = body['agents'] cls.agent = agents[0] @test.attr(type='smoke') def test_list_agent(self): resp, body = self.admin_client.list_agents() self.assertEqual('200', resp['status']) agents = body['agents'] # Hearthbeats must be excluded from comparison self.agent.pop('heartbeat_timestamp', None) self.agent.pop('configurations', None) for agent in agents: agent.pop('heartbeat_timestamp', None) agent.pop('configurations', None) self.assertIn(self.agent, agents) @test.attr(type=['smoke']) def test_list_agents_non_admin(self): resp, body = self.client.list_agents() self.assertEqual('200', resp['status']) self.assertEqual(len(body["agents"]), 0) @test.attr(type='smoke') def test_show_agent(self): resp, body = self.admin_client.show_agent(self.agent['id']) agent = body['agent'] self.assertEqual('200', resp['status']) self.assertEqual(agent['id'], self.agent['id']) @test.attr(type='smoke') def test_update_agent_status(self): origin_status = self.agent['admin_state_up'] # Try to update the 'admin_state_up' to the original # one to avoid the negative effect. agent_status = {'admin_state_up': origin_status} resp, body = self.admin_client.update_agent(agent_id=self.agent['id'], agent_info=agent_status) updated_status = body['agent']['admin_state_up'] self.assertEqual('200', resp['status']) self.assertEqual(origin_status, updated_status) @test.attr(type='smoke') def test_update_agent_description(self): self.useFixture(fixtures.LockFixture('agent_description')) description = 'description for update agent.' agent_description = {'description': description} resp, body = self.admin_client.update_agent( agent_id=self.agent['id'], agent_info=agent_description) self.assertEqual('200', resp['status']) self.addCleanup(self._restore_agent) updated_description = body['agent']['description'] self.assertEqual(updated_description, description) def _restore_agent(self): """ Restore the agent description after update test. """ description = self.agent['description'] or '' origin_agent = {'description': description} self.admin_client.update_agent(agent_id=self.agent['id'], agent_info=origin_agent) class AgentManagementTestXML(AgentManagementTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/test_l3_agent_scheduler.py0000664000175000017500000000641212332757070030256 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.network import base from tempest.common.utils import data_utils from tempest import test class L3AgentSchedulerTestJSON(base.BaseAdminNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: List routers that the given L3 agent is hosting. List L3 agents hosting the given router. Add and Remove Router to L3 agent v2.0 of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: """ @classmethod def setUpClass(cls): super(L3AgentSchedulerTestJSON, cls).setUpClass() if not test.is_extension_enabled('l3_agent_scheduler', 'network'): msg = "L3 Agent Scheduler Extension not enabled." raise cls.skipException(msg) # Trying to get agent details for L3 Agent resp, body = cls.admin_client.list_agents() agents = body['agents'] for agent in agents: if agent['agent_type'] == 'L3 agent': cls.agent = agent break else: msg = "L3 Agent not found" raise cls.skipException(msg) @test.attr(type='smoke') def test_list_routers_on_l3_agent(self): resp, body = self.admin_client.list_routers_on_l3_agent( self.agent['id']) self.assertEqual('200', resp['status']) @test.attr(type='smoke') def test_add_list_remove_router_on_l3_agent(self): l3_agent_ids = list() name = data_utils.rand_name('router1-') resp, router = self.client.create_router(name) self.addCleanup(self.client.delete_router, router['router']['id']) resp, body = self.admin_client.add_router_to_l3_agent( self.agent['id'], router['router']['id']) self.assertEqual('201', resp['status']) resp, body = self.admin_client.list_l3_agents_hosting_router( router['router']['id']) self.assertEqual('200', resp['status']) for agent in body['agents']: l3_agent_ids.append(agent['id']) self.assertIn('agent_type', agent) self.assertEqual('L3 agent', agent['agent_type']) self.assertIn(self.agent['id'], l3_agent_ids) del l3_agent_ids[:] resp, body = self.admin_client.remove_router_from_l3_agent( self.agent['id'], router['router']['id']) self.assertEqual('204', resp['status']) # NOTE(afazekas): The deletion not asserted, because neutron # is not forbidden to reschedule the router to the same agent class L3AgentSchedulerTestXML(L3AgentSchedulerTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/admin/test_load_balancer_admin_actions.py0000664000175000017500000001220712332757070032161 0ustar chuckchuck00000000000000# Copyright 2014 Mirantis.inc # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import test class LoadBalancerAdminTestJSON(base.BaseAdminNetworkTest): _interface = 'json' """ Test admin actions for load balancer. Create VIP for another tenant Create health monitor for another tenant """ @classmethod @test.safe_setup def setUpClass(cls): super(LoadBalancerAdminTestJSON, cls).setUpClass() if not test.is_extension_enabled('lbaas', 'network'): msg = "lbaas extension not enabled." raise cls.skipException(msg) cls.force_tenant_isolation = True manager = cls.get_client_manager() cls.client = manager.network_client cls.tenant_id = cls.isolated_creds.get_primary_creds().tenant_id cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) cls.pool = cls.create_pool(data_utils.rand_name('pool-'), "ROUND_ROBIN", "HTTP", cls.subnet) @test.attr(type='smoke') def test_create_vip_as_admin_for_another_tenant(self): name = data_utils.rand_name('vip-') resp, body = self.admin_client.create_pool( name=data_utils.rand_name('pool-'), lb_method="ROUND_ROBIN", protocol="HTTP", subnet_id=self.subnet['id'], tenant_id=self.tenant_id) self.assertEqual('201', resp['status']) pool = body['pool'] self.addCleanup(self.admin_client.delete_pool, pool['id']) resp, body = self.admin_client.create_vip(name=name, protocol="HTTP", protocol_port=80, subnet_id=self.subnet['id'], pool_id=pool['id'], tenant_id=self.tenant_id) self.assertEqual('201', resp['status']) vip = body['vip'] self.addCleanup(self.admin_client.delete_vip, vip['id']) self.assertIsNotNone(vip['id']) self.assertEqual(self.tenant_id, vip['tenant_id']) resp, body = self.client.show_vip(vip['id']) self.assertEqual('200', resp['status']) show_vip = body['vip'] self.assertEqual(vip['id'], show_vip['id']) self.assertEqual(vip['name'], show_vip['name']) @test.attr(type='smoke') def test_create_health_monitor_as_admin_for_another_tenant(self): resp, body = ( self.admin_client.create_health_monitor(delay=4, max_retries=3, type="TCP", timeout=1, tenant_id=self.tenant_id)) self.assertEqual('201', resp['status']) health_monitor = body['health_monitor'] self.addCleanup(self.admin_client.delete_health_monitor, health_monitor['id']) self.assertIsNotNone(health_monitor['id']) self.assertEqual(self.tenant_id, health_monitor['tenant_id']) resp, body = self.client.show_health_monitor(health_monitor['id']) self.assertEqual('200', resp['status']) show_health_monitor = body['health_monitor'] self.assertEqual(health_monitor['id'], show_health_monitor['id']) @test.attr(type='smoke') def test_create_pool_from_admin_user_other_tenant(self): resp, body = self.admin_client.create_pool( name=data_utils.rand_name('pool-'), lb_method="ROUND_ROBIN", protocol="HTTP", subnet_id=self.subnet['id'], tenant_id=self.tenant_id) self.assertEqual('201', resp['status']) pool = body['pool'] self.addCleanup(self.admin_client.delete_pool, pool['id']) self.assertIsNotNone(pool['id']) self.assertEqual(self.tenant_id, pool['tenant_id']) @test.attr(type='smoke') def test_create_member_from_admin_user_other_tenant(self): resp, body = self.admin_client.create_member( address="10.0.9.47", protocol_port=80, pool_id=self.pool['id'], tenant_id=self.tenant_id) self.assertEqual('201', resp['status']) member = body['member'] self.addCleanup(self.admin_client.delete_member, member['id']) self.assertIsNotNone(member['id']) self.assertEqual(self.tenant_id, member['tenant_id']) class LoadBalancerAdminTestXML(LoadBalancerAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_load_balancer.py0000664000175000017500000005204312332757070026203 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import test class LoadBalancerTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: create vIP, and Pool show vIP list vIP update vIP delete vIP update pool delete pool show pool list pool health monitoring operations """ @classmethod @test.safe_setup def setUpClass(cls): super(LoadBalancerTestJSON, cls).setUpClass() if not test.is_extension_enabled('lbaas', 'network'): msg = "lbaas extension not enabled." raise cls.skipException(msg) cls.network = cls.create_network() cls.name = cls.network['name'] cls.subnet = cls.create_subnet(cls.network) pool_name = data_utils.rand_name('pool-') vip_name = data_utils.rand_name('vip-') cls.pool = cls.create_pool(pool_name, "ROUND_ROBIN", "HTTP", cls.subnet) cls.vip = cls.create_vip(name=vip_name, protocol="HTTP", protocol_port=80, subnet=cls.subnet, pool=cls.pool) cls.member = cls.create_member(80, cls.pool) cls.health_monitor = cls.create_health_monitor(delay=4, max_retries=3, Type="TCP", timeout=1) def _check_list_with_filter(self, obj_name, attr_exceptions, **kwargs): create_obj = getattr(self.client, 'create_' + obj_name) delete_obj = getattr(self.client, 'delete_' + obj_name) list_objs = getattr(self.client, 'list_' + obj_name + 's') resp, body = create_obj(**kwargs) self.assertEqual('201', resp['status']) obj = body[obj_name] self.addCleanup(delete_obj, obj['id']) for key, value in obj.iteritems(): # It is not relevant to filter by all arguments. That is why # there is a list of attr to except if key not in attr_exceptions: resp, body = list_objs(**{key: value}) self.assertEqual('200', resp['status']) objs = [v[key] for v in body[obj_name + 's']] self.assertIn(value, objs) @test.attr(type='smoke') def test_list_vips(self): # Verify the vIP exists in the list of all vIPs resp, body = self.client.list_vips() self.assertEqual('200', resp['status']) vips = body['vips'] self.assertIn(self.vip['id'], [v['id'] for v in vips]) @test.attr(type='smoke') def test_list_vips_with_filter(self): name = data_utils.rand_name('vip-') resp, body = self.client.create_pool( name=data_utils.rand_name("pool-"), lb_method="ROUND_ROBIN", protocol="HTTPS", subnet_id=self.subnet['id']) self.assertEqual('201', resp['status']) pool = body['pool'] self.addCleanup(self.client.delete_pool, pool['id']) attr_exceptions = ['status', 'session_persistence', 'status_description'] self._check_list_with_filter( 'vip', attr_exceptions, name=name, protocol="HTTPS", protocol_port=81, subnet_id=self.subnet['id'], pool_id=pool['id'], description=data_utils.rand_name('description-'), admin_state_up=False) @test.attr(type='smoke') def test_create_update_delete_pool_vip(self): # Creates a vip name = data_utils.rand_name('vip-') address = self.subnet['allocation_pools'][0]['end'] resp, body = self.client.create_pool( name=data_utils.rand_name("pool-"), lb_method='ROUND_ROBIN', protocol='HTTP', subnet_id=self.subnet['id']) pool = body['pool'] resp, body = self.client.create_vip(name=name, protocol="HTTP", protocol_port=80, subnet_id=self.subnet['id'], pool_id=pool['id'], address=address) self.assertEqual('201', resp['status']) vip = body['vip'] vip_id = vip['id'] # Confirm VIP's address correctness with a show resp, body = self.client.show_vip(vip_id) self.assertEqual('200', resp['status']) vip = body['vip'] self.assertEqual(address, vip['address']) # Verification of vip update new_name = "New_vip" new_description = "New description" persistence_type = "HTTP_COOKIE" update_data = {"session_persistence": { "type": persistence_type}} resp, body = self.client.update_vip(vip_id, name=new_name, description=new_description, connection_limit=10, admin_state_up=False, **update_data) self.assertEqual('200', resp['status']) updated_vip = body['vip'] self.assertEqual(new_name, updated_vip['name']) self.assertEqual(new_description, updated_vip['description']) self.assertEqual(10, updated_vip['connection_limit']) self.assertFalse(updated_vip['admin_state_up']) self.assertEqual(persistence_type, updated_vip['session_persistence']['type']) # Verification of vip delete resp, body = self.client.delete_vip(vip['id']) self.assertEqual('204', resp['status']) self.client.wait_for_resource_deletion('vip', vip['id']) # Verification of pool update new_name = "New_pool" resp, body = self.client.update_pool(pool['id'], name=new_name, description="new_description", lb_method='LEAST_CONNECTIONS') self.assertEqual('200', resp['status']) updated_pool = body['pool'] self.assertEqual(new_name, updated_pool['name']) self.assertEqual('new_description', updated_pool['description']) self.assertEqual('LEAST_CONNECTIONS', updated_pool['lb_method']) # Verification of pool delete resp, body = self.client.delete_pool(pool['id']) self.assertEqual('204', resp['status']) @test.attr(type='smoke') def test_show_vip(self): # Verifies the details of a vip resp, body = self.client.show_vip(self.vip['id']) self.assertEqual('200', resp['status']) vip = body['vip'] for key, value in vip.iteritems(): # 'status' should not be confirmed in api tests if key != 'status': self.assertEqual(self.vip[key], value) @test.attr(type='smoke') def test_show_pool(self): # Here we need to new pool without any dependence with vips resp, body = self.client.create_pool( name=data_utils.rand_name("pool-"), lb_method='ROUND_ROBIN', protocol='HTTP', subnet_id=self.subnet['id']) self.assertEqual('201', resp['status']) pool = body['pool'] self.addCleanup(self.client.delete_pool, pool['id']) # Verifies the details of a pool resp, body = self.client.show_pool(pool['id']) self.assertEqual('200', resp['status']) shown_pool = body['pool'] for key, value in pool.iteritems(): # 'status' should not be confirmed in api tests if key != 'status': self.assertEqual(value, shown_pool[key]) @test.attr(type='smoke') def test_list_pools(self): # Verify the pool exists in the list of all pools resp, body = self.client.list_pools() self.assertEqual('200', resp['status']) pools = body['pools'] self.assertIn(self.pool['id'], [p['id'] for p in pools]) @test.attr(type='smoke') def test_list_pools_with_filters(self): attr_exceptions = ['status', 'vip_id', 'members', 'provider', 'status_description'] self._check_list_with_filter( 'pool', attr_exceptions, name=data_utils.rand_name("pool-"), lb_method="ROUND_ROBIN", protocol="HTTPS", subnet_id=self.subnet['id'], description=data_utils.rand_name('description-'), admin_state_up=False) @test.attr(type='smoke') def test_list_members(self): # Verify the member exists in the list of all members resp, body = self.client.list_members() self.assertEqual('200', resp['status']) members = body['members'] self.assertIn(self.member['id'], [m['id'] for m in members]) @test.attr(type='smoke') def test_list_members_with_filters(self): attr_exceptions = ['status', 'status_description'] self._check_list_with_filter('member', attr_exceptions, address="10.0.9.47", protocol_port=80, pool_id=self.pool['id']) @test.attr(type='smoke') def test_create_update_delete_member(self): # Creates a member resp, body = self.client.create_member(address="10.0.9.47", protocol_port=80, pool_id=self.pool['id']) self.assertEqual('201', resp['status']) member = body['member'] # Verification of member update resp, body = self.client.update_member(member['id'], admin_state_up=False) self.assertEqual('200', resp['status']) updated_member = body['member'] self.assertFalse(updated_member['admin_state_up']) # Verification of member delete resp, body = self.client.delete_member(member['id']) self.assertEqual('204', resp['status']) @test.attr(type='smoke') def test_show_member(self): # Verifies the details of a member resp, body = self.client.show_member(self.member['id']) self.assertEqual('200', resp['status']) member = body['member'] for key, value in member.iteritems(): # 'status' should not be confirmed in api tests if key != 'status': self.assertEqual(self.member[key], value) @test.attr(type='smoke') def test_list_health_monitors(self): # Verify the health monitor exists in the list of all health monitors resp, body = self.client.list_health_monitors() self.assertEqual('200', resp['status']) health_monitors = body['health_monitors'] self.assertIn(self.health_monitor['id'], [h['id'] for h in health_monitors]) @test.attr(type='smoke') def test_list_health_monitors_with_filters(self): attr_exceptions = ['status', 'status_description', 'pools'] self._check_list_with_filter('health_monitor', attr_exceptions, delay=5, max_retries=4, type="TCP", timeout=2) @test.attr(type='smoke') def test_create_update_delete_health_monitor(self): # Creates a health_monitor resp, body = self.client.create_health_monitor(delay=4, max_retries=3, type="TCP", timeout=1) self.assertEqual('201', resp['status']) health_monitor = body['health_monitor'] # Verification of health_monitor update resp, body = (self.client.update_health_monitor (health_monitor['id'], admin_state_up=False)) self.assertEqual('200', resp['status']) updated_health_monitor = body['health_monitor'] self.assertFalse(updated_health_monitor['admin_state_up']) # Verification of health_monitor delete resp, body = self.client.delete_health_monitor(health_monitor['id']) self.assertEqual('204', resp['status']) @test.attr(type='smoke') def test_create_health_monitor_http_type(self): hm_type = "HTTP" resp, body = self.client.create_health_monitor(delay=4, max_retries=3, type=hm_type, timeout=1) self.assertEqual('201', resp['status']) health_monitor = body['health_monitor'] self.addCleanup(self.client.delete_health_monitor, health_monitor['id']) self.assertEqual(hm_type, health_monitor['type']) @test.attr(type='smoke') def test_update_health_monitor_http_method(self): resp, body = self.client.create_health_monitor(delay=4, max_retries=3, type="HTTP", timeout=1) self.assertEqual('201', resp['status']) health_monitor = body['health_monitor'] self.addCleanup(self.client.delete_health_monitor, health_monitor['id']) resp, body = (self.client.update_health_monitor (health_monitor['id'], http_method="POST", url_path="/home/user", expected_codes="290")) self.assertEqual('200', resp['status']) updated_health_monitor = body['health_monitor'] self.assertEqual("POST", updated_health_monitor['http_method']) self.assertEqual("/home/user", updated_health_monitor['url_path']) self.assertEqual("290", updated_health_monitor['expected_codes']) @test.attr(type='smoke') def test_show_health_monitor(self): # Verifies the details of a health_monitor resp, body = self.client.show_health_monitor(self.health_monitor['id']) self.assertEqual('200', resp['status']) health_monitor = body['health_monitor'] for key, value in health_monitor.iteritems(): # 'status' should not be confirmed in api tests if key != 'status': self.assertEqual(self.health_monitor[key], value) @test.attr(type='smoke') def test_associate_disassociate_health_monitor_with_pool(self): # Verify that a health monitor can be associated with a pool resp, body = (self.client.associate_health_monitor_with_pool (self.health_monitor['id'], self.pool['id'])) self.assertEqual('201', resp['status']) resp, body = self.client.show_health_monitor( self.health_monitor['id']) health_monitor = body['health_monitor'] resp, body = self.client.show_pool(self.pool['id']) pool = body['pool'] self.assertIn(pool['id'], [p['pool_id'] for p in health_monitor['pools']]) self.assertIn(health_monitor['id'], pool['health_monitors']) # Verify that a health monitor can be disassociated from a pool resp, body = (self.client.disassociate_health_monitor_with_pool (self.health_monitor['id'], self.pool['id'])) self.assertEqual('204', resp['status']) resp, body = self.client.show_pool(self.pool['id']) pool = body['pool'] resp, body = self.client.show_health_monitor( self.health_monitor['id']) health_monitor = body['health_monitor'] self.assertNotIn(health_monitor['id'], pool['health_monitors']) self.assertNotIn(pool['id'], [p['pool_id'] for p in health_monitor['pools']]) @test.attr(type='smoke') def test_get_lb_pool_stats(self): # Verify the details of pool stats resp, body = self.client.list_lb_pool_stats(self.pool['id']) self.assertEqual('200', resp['status']) stats = body['stats'] self.assertIn("bytes_in", stats) self.assertIn("total_connections", stats) self.assertIn("active_connections", stats) self.assertIn("bytes_out", stats) @test.attr(type='smoke') def test_update_list_of_health_monitors_associated_with_pool(self): resp, _ = (self.client.associate_health_monitor_with_pool (self.health_monitor['id'], self.pool['id'])) self.assertEqual('201', resp['status']) resp, _ = self.client.update_health_monitor( self.health_monitor['id'], admin_state_up=False) self.assertEqual('200', resp['status']) resp, body = self.client.show_pool(self.pool['id']) self.assertEqual('200', resp['status']) health_monitors = body['pool']['health_monitors'] for health_monitor_id in health_monitors: resp, body = self.client.show_health_monitor(health_monitor_id) self.assertEqual('200', resp['status']) self.assertFalse(body['health_monitor']['admin_state_up']) resp, _ = (self.client.disassociate_health_monitor_with_pool (self.health_monitor['id'], self.pool['id'])) self.assertEqual('204', resp['status']) @test.attr(type='smoke') def test_update_admin_state_up_of_pool(self): resp, _ = self.client.update_pool(self.pool['id'], admin_state_up=False) self.assertEqual('200', resp['status']) resp, body = self.client.show_pool(self.pool['id']) self.assertEqual('200', resp['status']) pool = body['pool'] self.assertFalse(pool['admin_state_up']) @test.attr(type='smoke') def test_show_vip_associated_with_pool(self): resp, body = self.client.show_pool(self.pool['id']) self.assertEqual('200', resp['status']) pool = body['pool'] resp, body = self.client.show_vip(pool['vip_id']) self.assertEqual('200', resp['status']) vip = body['vip'] self.assertEqual(self.vip['name'], vip['name']) self.assertEqual(self.vip['id'], vip['id']) @test.attr(type='smoke') def test_show_members_associated_with_pool(self): resp, body = self.client.show_pool(self.pool['id']) self.assertEqual('200', resp['status']) members = body['pool']['members'] for member_id in members: resp, body = self.client.show_member(member_id) self.assertEqual('200', resp['status']) self.assertIsNotNone(body['member']['status']) self.assertEqual(member_id, body['member']['id']) self.assertIsNotNone(body['member']['admin_state_up']) @test.attr(type='smoke') def test_update_pool_related_to_member(self): # Create new pool resp, body = self.client.create_pool( name=data_utils.rand_name("pool-"), lb_method='ROUND_ROBIN', protocol='HTTP', subnet_id=self.subnet['id']) self.assertEqual('201', resp['status']) new_pool = body['pool'] self.addCleanup(self.client.delete_pool, new_pool['id']) # Update member with new pool's id resp, body = self.client.update_member(self.member['id'], pool_id=new_pool['id']) self.assertEqual('200', resp['status']) # Confirm with show that pool_id change resp, body = self.client.show_member(self.member['id']) member = body['member'] self.assertEqual(member['pool_id'], new_pool['id']) # Update member with old pool id, this is needed for clean up resp, body = self.client.update_member(self.member['id'], pool_id=self.pool['id']) self.assertEqual('200', resp['status']) @test.attr(type='smoke') def test_update_member_weight(self): resp, _ = self.client.update_member(self.member['id'], weight=2) self.assertEqual('200', resp['status']) resp, body = self.client.show_member(self.member['id']) self.assertEqual('200', resp['status']) member = body['member'] self.assertEqual(2, member['weight']) class LoadBalancerTestXML(LoadBalancerTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_security_groups.py0000664000175000017500000001335312332757070026704 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base_security_groups as base from tempest.common.utils import data_utils from tempest import test class SecGroupTest(base.BaseSecGroupTest): _interface = 'json' @classmethod def setUpClass(cls): super(SecGroupTest, cls).setUpClass() if not test.is_extension_enabled('security-group', 'network'): msg = "security-group extension not enabled." raise cls.skipException(msg) @test.attr(type='smoke') def test_list_security_groups(self): # Verify the that security group belonging to tenant exist in list resp, body = self.client.list_security_groups() self.assertEqual('200', resp['status']) security_groups = body['security_groups'] found = None for n in security_groups: if (n['name'] == 'default'): found = n['id'] msg = "Security-group list doesn't contain default security-group" self.assertIsNotNone(found, msg) @test.attr(type='smoke') def test_create_list_update_show_delete_security_group(self): group_create_body, name = self._create_security_group() # List security groups and verify if created group is there in response resp, list_body = self.client.list_security_groups() self.assertEqual('200', resp['status']) secgroup_list = list() for secgroup in list_body['security_groups']: secgroup_list.append(secgroup['id']) self.assertIn(group_create_body['security_group']['id'], secgroup_list) # Update the security group new_name = data_utils.rand_name('security-') new_description = data_utils.rand_name('security-description') resp, update_body = self.client.update_security_group( group_create_body['security_group']['id'], name=new_name, description=new_description) # Verify if security group is updated self.assertEqual('200', resp['status']) self.assertEqual(update_body['security_group']['name'], new_name) self.assertEqual(update_body['security_group']['description'], new_description) # Show details of the updated security group resp, show_body = self.client.show_security_group( group_create_body['security_group']['id']) self.assertEqual(show_body['security_group']['name'], new_name) self.assertEqual(show_body['security_group']['description'], new_description) @test.attr(type='smoke') def test_create_show_delete_security_group_rule(self): group_create_body, _ = self._create_security_group() # Create rules for each protocol protocols = ['tcp', 'udp', 'icmp'] for protocol in protocols: resp, rule_create_body = self.client.create_security_group_rule( security_group_id=group_create_body['security_group']['id'], protocol=protocol, direction='ingress' ) self.assertEqual('201', resp['status']) self.addCleanup(self._delete_security_group_rule, rule_create_body['security_group_rule']['id'] ) # Show details of the created security rule resp, show_rule_body = self.client.show_security_group_rule( rule_create_body['security_group_rule']['id'] ) self.assertEqual('200', resp['status']) # List rules and verify created rule is in response resp, rule_list_body = self.client.list_security_group_rules() self.assertEqual('200', resp['status']) rule_list = [rule['id'] for rule in rule_list_body['security_group_rules']] self.assertIn(rule_create_body['security_group_rule']['id'], rule_list) @test.attr(type='smoke') def test_create_security_group_rule_with_additional_args(self): # Verify creating security group rule with the following # arguments works: "protocol": "tcp", "port_range_max": 77, # "port_range_min": 77, "direction":"ingress". group_create_body, _ = self._create_security_group() direction = 'ingress' protocol = 'tcp' port_range_min = 77 port_range_max = 77 resp, rule_create_body = self.client.create_security_group_rule( security_group_id=group_create_body['security_group']['id'], direction=direction, protocol=protocol, port_range_min=port_range_min, port_range_max=port_range_max ) self.assertEqual('201', resp['status']) sec_group_rule = rule_create_body['security_group_rule'] self.addCleanup(self._delete_security_group_rule, sec_group_rule['id'] ) self.assertEqual(sec_group_rule['direction'], direction) self.assertEqual(sec_group_rule['protocol'], protocol) self.assertEqual(int(sec_group_rule['port_range_min']), port_range_min) self.assertEqual(int(sec_group_rule['port_range_max']), port_range_max) class SecGroupTestXML(SecGroupTest): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/test_fwaas_extensions.py0000664000175000017500000002164412332757070027020 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.network import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class FWaaSExtensionTestJSON(base.BaseNetworkTest): _interface = 'json' """ Tests the following operations in the Neutron API using the REST client for Neutron: List firewall rules Create firewall rule Update firewall rule Delete firewall rule Show firewall rule List firewall policies Create firewall policy Update firewall policy Delete firewall policy Show firewall policy List firewall Create firewall Update firewall Delete firewall Show firewall """ @classmethod def setUpClass(cls): super(FWaaSExtensionTestJSON, cls).setUpClass() if not test.is_extension_enabled('fwaas', 'network'): msg = "FWaaS Extension not enabled." raise cls.skipException(msg) cls.fw_rule = cls.create_firewall_rule("allow", "tcp") cls.fw_policy = cls.create_firewall_policy() def _try_delete_policy(self, policy_id): # delete policy, if it exists try: self.client.delete_firewall_policy(policy_id) # if policy is not found, this means it was deleted in the test except exceptions.NotFound: pass def _try_delete_firewall(self, fw_id): # delete firewall, if it exists try: self.client.delete_firewall(fw_id) # if firewall is not found, this means it was deleted in the test except exceptions.NotFound: pass self.client.wait_for_resource_deletion('firewall', fw_id) def _wait_for_active(self, fw_id): def _wait(): resp, firewall = self.client.show_firewall(fw_id) self.assertEqual('200', resp['status']) firewall = firewall['firewall'] return firewall['status'] == 'ACTIVE' if not test.call_until_true(_wait, CONF.network.build_timeout, CONF.network.build_interval): m = 'Timed out waiting for firewall %s to become ACTIVE.' % fw_id raise exceptions.TimeoutException(m) @test.attr(type='smoke') def test_list_firewall_rules(self): # List firewall rules resp, fw_rules = self.client.list_firewall_rules() self.assertEqual('200', resp['status']) fw_rules = fw_rules['firewall_rules'] self.assertIn((self.fw_rule['id'], self.fw_rule['name'], self.fw_rule['action'], self.fw_rule['protocol'], self.fw_rule['ip_version'], self.fw_rule['enabled']), [(m['id'], m['name'], m['action'], m['protocol'], m['ip_version'], m['enabled']) for m in fw_rules]) @test.attr(type='smoke') def test_create_update_delete_firewall_rule(self): # Create firewall rule resp, body = self.client.create_firewall_rule( name=data_utils.rand_name("fw-rule"), action="allow", protocol="tcp") self.assertEqual('201', resp['status']) fw_rule_id = body['firewall_rule']['id'] # Update firewall rule resp, body = self.client.update_firewall_rule(fw_rule_id, shared=True) self.assertEqual('200', resp['status']) self.assertTrue(body["firewall_rule"]['shared']) # Delete firewall rule resp, _ = self.client.delete_firewall_rule(fw_rule_id) self.assertEqual('204', resp['status']) # Confirm deletion resp, fw_rules = self.client.list_firewall_rules() self.assertNotIn(fw_rule_id, [m['id'] for m in fw_rules['firewall_rules']]) @test.attr(type='smoke') def test_show_firewall_rule(self): # show a created firewall rule resp, fw_rule = self.client.show_firewall_rule(self.fw_rule['id']) self.assertEqual('200', resp['status']) for key, value in fw_rule['firewall_rule'].iteritems(): self.assertEqual(self.fw_rule[key], value) @test.attr(type='smoke') def test_list_firewall_policies(self): resp, fw_policies = self.client.list_firewall_policies() self.assertEqual('200', resp['status']) fw_policies = fw_policies['firewall_policies'] self.assertIn((self.fw_policy['id'], self.fw_policy['name'], self.fw_policy['firewall_rules']), [(m['id'], m['name'], m['firewall_rules']) for m in fw_policies]) @test.attr(type='smoke') def test_create_update_delete_firewall_policy(self): # Create firewall policy resp, body = self.client.create_firewall_policy( name=data_utils.rand_name("fw-policy")) self.assertEqual('201', resp['status']) fw_policy_id = body['firewall_policy']['id'] self.addCleanup(self._try_delete_policy, fw_policy_id) # Update firewall policy resp, body = self.client.update_firewall_policy(fw_policy_id, shared=True, name="updated_policy") self.assertEqual('200', resp['status']) updated_fw_policy = body["firewall_policy"] self.assertTrue(updated_fw_policy['shared']) self.assertEqual("updated_policy", updated_fw_policy['name']) # Delete firewall policy resp, _ = self.client.delete_firewall_policy(fw_policy_id) self.assertEqual('204', resp['status']) # Confirm deletion resp, fw_policies = self.client.list_firewall_policies() fw_policies = fw_policies['firewall_policies'] self.assertNotIn(fw_policy_id, [m['id'] for m in fw_policies]) @test.attr(type='smoke') def test_show_firewall_policy(self): # show a created firewall policy resp, fw_policy = self.client.show_firewall_policy( self.fw_policy['id']) self.assertEqual('200', resp['status']) fw_policy = fw_policy['firewall_policy'] for key, value in fw_policy.iteritems(): self.assertEqual(self.fw_policy[key], value) @test.attr(type='smoke') def test_create_show_delete_firewall(self): # Create tenant network resources required for an ACTIVE firewall network = self.create_network() subnet = self.create_subnet(network) router = self.create_router( data_utils.rand_name('router-'), admin_state_up=True) self.client.add_router_interface_with_subnet_id( router['id'], subnet['id']) # Create firewall resp, body = self.client.create_firewall( name=data_utils.rand_name("firewall"), firewall_policy_id=self.fw_policy['id']) self.assertEqual('201', resp['status']) created_firewall = body['firewall'] firewall_id = created_firewall['id'] self.addCleanup(self._try_delete_firewall, firewall_id) self._wait_for_active(firewall_id) # show a created firewall resp, firewall = self.client.show_firewall(firewall_id) self.assertEqual('200', resp['status']) firewall = firewall['firewall'] for key, value in firewall.iteritems(): if key == 'status': continue self.assertEqual(created_firewall[key], value) # list firewall resp, firewalls = self.client.list_firewalls() self.assertEqual('200', resp['status']) firewalls = firewalls['firewalls'] self.assertIn((created_firewall['id'], created_firewall['name'], created_firewall['firewall_policy_id']), [(m['id'], m['name'], m['firewall_policy_id']) for m in firewalls]) # Delete firewall resp, _ = self.client.delete_firewall(firewall_id) self.assertEqual('204', resp['status']) class FWaaSExtensionTestXML(FWaaSExtensionTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/network/base.py0000664000175000017500000003432212332757070023310 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netaddr from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseNetworkTest(tempest.test.BaseTestCase): """ Base class for the Neutron tests that use the Tempest Neutron REST client Per the Neutron API Guide, API v1.x was removed from the source code tree (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html) Therefore, v2.x of the Neutron API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: tenant_network_cidr with a block of cidr's from which smaller blocks can be allocated for tenant networks tenant_network_mask_bits with the mask bits to be used to partition the block defined by tenant-network_cidr Finally, it is assumed that the following option is defined in the [service_available] section of etc/tempest.conf neutron as True """ force_tenant_isolation = False # Default to ipv4. _ip_version = 4 @classmethod def setUpClass(cls): # Create no network resources for these test. cls.set_network_resources() super(BaseNetworkTest, cls).setUpClass() if not CONF.service_available.neutron: raise cls.skipException("Neutron support is required") os = cls.get_client_manager() cls.network_cfg = CONF.network cls.client = os.network_client cls.networks = [] cls.subnets = [] cls.ports = [] cls.routers = [] cls.pools = [] cls.vips = [] cls.members = [] cls.health_monitors = [] cls.vpnservices = [] cls.ikepolicies = [] cls.floating_ips = [] cls.metering_labels = [] cls.metering_label_rules = [] cls.fw_rules = [] cls.fw_policies = [] @classmethod def tearDownClass(cls): # Clean up firewall policies for fw_policy in cls.fw_policies: cls.client.delete_firewall_policy(fw_policy['id']) # Clean up firewall rules for fw_rule in cls.fw_rules: cls.client.delete_firewall_rule(fw_rule['id']) # Clean up ike policies for ikepolicy in cls.ikepolicies: cls.client.delete_ikepolicy(ikepolicy['id']) # Clean up vpn services for vpnservice in cls.vpnservices: cls.client.delete_vpnservice(vpnservice['id']) # Clean up floating IPs for floating_ip in cls.floating_ips: cls.client.delete_floatingip(floating_ip['id']) # Clean up routers for router in cls.routers: cls.delete_router(router) # Clean up health monitors for health_monitor in cls.health_monitors: cls.client.delete_health_monitor(health_monitor['id']) # Clean up members for member in cls.members: cls.client.delete_member(member['id']) # Clean up vips for vip in cls.vips: cls.client.delete_vip(vip['id']) # Clean up pools for pool in cls.pools: cls.client.delete_pool(pool['id']) # Clean up metering label rules for metering_label_rule in cls.metering_label_rules: cls.admin_client.delete_metering_label_rule( metering_label_rule['id']) # Clean up metering labels for metering_label in cls.metering_labels: cls.admin_client.delete_metering_label(metering_label['id']) # Clean up ports for port in cls.ports: cls.client.delete_port(port['id']) # Clean up subnets for subnet in cls.subnets: cls.client.delete_subnet(subnet['id']) # Clean up networks for network in cls.networks: cls.client.delete_network(network['id']) cls.clear_isolated_creds() super(BaseNetworkTest, cls).tearDownClass() @classmethod def create_network(cls, network_name=None): """Wrapper utility that returns a test network.""" network_name = network_name or data_utils.rand_name('test-network-') resp, body = cls.client.create_network(name=network_name) network = body['network'] cls.networks.append(network) return network @classmethod def create_subnet(cls, network): """Wrapper utility that returns a test subnet.""" # The cidr and mask_bits depend on the ip version. if cls._ip_version == 4: cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr) mask_bits = CONF.network.tenant_network_mask_bits elif cls._ip_version == 6: cidr = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr) mask_bits = CONF.network.tenant_network_v6_mask_bits # Find a cidr that is not in use yet and create a subnet with it for subnet_cidr in cidr.subnet(mask_bits): try: resp, body = cls.client.create_subnet( network_id=network['id'], cidr=str(subnet_cidr), ip_version=cls._ip_version) break except exceptions.BadRequest as e: is_overlapping_cidr = 'overlaps with another subnet' in str(e) if not is_overlapping_cidr: raise else: message = 'Available CIDR for subnet creation could not be found' raise exceptions.BuildErrorException(message) subnet = body['subnet'] cls.subnets.append(subnet) return subnet @classmethod def create_port(cls, network, **kwargs): """Wrapper utility that returns a test port.""" resp, body = cls.client.create_port(network_id=network['id'], **kwargs) port = body['port'] cls.ports.append(port) return port @classmethod def update_port(cls, port, **kwargs): """Wrapper utility that updates a test port.""" resp, body = cls.client.update_port(port['id'], **kwargs) return body['port'] @classmethod def create_router(cls, router_name=None, admin_state_up=False, external_network_id=None, enable_snat=None): ext_gw_info = {} if external_network_id: ext_gw_info['network_id'] = external_network_id if enable_snat: ext_gw_info['enable_snat'] = enable_snat resp, body = cls.client.create_router( router_name, external_gateway_info=ext_gw_info, admin_state_up=admin_state_up) router = body['router'] cls.routers.append(router) return router @classmethod def create_floatingip(cls, external_network_id): """Wrapper utility that returns a test floating IP.""" resp, body = cls.client.create_floatingip( floating_network_id=external_network_id) fip = body['floatingip'] cls.floating_ips.append(fip) return fip @classmethod def create_pool(cls, name, lb_method, protocol, subnet): """Wrapper utility that returns a test pool.""" resp, body = cls.client.create_pool( name=name, lb_method=lb_method, protocol=protocol, subnet_id=subnet['id']) pool = body['pool'] cls.pools.append(pool) return pool @classmethod def update_pool(cls, name): """Wrapper utility that returns a test pool.""" resp, body = cls.client.update_pool(name=name) pool = body['pool'] return pool @classmethod def create_vip(cls, name, protocol, protocol_port, subnet, pool): """Wrapper utility that returns a test vip.""" resp, body = cls.client.create_vip(name=name, protocol=protocol, protocol_port=protocol_port, subnet_id=subnet['id'], pool_id=pool['id']) vip = body['vip'] cls.vips.append(vip) return vip @classmethod def update_vip(cls, name): resp, body = cls.client.update_vip(name=name) vip = body['vip'] return vip @classmethod def create_member(cls, protocol_port, pool): """Wrapper utility that returns a test member.""" resp, body = cls.client.create_member(address="10.0.9.46", protocol_port=protocol_port, pool_id=pool['id']) member = body['member'] cls.members.append(member) return member @classmethod def update_member(cls, admin_state_up): resp, body = cls.client.update_member(admin_state_up=admin_state_up) member = body['member'] return member @classmethod def create_health_monitor(cls, delay, max_retries, Type, timeout): """Wrapper utility that returns a test health monitor.""" resp, body = cls.client.create_health_monitor(delay=delay, max_retries=max_retries, type=Type, timeout=timeout) health_monitor = body['health_monitor'] cls.health_monitors.append(health_monitor) return health_monitor @classmethod def update_health_monitor(cls, admin_state_up): resp, body = cls.client.update_vip(admin_state_up=admin_state_up) health_monitor = body['health_monitor'] return health_monitor @classmethod def create_router_interface(cls, router_id, subnet_id): """Wrapper utility that returns a router interface.""" resp, interface = cls.client.add_router_interface_with_subnet_id( router_id, subnet_id) return interface @classmethod def create_vpnservice(cls, subnet_id, router_id): """Wrapper utility that returns a test vpn service.""" resp, body = cls.client.create_vpnservice( subnet_id=subnet_id, router_id=router_id, admin_state_up=True, name=data_utils.rand_name("vpnservice-")) vpnservice = body['vpnservice'] cls.vpnservices.append(vpnservice) return vpnservice @classmethod def create_ikepolicy(cls, name): """Wrapper utility that returns a test ike policy.""" resp, body = cls.client.create_ikepolicy(name=name) ikepolicy = body['ikepolicy'] cls.ikepolicies.append(ikepolicy) return ikepolicy @classmethod def create_firewall_rule(cls, action, protocol): """Wrapper utility that returns a test firewall rule.""" resp, body = cls.client.create_firewall_rule( name=data_utils.rand_name("fw-rule"), action=action, protocol=protocol) fw_rule = body['firewall_rule'] cls.fw_rules.append(fw_rule) return fw_rule @classmethod def create_firewall_policy(cls): """Wrapper utility that returns a test firewall policy.""" resp, body = cls.client.create_firewall_policy( name=data_utils.rand_name("fw-policy")) fw_policy = body['firewall_policy'] cls.fw_policies.append(fw_policy) return fw_policy @classmethod def delete_router(cls, router): resp, body = cls.client.list_router_interfaces(router['id']) interfaces = body['ports'] for i in interfaces: cls.client.remove_router_interface_with_subnet_id( router['id'], i['fixed_ips'][0]['subnet_id']) cls.client.delete_router(router['id']) class BaseAdminNetworkTest(BaseNetworkTest): @classmethod def setUpClass(cls): super(BaseAdminNetworkTest, cls).setUpClass() admin_username = CONF.compute_admin.username admin_password = CONF.compute_admin.password admin_tenant = CONF.compute_admin.tenant_name if not (admin_username and admin_password and admin_tenant): msg = ("Missing Administrative Network API credentials " "in configuration.") raise cls.skipException(msg) if (CONF.compute.allow_tenant_isolation or cls.force_tenant_isolation is True): cls.os_adm = clients.Manager(cls.isolated_creds.get_admin_creds(), interface=cls._interface) else: cls.os_adm = clients.ComputeAdminManager(interface=cls._interface) cls.admin_client = cls.os_adm.network_client @classmethod def create_metering_label(cls, name, description): """Wrapper utility that returns a test metering label.""" resp, body = cls.admin_client.create_metering_label( description=description, name=data_utils.rand_name("metering-label")) metering_label = body['metering_label'] cls.metering_labels.append(metering_label) return metering_label @classmethod def create_metering_label_rule(cls, remote_ip_prefix, direction, metering_label_id): """Wrapper utility that returns a test metering label rule.""" resp, body = cls.admin_client.create_metering_label_rule( remote_ip_prefix=remote_ip_prefix, direction=direction, metering_label_id=metering_label_id) metering_label_rule = body['metering_label_rule'] cls.metering_label_rules.append(metering_label_rule) return metering_label_rule tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/0000775000175000017500000000000012332757136022266 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/test_api_discovery.py0000664000175000017500000000307012332757070026534 0ustar chuckchuck00000000000000# 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 tempest.api.baremetal import base from tempest import test class TestApiDiscovery(base.BaseBaremetalTest): """Tests for API discovery features.""" @test.attr(type='smoke') def test_api_versions(self): resp, descr = self.client.get_api_description() expected_versions = ('v1',) versions = [version['id'] for version in descr['versions']] for v in expected_versions: self.assertIn(v, versions) @test.attr(type='smoke') def test_default_version(self): resp, descr = self.client.get_api_description() default_version = descr['default_version'] self.assertEqual(default_version['id'], 'v1') @test.attr(type='smoke') def test_version_1_resources(self): resp, descr = self.client.get_version_description(version='v1') expected_resources = ('nodes', 'chassis', 'ports', 'links', 'media_types') for res in expected_resources: self.assertIn(res, descr) tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/test_nodes.py0000664000175000017500000000620612332757070025010 0ustar chuckchuck00000000000000# 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 six from tempest.api.baremetal import base from tempest import exceptions as exc from tempest import test class TestNodes(base.BaseBaremetalTest): '''Tests for baremetal nodes.''' def setUp(self): super(TestNodes, self).setUp() self.chassis = self.create_chassis()['chassis'] @test.attr(type='smoke') def test_create_node(self): params = {'cpu_arch': 'x86_64', 'cpu_num': '12', 'storage': '10240', 'memory': '1024'} node = self.create_node(self.chassis['uuid'], **params)['node'] for key in params: self.assertEqual(node['properties'][key], params[key]) @test.attr(type='smoke') def test_delete_node(self): node = self.create_node(self.chassis['uuid'])['node'] node_id = node['uuid'] resp = self.delete_node(node_id) self.assertEqual(resp['status'], '204') self.assertRaises(exc.NotFound, self.client.show_node, node_id) @test.attr(type='smoke') def test_show_node(self): params = {'cpu_arch': 'x86_64', 'cpu_num': '4', 'storage': '100', 'memory': '512'} created_node = self.create_node(self.chassis['uuid'], **params)['node'] resp, loaded_node = self.client.show_node(created_node['uuid']) for key, val in created_node.iteritems(): if key not in ('created_at', 'updated_at'): self.assertEqual(loaded_node[key], val) @test.attr(type='smoke') def test_list_nodes(self): uuids = [self.create_node(self.chassis['uuid'])['node']['uuid'] for i in range(0, 5)] resp, body = self.client.list_nodes() loaded_uuids = [n['uuid'] for n in body['nodes']] for u in uuids: self.assertIn(u, loaded_uuids) @test.attr(type='smoke') def test_update_node(self): props = {'cpu_arch': 'x86_64', 'cpu_num': '12', 'storage': '10', 'memory': '128'} node = self.create_node(self.chassis['uuid'], **props)['node'] node_id = node['uuid'] new_props = {'cpu_arch': 'x86', 'cpu_num': '1', 'storage': '10000', 'memory': '12300'} self.client.update_node(node_id, properties=new_props) resp, node = self.client.show_node(node_id) for name, value in six.iteritems(new_props): if name not in ('created_at', 'updated_at'): self.assertEqual(node['properties'][name], value) tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/test_ports.py0000664000175000017500000002462312332757070025052 0ustar chuckchuck00000000000000# 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 tempest.api.baremetal import base from tempest.common.utils import data_utils from tempest import exceptions as exc from tempest import test class TestPorts(base.BaseBaremetalTest): """Tests for ports.""" def setUp(self): super(TestPorts, self).setUp() chassis = self.create_chassis()['chassis'] self.node = self.create_node(chassis['uuid'])['node'] @test.attr(type='smoke') def test_create_port(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() result = self.create_port(node_id=node_id, address=address) port = result['port'] resp, body = self.client.show_port(port['uuid']) self.assertEqual(200, resp.status) self.assertEqual(port['uuid'], body['uuid']) self.assertEqual(address, body['address']) self.assertEqual({}, body['extra']) self.assertEqual(node_id, body['node_uuid']) @test.attr(type='smoke') def test_create_port_specifying_uuid(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() uuid = data_utils.rand_uuid() self.create_port(node_id=node_id, address=address, uuid=uuid) resp, body = self.client.show_port(uuid) self.assertEqual(200, resp.status) self.assertEqual(uuid, body['uuid']) self.assertEqual(address, body['address']) self.assertEqual({}, body['extra']) self.assertEqual(node_id, body['node_uuid']) @test.attr(type='smoke') def test_create_port_with_extra(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key': 'value'} result = self.create_port(node_id=node_id, address=address, extra=extra) port = result['port'] resp, body = self.client.show_port(port['uuid']) self.assertEqual(200, resp.status) self.assertEqual(port['uuid'], body['uuid']) self.assertEqual(address, body['address']) self.assertEqual(extra, body['extra']) self.assertEqual(node_id, body['node_uuid']) @test.attr(type='smoke') def test_delete_port(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] resp = self.delete_port(port_id) self.assertEqual(204, resp.status) self.assertRaises(exc.NotFound, self.client.show_port, port_id) @test.attr(type='smoke') def test_show_port(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key': 'value'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] resp, port = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertEqual(port_id, port['uuid']) self.assertEqual(address, port['address']) self.assertEqual(extra, port['extra']) @test.attr(type='smoke') def test_show_port_with_links(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] resp, body = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertIn('links', body.keys()) self.assertEqual(2, len(body['links'])) self.assertIn(port_id, body['links'][0]['href']) @test.attr(type='smoke') def test_list_ports(self): node_id = self.node['uuid'] uuids = [self.create_port(node_id=node_id, address=data_utils.rand_mac_address()) ['port']['uuid'] for i in xrange(5)] resp, body = self.client.list_ports() self.assertEqual(200, resp.status) loaded_uuids = [p['uuid'] for p in body['ports']] for uuid in uuids: self.assertIn(uuid, loaded_uuids) # Verify self links. for port in body['ports']: self.validate_self_link('ports', port['uuid'], port['links'][0]['href']) @test.attr(type='smoke') def test_list_with_limit(self): node_id = self.node['uuid'] for i in xrange(5): self.create_port(node_id=node_id, address=data_utils.rand_mac_address()) resp, body = self.client.list_ports(limit=3) self.assertEqual(200, resp.status) self.assertEqual(3, len(body['ports'])) next_marker = body['ports'][-1]['uuid'] self.assertIn(next_marker, body['next']) def test_list_ports_details(self): node_id = self.node['uuid'] uuids = [ self.create_port(node_id=node_id, address=data_utils.rand_mac_address()) ['port']['uuid'] for i in range(0, 5)] resp, body = self.client.list_ports_detail() self.assertEqual(200, resp.status) ports_dict = {port['uuid']: port for port in body['ports'] if port['uuid'] in uuids} for uuid in uuids: self.assertIn(uuid, ports_dict) port = ports_dict[uuid] self.assertIn('extra', port) self.assertIn('node_uuid', port) # never expose the node_id self.assertNotIn('node_id', port) # Verify self link. self.validate_self_link('ports', port['uuid'], port['links'][0]['href']) @test.attr(type='smoke') def test_update_port_replace(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] new_address = data_utils.rand_mac_address() new_extra = {'key1': 'new-value1', 'key2': 'new-value2', 'key3': 'new-value3'} patch = [{'path': '/address', 'op': 'replace', 'value': new_address}, {'path': '/extra/key1', 'op': 'replace', 'value': new_extra['key1']}, {'path': '/extra/key2', 'op': 'replace', 'value': new_extra['key2']}, {'path': '/extra/key3', 'op': 'replace', 'value': new_extra['key3']}] self.client.update_port(port_id, patch) resp, body = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertEqual(new_address, body['address']) self.assertEqual(new_extra, body['extra']) @test.attr(type='smoke') def test_update_port_remove(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] # Removing one item from the collection resp, _ = self.client.update_port(port_id, [{'path': '/extra/key2', 'op': 'remove'}]) self.assertEqual(200, resp.status) extra.pop('key2') resp, body = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertEqual(extra, body['extra']) # Removing the collection resp, _ = self.client.update_port(port_id, [{'path': '/extra', 'op': 'remove'}]) self.assertEqual(200, resp.status) resp, body = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertEqual({}, body['extra']) # Assert nothing else was changed self.assertEqual(node_id, body['node_uuid']) self.assertEqual(address, body['address']) @test.attr(type='smoke') def test_update_port_add(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] extra = {'key1': 'value1', 'key2': 'value2'} patch = [{'path': '/extra/key1', 'op': 'add', 'value': extra['key1']}, {'path': '/extra/key2', 'op': 'add', 'value': extra['key2']}] self.client.update_port(port_id, patch) resp, body = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertEqual(extra, body['extra']) @test.attr(type='smoke') def test_update_port_mixed_ops(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key1': 'value1', 'key2': 'value2'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] new_address = data_utils.rand_mac_address() new_extra = {'key1': 'new-value1', 'key3': 'new-value3'} patch = [{'path': '/address', 'op': 'replace', 'value': new_address}, {'path': '/extra/key1', 'op': 'replace', 'value': new_extra['key1']}, {'path': '/extra/key2', 'op': 'remove'}, {'path': '/extra/key3', 'op': 'add', 'value': new_extra['key3']}] self.client.update_port(port_id, patch) resp, body = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertEqual(new_address, body['address']) self.assertEqual(new_extra, body['extra']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/test_chassis.py0000664000175000017500000000523712332757070025340 0ustar chuckchuck00000000000000# -*- coding: utf-8 -*- # 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 tempest.api.baremetal import base from tempest.common.utils import data_utils from tempest import exceptions as exc from tempest import test class TestChassis(base.BaseBaremetalTest): """Tests for chassis.""" @test.attr(type='smoke') def test_create_chassis(self): descr = data_utils.rand_name('test-chassis-') ch = self.create_chassis(description=descr)['chassis'] self.assertEqual(ch['description'], descr) @test.attr(type='smoke') def test_create_chassis_unicode_description(self): # Use a unicode string for testing: # 'We ♡ OpenStack in Ukraine' descr = u'В Україні ♡ OpenStack!' ch = self.create_chassis(description=descr)['chassis'] self.assertEqual(ch['description'], descr) @test.attr(type='smoke') def test_show_chassis(self): descr = data_utils.rand_name('test-chassis-') uuid = self.create_chassis(description=descr)['chassis']['uuid'] resp, chassis = self.client.show_chassis(uuid) self.assertEqual(chassis['uuid'], uuid) self.assertEqual(chassis['description'], descr) @test.attr(type="smoke") def test_list_chassis(self): created_ids = [self.create_chassis()['chassis']['uuid'] for i in range(0, 5)] resp, body = self.client.list_chassis() loaded_ids = [ch['uuid'] for ch in body['chassis']] for i in created_ids: self.assertIn(i, loaded_ids) @test.attr(type='smoke') def test_delete_chassis(self): uuid = self.create_chassis()['chassis']['uuid'] self.delete_chassis(uuid) self.assertRaises(exc.NotFound, self.client.show_chassis, uuid) @test.attr(type='smoke') def test_update_chassis(self): chassis_id = self.create_chassis()['chassis']['uuid'] new_description = data_utils.rand_name('new-description-') self.client.update_chassis(chassis_id, description=new_description) resp, chassis = self.client.show_chassis(chassis_id) self.assertEqual(chassis['description'], new_description) tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/__init__.py0000664000175000017500000000000012332757070024362 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/test_nodestates.py0000664000175000017500000000237012332757070026047 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.baremetal import base from tempest import test class TestNodeStates(base.BaseBaremetalTest): """Tests for baremetal NodeStates.""" @classmethod def setUpClass(self): super(TestNodeStates, self).setUpClass() chassis = self.create_chassis()['chassis'] self.node = self.create_node(chassis['uuid'])['node'] @test.attr(type='smoke') def test_list_nodestates(self): resp, nodestates = self.client.list_nodestates(self.node['uuid']) self.assertEqual('200', resp['status']) for key in nodestates: self.assertEqual(nodestates[key], self.node[key]) tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/test_drivers.py0000664000175000017500000000175112332757070025356 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.baremetal import base from tempest import test class TestDrivers(base.BaseBaremetalTest): """Tests for drivers.""" @test.attr(type="smoke") def test_list_drivers(self): resp, drivers = self.client.list_drivers() self.assertEqual('200', resp['status']) self.assertIn('fake', [d['name'] for d in drivers['drivers']]) tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/test_ports_negative.py0000664000175000017500000003443612332757070026737 0ustar chuckchuck00000000000000# 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 tempest.api.baremetal import base from tempest.common.utils import data_utils from tempest import exceptions as exc from tempest import test class TestPortsNegative(base.BaseBaremetalTest): """Negative tests for ports.""" def setUp(self): super(TestPortsNegative, self).setUp() chassis = self.create_chassis()['chassis'] self.node = self.create_node(chassis['uuid'])['node'] @test.attr(type=['negative', 'smoke']) def test_create_port_malformed_mac(self): node_id = self.node['uuid'] address = 'malformed:mac' self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id, address=address) @test.attr(type=['negative', 'smoke']) def test_create_port_malformed_extra(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key': 0.123} self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id, address=address, extra=extra) @test.attr(type=['negative', 'smoke']) def test_create_port_nonexsistent_node_id(self): node_id = str(data_utils.rand_uuid()) address = data_utils.rand_mac_address() self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id, address=address) @test.attr(type=['negative', 'smoke']) def test_show_port_malformed_uuid(self): self.assertRaises(exc.BadRequest, self.client.show_port, 'malformed:uuid') @test.attr(type=['negative', 'smoke']) def test_show_port_nonexistent_uuid(self): self.assertRaises(exc.NotFound, self.client.show_port, data_utils.rand_uuid()) @test.attr(type=['negative', 'smoke']) def test_show_port_by_mac_not_allowed(self): self.assertRaises(exc.BadRequest, self.client.show_port, data_utils.rand_mac_address()) @test.attr(type=['negative', 'smoke']) def test_create_port_duplicated_port_uuid(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() uuid = data_utils.rand_uuid() self.create_port(node_id=node_id, address=address, uuid=uuid) self.assertRaises(exc.Conflict, self.create_port, node_id=node_id, address=address, uuid=uuid) @test.attr(type=['negative', 'smoke']) def test_create_port_no_mandatory_field_node_id(self): address = data_utils.rand_mac_address() self.assertRaises(exc.BadRequest, self.create_port, node_id=None, address=address) @test.attr(type=['negative', 'smoke']) def test_create_port_no_mandatory_field_mac(self): node_id = self.node['uuid'] self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id, address=None) @test.attr(type=['negative', 'smoke']) def test_create_port_malformed_port_uuid(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() uuid = 'malformed:uuid' self.assertRaises(exc.BadRequest, self.create_port, node_id=node_id, address=address, uuid=uuid) @test.attr(type=['negative', 'smoke']) def test_create_port_malformed_node_id(self): address = data_utils.rand_mac_address() self.assertRaises(exc.BadRequest, self.create_port, node_id='malformed:nodeid', address=address) @test.attr(type=['negative', 'smoke']) def test_create_port_duplicated_mac(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() self.create_port(node_id=node_id, address=address) self.assertRaises(exc.Conflict, self.create_port, node_id=node_id, address=address) @test.attr(type=['negative', 'smoke']) def test_update_port_by_mac_not_allowed(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key': 'value'} self.create_port(node_id=node_id, address=address, extra=extra) patch = [{'path': '/extra/key', 'op': 'replace', 'value': 'new-value'}] self.assertRaises(exc.BadRequest, self.client.update_port, address, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_nonexistent(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key': 'value'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] self.client.delete_port(port_id) patch = [{'path': '/extra/key', 'op': 'replace', 'value': 'new-value'}] self.assertRaises(exc.NotFound, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_malformed_port_uuid(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() self.create_port(node_id=node_id, address=address) new_address = data_utils.rand_mac_address() self.assertRaises(exc.BadRequest, self.client.update_port, uuid='malformed:uuid', patch=[{'path': '/address', 'op': 'replace', 'value': new_address}]) @test.attr(type=['negative', 'smoke']) def test_update_port_add_malformed_extra(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, [{'path': '/extra/key', ' op': 'add', 'value': 0.123}]) @test.attr(type=['negative', 'smoke']) def test_update_port_add_whole_malformed_extra(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, [{'path': '/extra', 'op': 'add', 'value': [1, 2, 3, 4, 'a']}]) @test.attr(type=['negative', 'smoke']) def test_update_port_add_nonexistent_property(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, [{'path': '/nonexistent', ' op': 'add', 'value': 'value'}]) @test.attr(type=['negative', 'smoke']) def test_update_port_replace_node_id_with_malformed(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port']['uuid'] patch = [{'path': '/node_uuid', 'op': 'replace', 'value': 'malformed:node_uuid'}] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_replace_mac_with_duplicated(self): node_id = self.node['uuid'] address1 = data_utils.rand_mac_address() address2 = data_utils.rand_mac_address() self.create_port(node_id=node_id, address=address1) port_id = self.create_port(node_id=node_id, address=address2)['port']['uuid'] patch = [{'path': '/address', 'op': 'replace', 'value': address1}] self.assertRaises(exc.Conflict, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_replace_node_id_with_nonexistent(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port']['uuid'] patch = [{'path': '/node_uuid', 'op': 'replace', 'value': data_utils.rand_uuid()}] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_replace_mac_with_malformed(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port']['uuid'] patch = [{'path': '/address', 'op': 'replace', 'value': 'malformed:mac'}] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_replace_extra_item_with_malformed(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key': 'value'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] patch = [{'path': '/extra/key', 'op': 'replace', 'value': 0.123}] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_replace_whole_extra_with_malformed(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key': 'value'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] patch = [{'path': '/extra', 'op': 'replace', 'value': [1, 2, 3, 4, 'a']}] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_replace_nonexistent_property(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port']['uuid'] patch = [{'path': '/nonexistent', ' op': 'replace', 'value': 'value'}] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, patch) @test.attr(type=['negative', 'smoke']) def test_update_port_remove_mandatory_field_mac(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, [{'path': '/address', 'op': 'remove'}]) @test.attr(type=['negative', 'smoke']) def test_update_port_remove_mandatory_field_port_uuid(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, [{'path': '/uuid', 'op': 'remove'}]) @test.attr(type=['negative', 'smoke']) def test_update_port_remove_nonexistent_property(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() port_id = self.create_port(node_id=node_id, address=address)['port'][ 'uuid'] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, [{'path': '/nonexistent', 'op': 'remove'}]) @test.attr(type=['negative', 'smoke']) def test_delete_port_by_mac_not_allowed(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() self.create_port(node_id=node_id, address=address) self.assertRaises(exc.BadRequest, self.client.delete_port, address) @test.attr(type=['negative', 'smoke']) def test_update_port_mixed_ops_integrity(self): node_id = self.node['uuid'] address = data_utils.rand_mac_address() extra = {'key1': 'value1', 'key2': 'value2'} port_id = self.create_port(node_id=node_id, address=address, extra=extra)['port']['uuid'] new_address = data_utils.rand_mac_address() new_extra = {'key1': 'new-value1', 'key3': 'new-value3'} patch = [{'path': '/address', 'op': 'replace', 'value': new_address}, {'path': '/extra/key1', 'op': 'replace', 'value': new_extra['key1']}, {'path': '/extra/key2', 'op': 'remove'}, {'path': '/extra/key3', 'op': 'add', 'value': new_extra['key3']}, {'path': '/nonexistent', 'op': 'replace', 'value': 'value'}] self.assertRaises(exc.BadRequest, self.client.update_port, port_id, patch) # patch should not be applied resp, body = self.client.show_port(port_id) self.assertEqual(200, resp.status) self.assertEqual(address, body['address']) self.assertEqual(extra, body['extra']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/baremetal/base.py0000664000175000017500000001331212332757070023547 0ustar chuckchuck00000000000000# 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 functools from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions as exc from tempest import test CONF = config.CONF def creates(resource): """Decorator that adds resources to the appropriate cleanup list.""" def decorator(f): @functools.wraps(f) def wrapper(cls, *args, **kwargs): result = f(cls, *args, **kwargs) body = result[resource] if 'uuid' in body: cls.created_objects[resource].add(body['uuid']) return result return wrapper return decorator class BaseBaremetalTest(test.BaseTestCase): """Base class for Baremetal API tests.""" @classmethod def setUpClass(cls): super(BaseBaremetalTest, cls).setUpClass() if not CONF.service_available.ironic: skip_msg = ('%s skipped as Ironic is not available' % cls.__name__) raise cls.skipException(skip_msg) mgr = clients.AdminManager() cls.client = mgr.baremetal_client cls.created_objects = {'chassis': set(), 'port': set(), 'node': set()} @classmethod def tearDownClass(cls): """Ensure that all created objects get destroyed.""" try: for resource, uuids in cls.created_objects.iteritems(): delete_method = getattr(cls.client, 'delete_%s' % resource) for u in uuids: delete_method(u, ignore_errors=exc.NotFound) finally: super(BaseBaremetalTest, cls).tearDownClass() @classmethod @creates('chassis') def create_chassis(cls, description=None, expect_errors=False): """ Wrapper utility for creating test chassis. :param description: A description of the chassis. if not supplied, a random value will be generated. :return: Created chassis. """ description = description or data_utils.rand_name('test-chassis-') resp, body = cls.client.create_chassis(description=description) return {'chassis': body, 'response': resp} @classmethod @creates('node') def create_node(cls, chassis_id, cpu_arch='x86', cpu_num=8, storage=1024, memory=4096, driver='fake'): """ Wrapper utility for creating test baremetal nodes. :param cpu_arch: CPU architecture of the node. Default: x86. :param cpu_num: Number of CPUs. Default: 8. :param storage: Disk size. Default: 1024. :param memory: Available RAM. Default: 4096. :return: Created node. """ resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch, cpu_num=cpu_num, storage=storage, memory=memory, driver=driver) return {'node': body, 'response': resp} @classmethod @creates('port') def create_port(cls, node_id, address, extra=None, uuid=None): """ Wrapper utility for creating test ports. :param address: MAC address of the port. :param extra: Meta data of the port. If not supplied, an empty dictionary will be created. :param uuid: UUID of the port. :return: Created port. """ extra = extra or {} resp, body = cls.client.create_port(address=address, node_id=node_id, extra=extra, uuid=uuid) return {'port': body, 'response': resp} @classmethod def delete_chassis(cls, chassis_id): """ Deletes a chassis having the specified UUID. :param uuid: The unique identifier of the chassis. :return: Server response. """ resp, body = cls.client.delete_chassis(chassis_id) if chassis_id in cls.created_objects['chassis']: cls.created_objects['chassis'].remove(chassis_id) return resp @classmethod def delete_node(cls, node_id): """ Deletes a node having the specified UUID. :param uuid: The unique identifier of the node. :return: Server response. """ resp, body = cls.client.delete_node(node_id) if node_id in cls.created_objects['node']: cls.created_objects['node'].remove(node_id) return resp @classmethod def delete_port(cls, port_id): """ Deletes a port having the specified UUID. :param uuid: The unique identifier of the port. :return: Server response. """ resp, body = cls.client.delete_port(port_id) if port_id in cls.created_objects['port']: cls.created_objects['port'].remove(port_id) return resp def validate_self_link(self, resource, uuid, link): """Check whether the given self link formatted correctly.""" expected_link = "{base}/{pref}/{res}/{uuid}".format( base=self.client.base_url, pref=self.client.uri_prefix, res=resource, uuid=uuid) self.assertEqual(expected_link, link) tempest-2014.1.dev4108.gf22b6cc/tempest/api/database/0000775000175000017500000000000012332757136022076 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/database/flavors/0000775000175000017500000000000012332757136023552 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/database/flavors/test_flavors.py0000664000175000017500000000304512332757070026636 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.database import base from tempest import test class DatabaseFlavorsTest(base.BaseDatabaseTest): @classmethod def setUpClass(cls): super(DatabaseFlavorsTest, cls).setUpClass() cls.client = cls.database_flavors_client @test.attr(type='smoke') def test_get_db_flavor(self): # The expected flavor details should be returned resp, flavor = self.client.get_db_flavor_details(self.db_flavor_ref) self.assertEqual(self.db_flavor_ref, str(flavor['id'])) self.assertIn('ram', flavor) self.assertIn('links', flavor) self.assertIn('name', flavor) @test.attr(type='smoke') def test_list_db_flavors(self): resp, flavor = self.client.get_db_flavor_details(self.db_flavor_ref) # List of all flavors should contain the expected flavor resp, flavors = self.client.list_db_flavors() self.assertIn(flavor, flavors) tempest-2014.1.dev4108.gf22b6cc/tempest/api/database/flavors/__init__.py0000664000175000017500000000000012332757070025646 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/database/flavors/test_flavors_negative.py0000664000175000017500000000227412332757070030523 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.database import base from tempest import exceptions from tempest import test class DatabaseFlavorsNegativeTest(base.BaseDatabaseTest): @classmethod def setUpClass(cls): super(DatabaseFlavorsNegativeTest, cls).setUpClass() cls.client = cls.database_flavors_client @test.attr(type=['negative', 'gate']) def test_get_non_existent_db_flavor(self): # flavor details are not returned for non-existent flavors self.assertRaises(exceptions.NotFound, self.client.get_db_flavor_details, -1) tempest-2014.1.dev4108.gf22b6cc/tempest/api/database/__init__.py0000664000175000017500000000000012332757070024172 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/database/base.py0000664000175000017500000000266712332757070023372 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import config from tempest.openstack.common import log as logging import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseDatabaseTest(tempest.test.BaseTestCase): """Base test case class for all Database API tests.""" _interface = 'json' force_tenant_isolation = False @classmethod def setUpClass(cls): super(BaseDatabaseTest, cls).setUpClass() if not CONF.service_available.trove: skip_msg = ("%s skipped as trove is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.catalog_type = CONF.database.catalog_type cls.db_flavor_ref = CONF.database.db_flavor_ref os = cls.get_client_manager() cls.os = os cls.database_flavors_client = cls.os.database_flavors_client tempest-2014.1.dev4108.gf22b6cc/tempest/api/utils.py0000664000175000017500000000256312332757070022047 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Common utilities used in testing.""" from tempest import test class skip_unless_attr(object): """Decorator that skips a test if a specified attr exists and is True.""" def __init__(self, attr, msg=None): self.attr = attr self.message = msg or ("Test case attribute %s not found " "or False") % attr def __call__(self, func): def _skipper(*args, **kw): """Wrapped skipper function.""" testobj = args[0] if not getattr(testobj, self.attr, False): raise test.BaseTestCase.skipException(self.message) func(*args, **kw) _skipper.__name__ = func.__name__ _skipper.__doc__ = func.__doc__ return _skipper tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/0000775000175000017500000000000012332757136021641 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volumes_actions.py0000664000175000017500000001641512332757070026470 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class VolumesActionsTest(base.BaseVolumeV1Test): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(VolumesActionsTest, cls).setUpClass() cls.client = cls.volumes_client cls.image_client = cls.os.image_client # Create a test shared instance srv_name = data_utils.rand_name(cls.__name__ + '-Instance-') resp, cls.server = cls.servers_client.create_server(srv_name, cls.image_ref, cls.flavor_ref) cls.servers_client.wait_for_server_status(cls.server['id'], 'ACTIVE') # Create a test shared volume for attach/detach tests cls.volume = cls.create_volume() @classmethod def tearDownClass(cls): # Delete the test instance cls.servers_client.delete_server(cls.server['id']) cls.servers_client.wait_for_server_termination(cls.server['id']) super(VolumesActionsTest, cls).tearDownClass() @test.stresstest(class_setup_per='process') @test.attr(type='smoke') @test.services('compute') def test_attach_detach_volume_to_instance(self): # Volume is attached and detached successfully from an instance mountpoint = '/dev/vdc' resp, body = self.client.attach_volume(self.volume['id'], self.server['id'], mountpoint) self.assertEqual(202, resp.status) self.client.wait_for_volume_status(self.volume['id'], 'in-use') resp, body = self.client.detach_volume(self.volume['id']) self.assertEqual(202, resp.status) self.client.wait_for_volume_status(self.volume['id'], 'available') @test.stresstest(class_setup_per='process') @test.attr(type='gate') @test.services('compute') def test_get_volume_attachment(self): # Verify that a volume's attachment information is retrieved mountpoint = '/dev/vdc' resp, body = self.client.attach_volume(self.volume['id'], self.server['id'], mountpoint) self.assertEqual(202, resp.status) self.client.wait_for_volume_status(self.volume['id'], 'in-use') # NOTE(gfidente): added in reverse order because functions will be # called in reverse order to the order they are added (LIFO) self.addCleanup(self.client.wait_for_volume_status, self.volume['id'], 'available') self.addCleanup(self.client.detach_volume, self.volume['id']) resp, volume = self.client.get_volume(self.volume['id']) self.assertEqual(200, resp.status) self.assertIn('attachments', volume) attachment = self.client.get_attachment_from_volume(volume) self.assertEqual(mountpoint, attachment['device']) self.assertEqual(self.server['id'], attachment['server_id']) self.assertEqual(self.volume['id'], attachment['id']) self.assertEqual(self.volume['id'], attachment['volume_id']) @test.attr(type='gate') @test.services('image') def test_volume_upload(self): # NOTE(gfidente): the volume uploaded in Glance comes from setUpClass, # it is shared with the other tests. After it is uploaded in Glance, # there is no way to delete it from Cinder, so we delete it from Glance # using the Glance image_client and from Cinder via tearDownClass. image_name = data_utils.rand_name('Image-') resp, body = self.client.upload_volume(self.volume['id'], image_name, CONF.volume.disk_format) image_id = body["image_id"] self.addCleanup(self.image_client.delete_image, image_id) self.assertEqual(202, resp.status) self.image_client.wait_for_image_status(image_id, 'active') self.client.wait_for_volume_status(self.volume['id'], 'available') @test.attr(type='gate') def test_volume_extend(self): # Extend Volume Test. extend_size = int(self.volume['size']) + 1 resp, body = self.client.extend_volume(self.volume['id'], extend_size) self.assertEqual(202, resp.status) self.client.wait_for_volume_status(self.volume['id'], 'available') resp, volume = self.client.get_volume(self.volume['id']) self.assertEqual(200, resp.status) self.assertEqual(int(volume['size']), extend_size) @test.attr(type='gate') def test_reserve_unreserve_volume(self): # Mark volume as reserved. resp, body = self.client.reserve_volume(self.volume['id']) self.assertEqual(202, resp.status) # To get the volume info resp, body = self.client.get_volume(self.volume['id']) self.assertEqual(200, resp.status) self.assertIn('attaching', body['status']) # Unmark volume as reserved. resp, body = self.client.unreserve_volume(self.volume['id']) self.assertEqual(202, resp.status) # To get the volume info resp, body = self.client.get_volume(self.volume['id']) self.assertEqual(200, resp.status) self.assertIn('available', body['status']) def _is_true(self, val): return val in ['true', 'True', True] @test.attr(type='gate') def test_volume_readonly_update(self): # Update volume readonly true readonly = True resp, body = self.client.update_volume_readonly(self.volume['id'], readonly) self.assertEqual(202, resp.status) # Get Volume information resp, fetched_volume = self.client.get_volume(self.volume['id']) bool_flag = self._is_true(fetched_volume['metadata']['readonly']) self.assertEqual(200, resp.status) self.assertEqual(True, bool_flag) # Update volume readonly false readonly = False resp, body = self.client.update_volume_readonly(self.volume['id'], readonly) self.assertEqual(202, resp.status) # Get Volume information resp, fetched_volume = self.client.get_volume(self.volume['id']) bool_flag = self._is_true(fetched_volume['metadata']['readonly']) self.assertEqual(200, resp.status) self.assertEqual(False, bool_flag) class VolumesActionsTestXML(VolumesActionsTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volumes_list.py0000664000175000017500000002106112332757070025774 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import operator from tempest.api.volume import base from tempest.common.utils import data_utils from tempest.openstack.common import log as logging from tempest import test from testtools import matchers LOG = logging.getLogger(__name__) VOLUME_FIELDS = ('id', 'display_name') class VolumesListTest(base.BaseVolumeV1Test): """ This test creates a number of 1G volumes. To run successfully, ensure that the backing file for the volume group that Nova uses has space for at least 3 1G volumes! If you are running a Devstack environment, ensure that the VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc """ _interface = 'json' def assertVolumesIn(self, fetched_list, expected_list, fields=None): if fields: expected_list = map(operator.itemgetter(*fields), expected_list) fetched_list = map(operator.itemgetter(*fields), fetched_list) missing_vols = [v for v in expected_list if v not in fetched_list] if len(missing_vols) == 0: return def str_vol(vol): return "%s:%s" % (vol['id'], vol['display_name']) raw_msg = "Could not find volumes %s in expected list %s; fetched %s" self.fail(raw_msg % ([str_vol(v) for v in missing_vols], [str_vol(v) for v in expected_list], [str_vol(v) for v in fetched_list])) @classmethod @test.safe_setup def setUpClass(cls): super(VolumesListTest, cls).setUpClass() cls.client = cls.volumes_client # Create 3 test volumes cls.volume_list = [] cls.volume_id_list = [] cls.metadata = {'Type': 'work'} for i in range(3): volume = cls.create_volume(metadata=cls.metadata) resp, volume = cls.client.get_volume(volume['id']) cls.volume_list.append(volume) cls.volume_id_list.append(volume['id']) @classmethod def tearDownClass(cls): # Delete the created volumes for volid in cls.volume_id_list: resp, _ = cls.client.delete_volume(volid) cls.client.wait_for_resource_deletion(volid) super(VolumesListTest, cls).tearDownClass() def _list_by_param_value_and_assert(self, params, with_detail=False): """ Perform list or list_details action with given params and validates result. """ if with_detail: resp, fetched_vol_list = \ self.client.list_volumes_with_detail(params=params) else: resp, fetched_vol_list = self.client.list_volumes(params=params) self.assertEqual(200, resp.status) # Validating params of fetched volumes for volume in fetched_vol_list: for key in params: msg = "Failed to list volumes %s by %s" % \ ('details' if with_detail else '', key) if key == 'metadata': self.assertThat(volume[key].items(), matchers.ContainsAll(params[key].items()), msg) else: self.assertEqual(params[key], volume[key], msg) @test.attr(type='smoke') def test_volume_list(self): # Get a list of Volumes # Fetch all volumes resp, fetched_list = self.client.list_volumes() self.assertEqual(200, resp.status) self.assertVolumesIn(fetched_list, self.volume_list, fields=VOLUME_FIELDS) @test.attr(type='gate') def test_volume_list_with_details(self): # Get a list of Volumes with details # Fetch all Volumes resp, fetched_list = self.client.list_volumes_with_detail() self.assertEqual(200, resp.status) self.assertVolumesIn(fetched_list, self.volume_list) @test.attr(type='gate') def test_volume_list_by_name(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'display_name': volume['display_name']} resp, fetched_vol = self.client.list_volumes(params) self.assertEqual(200, resp.status) self.assertEqual(1, len(fetched_vol), str(fetched_vol)) self.assertEqual(fetched_vol[0]['display_name'], volume['display_name']) @test.attr(type='gate') def test_volume_list_details_by_name(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'display_name': volume['display_name']} resp, fetched_vol = self.client.list_volumes_with_detail(params) self.assertEqual(200, resp.status) self.assertEqual(1, len(fetched_vol), str(fetched_vol)) self.assertEqual(fetched_vol[0]['display_name'], volume['display_name']) @test.attr(type='gate') def test_volumes_list_by_status(self): params = {'status': 'available'} resp, fetched_list = self.client.list_volumes(params) self.assertEqual(200, resp.status) for volume in fetched_list: self.assertEqual('available', volume['status']) self.assertVolumesIn(fetched_list, self.volume_list, fields=VOLUME_FIELDS) @test.attr(type='gate') def test_volumes_list_details_by_status(self): params = {'status': 'available'} resp, fetched_list = self.client.list_volumes_with_detail(params) self.assertEqual(200, resp.status) for volume in fetched_list: self.assertEqual('available', volume['status']) self.assertVolumesIn(fetched_list, self.volume_list) @test.attr(type='gate') def test_volumes_list_by_availability_zone(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] zone = volume['availability_zone'] params = {'availability_zone': zone} resp, fetched_list = self.client.list_volumes(params) self.assertEqual(200, resp.status) for volume in fetched_list: self.assertEqual(zone, volume['availability_zone']) self.assertVolumesIn(fetched_list, self.volume_list, fields=VOLUME_FIELDS) @test.attr(type='gate') def test_volumes_list_details_by_availability_zone(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] zone = volume['availability_zone'] params = {'availability_zone': zone} resp, fetched_list = self.client.list_volumes_with_detail(params) self.assertEqual(200, resp.status) for volume in fetched_list: self.assertEqual(zone, volume['availability_zone']) self.assertVolumesIn(fetched_list, self.volume_list) @test.attr(type='gate') def test_volume_list_with_param_metadata(self): # Test to list volumes when metadata param is given params = {'metadata': self.metadata} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_volume_list_with_detail_param_metadata(self): # Test to list volumes details when metadata param is given params = {'metadata': self.metadata} self._list_by_param_value_and_assert(params, with_detail=True) @test.attr(type='gate') def test_volume_list_param_display_name_and_status(self): # Test to list volume when display name and status param is given volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'display_name': volume['display_name'], 'status': 'available'} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_volume_list_with_detail_param_display_name_and_status(self): # Test to list volume when name and status param is given volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'display_name': volume['display_name'], 'status': 'available'} self._list_by_param_value_and_assert(params, with_detail=True) class VolumeListTestXML(VolumesListTest): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volumes_get.py0000664000175000017500000001603012332757070025600 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from testtools import matchers from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class VolumesGetTest(base.BaseVolumeV1Test): _interface = "json" @classmethod def setUpClass(cls): super(VolumesGetTest, cls).setUpClass() cls.client = cls.volumes_client def _delete_volume(self, volume_id): resp, _ = self.client.delete_volume(volume_id) self.assertEqual(202, resp.status) self.client.wait_for_resource_deletion(volume_id) def _is_true(self, val): # NOTE(jdg): Temporary conversion method to get cinder patch # merged. Then we'll make this strict again and #specifically check "true" or "false" if val in ['true', 'True', True]: return True else: return False def _volume_create_get_update_delete(self, **kwargs): # Create a volume, Get it's details and Delete the volume volume = {} v_name = data_utils.rand_name('Volume') metadata = {'Type': 'Test'} # Create a volume resp, volume = self.client.create_volume(display_name=v_name, metadata=metadata, **kwargs) self.assertEqual(200, resp.status) self.assertIn('id', volume) self.addCleanup(self._delete_volume, volume['id']) self.assertIn('display_name', volume) self.assertEqual(volume['display_name'], v_name, "The created volume name is not equal " "to the requested name") self.assertTrue(volume['id'] is not None, "Field volume id is empty or not found.") self.client.wait_for_volume_status(volume['id'], 'available') # Get Volume information resp, fetched_volume = self.client.get_volume(volume['id']) self.assertEqual(200, resp.status) self.assertEqual(v_name, fetched_volume['display_name'], 'The fetched Volume name is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume id is different ' 'from the created Volume') self.assertThat(fetched_volume['metadata'].items(), matchers.ContainsAll(metadata.items()), 'The fetched Volume metadata misses data ' 'from the created Volume') # NOTE(jdg): Revert back to strict true/false checking # after fix for bug #1227837 merges boot_flag = self._is_true(fetched_volume['bootable']) if 'imageRef' in kwargs: self.assertEqual(boot_flag, True) if 'imageRef' not in kwargs: self.assertEqual(boot_flag, False) # Update Volume # Test volume update when display_name is same with original value resp, update_volume = \ self.client.update_volume(volume['id'], display_name=v_name) self.assertEqual(200, resp.status) # Test volume update when display_name is new new_v_name = data_utils.rand_name('new-Volume') new_desc = 'This is the new description of volume' resp, update_volume = \ self.client.update_volume(volume['id'], display_name=new_v_name, display_description=new_desc) # Assert response body for update_volume method self.assertEqual(200, resp.status) self.assertEqual(new_v_name, update_volume['display_name']) self.assertEqual(new_desc, update_volume['display_description']) # Assert response body for get_volume method resp, updated_volume = self.client.get_volume(volume['id']) self.assertEqual(200, resp.status) self.assertEqual(volume['id'], updated_volume['id']) self.assertEqual(new_v_name, updated_volume['display_name']) self.assertEqual(new_desc, updated_volume['display_description']) self.assertThat(updated_volume['metadata'].items(), matchers.ContainsAll(metadata.items()), 'The fetched Volume metadata misses data ' 'from the created Volume') # Test volume create when display_name is none and display_description # contains specific characters, # then test volume update if display_name is duplicated new_volume = {} new_v_desc = data_utils.rand_name('@#$%^* description') resp, new_volume = \ self.client.create_volume(size=1, display_description=new_v_desc, availability_zone= volume['availability_zone']) self.assertEqual(200, resp.status) self.assertIn('id', new_volume) self.addCleanup(self._delete_volume, new_volume['id']) self.client.wait_for_volume_status(new_volume['id'], 'available') resp, update_volume = \ self.client.update_volume(new_volume['id'], display_name=volume['display_name'], display_description= volume['display_description']) self.assertEqual(200, resp.status) # NOTE(jdg): Revert back to strict true/false checking # after fix for bug #1227837 merges boot_flag = self._is_true(updated_volume['bootable']) if 'imageRef' in kwargs: self.assertEqual(boot_flag, True) if 'imageRef' not in kwargs: self.assertEqual(boot_flag, False) @test.attr(type='smoke') def test_volume_create_get_update_delete(self): self._volume_create_get_update_delete() @test.attr(type='smoke') @test.services('image') def test_volume_create_get_update_delete_from_image(self): self._volume_create_get_update_delete(imageRef=CONF.compute.image_ref) @test.attr(type='gate') def test_volume_create_get_update_delete_as_clone(self): origin = self.create_volume() self._volume_create_get_update_delete(source_volid=origin['id']) class VolumesGetTestXML(VolumesGetTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volume_transfers.py0000664000175000017500000001134612332757070026652 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from testtools import matchers from tempest.api.volume import base from tempest import clients from tempest import config from tempest import test CONF = config.CONF class VolumesTransfersTest(base.BaseVolumeV1Test): _interface = "json" @classmethod def setUpClass(cls): super(VolumesTransfersTest, cls).setUpClass() # Add another tenant to test volume-transfer if CONF.compute.allow_tenant_isolation: cls.os_alt = clients.Manager(cls.isolated_creds.get_alt_creds(), interface=cls._interface) cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id'] # Add admin tenant to cleanup resources cls.os_adm = clients.Manager(cls.isolated_creds.get_admin_creds(), interface=cls._interface) else: cls.os_alt = clients.AltManager() alt_tenant_name = cls.os_alt.credentials['tenant_name'] identity_client = cls._get_identity_admin_client() _, tenants = identity_client.list_tenants() cls.alt_tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == alt_tenant_name][0] cls.os_adm = clients.ComputeAdminManager(interface=cls._interface) cls.client = cls.volumes_client cls.alt_client = cls.os_alt.volumes_client cls.adm_client = cls.os_adm.volumes_client def _delete_volume(self, volume_id): # Delete the specified volume using admin creds resp, _ = self.adm_client.delete_volume(volume_id) self.assertEqual(202, resp.status) self.adm_client.wait_for_resource_deletion(volume_id) @test.attr(type='gate') def test_create_get_list_accept_volume_transfer(self): # Create a volume first volume = self.create_volume() self.addCleanup(self._delete_volume, volume['id']) # Create a volume transfer resp, transfer = self.client.create_volume_transfer(volume['id']) self.assertEqual(202, resp.status) transfer_id = transfer['id'] auth_key = transfer['auth_key'] self.client.wait_for_volume_status(volume['id'], 'awaiting-transfer') # Get a volume transfer resp, body = self.client.get_volume_transfer(transfer_id) self.assertEqual(200, resp.status) self.assertEqual(volume['id'], body['volume_id']) # List volume transfers, the result should be greater than # or equal to 1 resp, body = self.client.list_volume_transfers() self.assertEqual(200, resp.status) self.assertThat(len(body), matchers.GreaterThan(0)) # Accept a volume transfer by alt_tenant resp, body = self.alt_client.accept_volume_transfer(transfer_id, auth_key) self.assertEqual(202, resp.status) self.alt_client.wait_for_volume_status(volume['id'], 'available') def test_create_list_delete_volume_transfer(self): # Create a volume first volume = self.create_volume() self.addCleanup(self._delete_volume, volume['id']) # Create a volume transfer resp, body = self.client.create_volume_transfer(volume['id']) self.assertEqual(202, resp.status) transfer_id = body['id'] self.client.wait_for_volume_status(volume['id'], 'awaiting-transfer') # List all volume transfers (looking for the one we created) resp, body = self.client.list_volume_transfers() self.assertEqual(200, resp.status) for transfer in body: if volume['id'] == transfer['volume_id']: break else: self.fail('Transfer not found for volume %s' % volume['id']) # Delete a volume transfer resp, body = self.client.delete_volume_transfer(transfer_id) self.assertEqual(202, resp.status) self.client.wait_for_volume_status(volume['id'], 'available') class VolumesTransfersTestXML(VolumesTransfersTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volumes_negative.py0000664000175000017500000002734112332757070026632 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class VolumesNegativeTest(base.BaseVolumeV1Test): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(VolumesNegativeTest, cls).setUpClass() cls.client = cls.volumes_client # Create a test shared instance and volume for attach/detach tests cls.volume = cls.create_volume() cls.mountpoint = "/dev/vdc" @test.attr(type=['negative', 'gate']) def test_volume_get_nonexistent_volume_id(self): # Should not be able to get a non-existent volume self.assertRaises(exceptions.NotFound, self.client.get_volume, str(uuid.uuid4())) @test.attr(type=['negative', 'gate']) def test_volume_delete_nonexistent_volume_id(self): # Should not be able to delete a non-existent Volume self.assertRaises(exceptions.NotFound, self.client.delete_volume, str(uuid.uuid4())) @test.attr(type=['negative', 'gate']) def test_create_volume_with_invalid_size(self): # Should not be able to create volume with invalid size # in request v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='#$%', display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_create_volume_with_out_passing_size(self): # Should not be able to create volume without passing size # in request v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='', display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_create_volume_with_size_zero(self): # Should not be able to create volume with size zero v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='0', display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_create_volume_with_size_negative(self): # Should not be able to create volume with size negative v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='-1', display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_create_volume_with_nonexistent_volume_type(self): # Should not be able to create volume with non-existent volume type v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.NotFound, self.client.create_volume, size='1', volume_type=str(uuid.uuid4()), display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_create_volume_with_nonexistent_snapshot_id(self): # Should not be able to create volume with non-existent snapshot v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.NotFound, self.client.create_volume, size='1', snapshot_id=str(uuid.uuid4()), display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_create_volume_with_nonexistent_source_volid(self): # Should not be able to create volume with non-existent source volume v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.NotFound, self.client.create_volume, size='1', source_volid=str(uuid.uuid4()), display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_update_volume_with_nonexistent_volume_id(self): v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.NotFound, self.client.update_volume, volume_id=str(uuid.uuid4()), display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_update_volume_with_invalid_volume_id(self): v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.NotFound, self.client.update_volume, volume_id='#$%%&^&^', display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_update_volume_with_empty_volume_id(self): v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.NotFound, self.client.update_volume, volume_id='', display_name=v_name, metadata=metadata) @test.attr(type=['negative', 'gate']) def test_get_invalid_volume_id(self): # Should not be able to get volume with invalid id self.assertRaises(exceptions.NotFound, self.client.get_volume, '#$%%&^&^') @test.attr(type=['negative', 'gate']) def test_get_volume_without_passing_volume_id(self): # Should not be able to get volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.get_volume, '') @test.attr(type=['negative', 'gate']) def test_delete_invalid_volume_id(self): # Should not be able to delete volume when invalid ID is passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '!@#$%^&*()') @test.attr(type=['negative', 'gate']) def test_delete_volume_without_passing_volume_id(self): # Should not be able to delete volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '') @test.attr(type=['negative', 'gate']) def test_attach_volumes_with_nonexistent_volume_id(self): srv_name = data_utils.rand_name('Instance-') resp, server = self.servers_client.create_server(srv_name, self.image_ref, self.flavor_ref) self.addCleanup(self.servers_client.delete_server, server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') self.assertRaises(exceptions.NotFound, self.client.attach_volume, str(uuid.uuid4()), server['id'], self.mountpoint) @test.attr(type=['negative', 'gate']) def test_detach_volumes_with_invalid_volume_id(self): self.assertRaises(exceptions.NotFound, self.client.detach_volume, 'xxx') @test.attr(type=['negative', 'gate']) def test_volume_extend_with_size_smaller_than_original_size(self): # Extend volume with smaller size than original size. extend_size = 0 self.assertRaises(exceptions.BadRequest, self.client.extend_volume, self.volume['id'], extend_size) @test.attr(type=['negative', 'gate']) def test_volume_extend_with_non_number_size(self): # Extend volume when size is non number. extend_size = 'abc' self.assertRaises(exceptions.BadRequest, self.client.extend_volume, self.volume['id'], extend_size) @test.attr(type=['negative', 'gate']) def test_volume_extend_with_None_size(self): # Extend volume with None size. extend_size = None self.assertRaises(exceptions.BadRequest, self.client.extend_volume, self.volume['id'], extend_size) @test.attr(type=['negative', 'gate']) def test_volume_extend_with_nonexistent_volume_id(self): # Extend volume size when volume is nonexistent. extend_size = int(self.volume['size']) + 1 self.assertRaises(exceptions.NotFound, self.client.extend_volume, str(uuid.uuid4()), extend_size) @test.attr(type=['negative', 'gate']) def test_volume_extend_without_passing_volume_id(self): # Extend volume size when passing volume id is None. extend_size = int(self.volume['size']) + 1 self.assertRaises(exceptions.NotFound, self.client.extend_volume, None, extend_size) @test.attr(type=['negative', 'gate']) def test_reserve_volume_with_nonexistent_volume_id(self): self.assertRaises(exceptions.NotFound, self.client.reserve_volume, str(uuid.uuid4())) @test.attr(type=['negative', 'gate']) def test_unreserve_volume_with_nonexistent_volume_id(self): self.assertRaises(exceptions.NotFound, self.client.unreserve_volume, str(uuid.uuid4())) @test.attr(type=['negative', 'gate']) def test_reserve_volume_with_negative_volume_status(self): # Mark volume as reserved. resp, body = self.client.reserve_volume(self.volume['id']) self.assertEqual(202, resp.status) # Mark volume which is marked as reserved before self.assertRaises(exceptions.BadRequest, self.client.reserve_volume, self.volume['id']) # Unmark volume as reserved. resp, body = self.client.unreserve_volume(self.volume['id']) self.assertEqual(202, resp.status) @test.attr(type=['negative', 'gate']) def test_list_volumes_with_nonexistent_name(self): v_name = data_utils.rand_name('Volume-') params = {'display_name': v_name} resp, fetched_volume = self.client.list_volumes(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(fetched_volume)) @test.attr(type=['negative', 'gate']) def test_list_volumes_detail_with_nonexistent_name(self): v_name = data_utils.rand_name('Volume-') params = {'display_name': v_name} resp, fetched_volume = self.client.list_volumes_with_detail(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(fetched_volume)) @test.attr(type=['negative', 'gate']) def test_list_volumes_with_invalid_status(self): params = {'status': 'null'} resp, fetched_volume = self.client.list_volumes(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(fetched_volume)) @test.attr(type=['negative', 'gate']) def test_list_volumes_detail_with_invalid_status(self): params = {'status': 'null'} resp, fetched_volume = self.client.list_volumes_with_detail(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(fetched_volume)) class VolumesNegativeTestXML(VolumesNegativeTest): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/__init__.py0000664000175000017500000000000012332757070023735 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_extensions.py0000664000175000017500000000342712332757070025454 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ExtensionsTestJSON(base.BaseVolumeV1Test): _interface = 'json' @test.attr(type='gate') def test_list_extensions(self): # List of all extensions resp, extensions = self.volumes_extension_client.list_extensions() self.assertEqual(200, resp.status) if len(CONF.volume_feature_enabled.api_extensions) == 0: raise self.skipException('There are not any extensions configured') extension_list = [extension.get('alias') for extension in extensions] LOG.debug("Cinder extensions: %s" % ','.join(extension_list)) ext = CONF.volume_feature_enabled.api_extensions[0] if ext == 'all': self.assertIn('Hosts', map(lambda x: x['name'], extensions)) elif ext: self.assertIn(ext, map(lambda x: x['name'], extensions)) else: raise self.skipException('There are not any extensions configured') class ExtensionsTestXML(ExtensionsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_snapshot_metadata.py0000664000175000017500000001123112332757070026744 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest import test class SnapshotMetadataTest(base.BaseVolumeV1Test): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(SnapshotMetadataTest, cls).setUpClass() cls.client = cls.snapshots_client # Create a volume cls.volume = cls.create_volume() # Create a snapshot cls.snapshot = cls.create_snapshot(volume_id=cls.volume['id']) cls.snapshot_id = cls.snapshot['id'] def tearDown(self): # Update the metadata to {} self.client.update_snapshot_metadata(self.snapshot_id, {}) super(SnapshotMetadataTest, self).tearDown() @test.attr(type='gate') def test_create_get_delete_snapshot_metadata(self): # Create metadata for the snapshot metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} expected = {"key2": "value2", "key3": "value3"} resp, body = self.client.create_snapshot_metadata(self.snapshot_id, metadata) self.assertEqual(200, resp.status) # Get the metadata of the snapshot resp, body = self.client.get_snapshot_metadata(self.snapshot_id) self.assertEqual(200, resp.status) self.assertEqual(metadata, body) # Delete one item metadata of the snapshot resp, body = self.client.delete_snapshot_metadata_item( self.snapshot_id, "key1") self.assertEqual(200, resp.status) resp, body = self.client.get_snapshot_metadata(self.snapshot_id) self.assertEqual(expected, body) @test.attr(type='gate') def test_update_snapshot_metadata(self): # Update metadata for the snapshot metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} update = {"key3": "value3_update", "key4": "value4"} # Create metadata for the snapshot resp, body = self.client.create_snapshot_metadata(self.snapshot_id, metadata) self.assertEqual(200, resp.status) # Get the metadata of the snapshot resp, body = self.client.get_snapshot_metadata(self.snapshot_id) self.assertEqual(200, resp.status) self.assertEqual(metadata, body) # Update metadata item resp, body = self.client.update_snapshot_metadata( self.snapshot_id, update) self.assertEqual(200, resp.status) # Get the metadata of the snapshot resp, body = self.client.get_snapshot_metadata(self.snapshot_id) self.assertEqual(200, resp.status) self.assertEqual(update, body) @test.attr(type='gate') def test_update_snapshot_metadata_item(self): # Update metadata item for the snapshot metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} update_item = {"key3": "value3_update"} expect = {"key1": "value1", "key2": "value2", "key3": "value3_update"} # Create metadata for the snapshot resp, body = self.client.create_snapshot_metadata(self.snapshot_id, metadata) self.assertEqual(200, resp.status) # Get the metadata of the snapshot resp, body = self.client.get_snapshot_metadata(self.snapshot_id) self.assertEqual(metadata, body) # Update metadata item resp, body = self.client.update_snapshot_metadata_item( self.snapshot_id, "key3", update_item) self.assertEqual(200, resp.status) # Get the metadata of the snapshot resp, body = self.client.get_snapshot_metadata(self.snapshot_id) self.assertEqual(200, resp.status) self.assertEqual(expect, body) class SnapshotMetadataTestXML(SnapshotMetadataTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volumes_snapshots.py0000664000175000017500000002067512332757070027055 0ustar chuckchuck00000000000000# 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 tempest.api.volume import base from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest import test LOG = logging.getLogger(__name__) CONF = config.CONF class VolumesSnapshotTest(base.BaseVolumeV1Test): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(VolumesSnapshotTest, cls).setUpClass() cls.volume_origin = cls.create_volume() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") @classmethod def tearDownClass(cls): super(VolumesSnapshotTest, cls).tearDownClass() def _detach(self, volume_id): """Detach volume.""" self.volumes_client.detach_volume(volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') def _list_by_param_values_and_assert(self, params, with_detail=False): """ Perform list or list_details action with given params and validates result. """ if with_detail: resp, fetched_snap_list = \ self.snapshots_client.\ list_snapshots_with_detail(params=params) else: resp, fetched_snap_list = \ self.snapshots_client.list_snapshots(params=params) self.assertEqual(200, resp.status) # Validating params of fetched snapshots for snap in fetched_snap_list: for key in params: msg = "Failed to list snapshots %s by %s" % \ ('details' if with_detail else '', key) self.assertEqual(params[key], snap[key], msg) @test.attr(type='gate') def test_snapshot_create_with_volume_in_use(self): # Create a snapshot when volume status is in-use # Create a test instance server_name = data_utils.rand_name('instance-') resp, server = self.servers_client.create_server(server_name, self.image_ref, self.flavor_ref) self.addCleanup(self.servers_client.delete_server, server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') mountpoint = '/dev/%s' % CONF.compute.volume_device_name resp, body = self.volumes_client.attach_volume( self.volume_origin['id'], server['id'], mountpoint) self.assertEqual(202, resp.status) self.volumes_client.wait_for_volume_status(self.volume_origin['id'], 'in-use') self.addCleanup(self._detach, self.volume_origin['id']) # Snapshot a volume even if it's attached to an instance snapshot = self.create_snapshot(self.volume_origin['id'], force=True) # Delete the snapshot self.snapshots_client.delete_snapshot(snapshot['id']) self.assertEqual(202, resp.status) self.snapshots_client.wait_for_resource_deletion(snapshot['id']) self.snapshots.remove(snapshot) @test.attr(type='gate') def test_snapshot_create_get_list_update_delete(self): # Create a snapshot s_name = data_utils.rand_name('snap') snapshot = self.create_snapshot(self.volume_origin['id'], display_name=s_name) # Get the snap and check for some of its details resp, snap_get = self.snapshots_client.get_snapshot(snapshot['id']) self.assertEqual(200, resp.status) self.assertEqual(self.volume_origin['id'], snap_get['volume_id'], "Referred volume origin mismatch") # Compare also with the output from the list action tracking_data = (snapshot['id'], snapshot['display_name']) resp, snaps_list = self.snapshots_client.list_snapshots() self.assertEqual(200, resp.status) snaps_data = [(f['id'], f['display_name']) for f in snaps_list] self.assertIn(tracking_data, snaps_data) # Updates snapshot with new values new_s_name = data_utils.rand_name('new-snap') new_desc = 'This is the new description of snapshot.' resp, update_snapshot = \ self.snapshots_client.update_snapshot(snapshot['id'], display_name=new_s_name, display_description=new_desc) # Assert response body for update_snapshot method self.assertEqual(200, resp.status) self.assertEqual(new_s_name, update_snapshot['display_name']) self.assertEqual(new_desc, update_snapshot['display_description']) # Assert response body for get_snapshot method resp, updated_snapshot = \ self.snapshots_client.get_snapshot(snapshot['id']) self.assertEqual(200, resp.status) self.assertEqual(new_s_name, updated_snapshot['display_name']) self.assertEqual(new_desc, updated_snapshot['display_description']) # Delete the snapshot self.snapshots_client.delete_snapshot(snapshot['id']) self.assertEqual(200, resp.status) self.snapshots_client.wait_for_resource_deletion(snapshot['id']) self.snapshots.remove(snapshot) @test.attr(type='gate') def test_snapshots_list_with_params(self): """list snapshots with params.""" # Create a snapshot display_name = data_utils.rand_name('snap') snapshot = self.create_snapshot(self.volume_origin['id'], display_name=display_name) # Verify list snapshots by display_name filter params = {'display_name': snapshot['display_name']} self._list_by_param_values_and_assert(params) # Verify list snapshots by status filter params = {'status': 'available'} self._list_by_param_values_and_assert(params) # Verify list snapshots by status and display name filter params = {'status': 'available', 'display_name': snapshot['display_name']} self._list_by_param_values_and_assert(params) @test.attr(type='gate') def test_snapshots_list_details_with_params(self): """list snapshot details with params.""" # Create a snapshot display_name = data_utils.rand_name('snap') snapshot = self.create_snapshot(self.volume_origin['id'], display_name=display_name) # Verify list snapshot details by display_name filter params = {'display_name': snapshot['display_name']} self._list_by_param_values_and_assert(params, with_detail=True) # Verify list snapshot details by status filter params = {'status': 'available'} self._list_by_param_values_and_assert(params, with_detail=True) # Verify list snapshot details by status and display name filter params = {'status': 'available', 'display_name': snapshot['display_name']} self._list_by_param_values_and_assert(params, with_detail=True) @test.attr(type='gate') def test_volume_from_snapshot(self): # Create a temporary snap using wrapper method from base, then # create a snap based volume, check resp code and deletes it snapshot = self.create_snapshot(self.volume_origin['id']) # NOTE(gfidente): size is required also when passing snapshot_id resp, volume = self.volumes_client.create_volume( size=1, snapshot_id=snapshot['id']) self.assertEqual(200, resp.status) self.volumes_client.wait_for_volume_status(volume['id'], 'available') self.volumes_client.delete_volume(volume['id']) self.volumes_client.wait_for_resource_deletion(volume['id']) self.clear_snapshots() class VolumesSnapshotTestXML(VolumesSnapshotTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volumes_snapshots_negative.py0000664000175000017500000000361612332757070030733 0ustar chuckchuck00000000000000# 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 uuid from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class VolumesSnapshotNegativeTest(base.BaseVolumeV1Test): _interface = "json" @classmethod def setUpClass(cls): super(VolumesSnapshotNegativeTest, cls).setUpClass() if not CONF.volume_feature_enabled.snapshot: raise cls.skipException("Cinder volume snapshots are disabled") @test.attr(type=['negative', 'gate']) def test_create_snapshot_with_nonexistent_volume_id(self): # Create a snapshot with nonexistent volume id s_name = data_utils.rand_name('snap') self.assertRaises(exceptions.NotFound, self.snapshots_client.create_snapshot, str(uuid.uuid4()), display_name=s_name) @test.attr(type=['negative', 'gate']) def test_create_snapshot_without_passing_volume_id(self): # Create a snapshot without passing volume id s_name = data_utils.rand_name('snap') self.assertRaises(exceptions.NotFound, self.snapshots_client.create_snapshot, None, display_name=s_name) class VolumesSnapshotNegativeTestXML(VolumesSnapshotNegativeTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/0000775000175000017500000000000012332757136022731 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volumes_actions.py0000664000175000017500000001010012332757070027541 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common.utils import data_utils as utils from tempest import test class VolumesActionsTest(base.BaseVolumeV1AdminTest): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(VolumesActionsTest, cls).setUpClass() cls.client = cls.volumes_client # Create admin volume client cls.admin_volume_client = cls.os_adm.volumes_client # Create a test shared volume for tests vol_name = utils.rand_name(cls.__name__ + '-Volume-') resp, cls.volume = cls.client.create_volume(size=1, display_name=vol_name) cls.client.wait_for_volume_status(cls.volume['id'], 'available') @classmethod def tearDownClass(cls): # Delete the test volume cls.client.delete_volume(cls.volume['id']) cls.client.wait_for_resource_deletion(cls.volume['id']) super(VolumesActionsTest, cls).tearDownClass() def _reset_volume_status(self, volume_id, status): # Reset the volume status resp, body = self.admin_volume_client.reset_volume_status(volume_id, status) return resp, body def tearDown(self): # Set volume's status to available after test self._reset_volume_status(self.volume['id'], 'available') super(VolumesActionsTest, self).tearDown() def _create_temp_volume(self): # Create a temp volume for force delete tests vol_name = utils.rand_name('Volume') resp, temp_volume = self.client.create_volume(size=1, display_name=vol_name) self.client.wait_for_volume_status(temp_volume['id'], 'available') return temp_volume def _create_reset_and_force_delete_temp_volume(self, status=None): # Create volume, reset volume status, and force delete temp volume temp_volume = self._create_temp_volume() if status: resp, body = self._reset_volume_status(temp_volume['id'], status) self.assertEqual(202, resp.status) resp_delete, volume_delete = self.admin_volume_client.\ force_delete_volume(temp_volume['id']) self.assertEqual(202, resp_delete.status) self.client.wait_for_resource_deletion(temp_volume['id']) @test.attr(type='gate') def test_volume_reset_status(self): # test volume reset status : available->error->available resp, body = self._reset_volume_status(self.volume['id'], 'error') self.assertEqual(202, resp.status) resp_get, volume_get = self.admin_volume_client.get_volume( self.volume['id']) self.assertEqual('error', volume_get['status']) def test_volume_force_delete_when_volume_is_creating(self): # test force delete when status of volume is creating self._create_reset_and_force_delete_temp_volume('creating') def test_volume_force_delete_when_volume_is_attaching(self): # test force delete when status of volume is attaching self._create_reset_and_force_delete_temp_volume('attaching') @test.attr(type='gate') def test_volume_force_delete_when_volume_is_error(self): # test force delete when status of volume is error self._create_reset_and_force_delete_temp_volume('error') class VolumesActionsTestXML(VolumesActionsTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_snapshots_actions.py0000664000175000017500000001342312332757070030104 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import test class SnapshotsActionsTest(base.BaseVolumeV1AdminTest): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(SnapshotsActionsTest, cls).setUpClass() cls.client = cls.snapshots_client # Create admin volume client cls.admin_snapshots_client = cls.os_adm.snapshots_client # Create a test shared volume for tests vol_name = data_utils.rand_name(cls.__name__ + '-Volume-') resp_vol, cls.volume = \ cls.volumes_client.create_volume(size=1, display_name=vol_name) cls.volumes_client.wait_for_volume_status(cls.volume['id'], 'available') # Create a test shared snapshot for tests snap_name = data_utils.rand_name(cls.__name__ + '-Snapshot-') resp_snap, cls.snapshot = \ cls.client.create_snapshot(cls.volume['id'], display_name=snap_name) cls.client.wait_for_snapshot_status(cls.snapshot['id'], 'available') @classmethod def tearDownClass(cls): # Delete the test snapshot cls.client.delete_snapshot(cls.snapshot['id']) cls.client.wait_for_resource_deletion(cls.snapshot['id']) # Delete the test volume cls.volumes_client.delete_volume(cls.volume['id']) cls.volumes_client.wait_for_resource_deletion(cls.volume['id']) super(SnapshotsActionsTest, cls).tearDownClass() def tearDown(self): # Set snapshot's status to available after test status = 'available' snapshot_id = self.snapshot['id'] self.admin_snapshots_client.reset_snapshot_status(snapshot_id, status) super(SnapshotsActionsTest, self).tearDown() def _create_reset_and_force_delete_temp_snapshot(self, status=None): # Create snapshot, reset snapshot status, # and force delete temp snapshot temp_snapshot = self.create_snapshot(self.volume['id']) if status: resp, body = self.admin_snapshots_client.\ reset_snapshot_status(temp_snapshot['id'], status) self.assertEqual(202, resp.status) resp_delete, volume_delete = self.admin_snapshots_client.\ force_delete_snapshot(temp_snapshot['id']) self.assertEqual(202, resp_delete.status) self.client.wait_for_resource_deletion(temp_snapshot['id']) def _get_progress_alias(self): return 'os-extended-snapshot-attributes:progress' @test.attr(type='gate') def test_reset_snapshot_status(self): # Reset snapshot status to creating status = 'creating' resp, body = self.admin_snapshots_client.\ reset_snapshot_status(self.snapshot['id'], status) self.assertEqual(202, resp.status) resp_get, snapshot_get \ = self.admin_snapshots_client.get_snapshot(self.snapshot['id']) self.assertEqual(200, resp_get.status) self.assertEqual(status, snapshot_get['status']) @test.attr(type='gate') def test_update_snapshot_status(self): # Reset snapshot status to creating status = 'creating' self.admin_snapshots_client.\ reset_snapshot_status(self.snapshot['id'], status) # Update snapshot status to error progress = '80%' status = 'error' progress_alias = self._get_progress_alias() resp, body = self.client.update_snapshot_status(self.snapshot['id'], status, progress) self.assertEqual(202, resp.status) resp_get, snapshot_get \ = self.admin_snapshots_client.get_snapshot(self.snapshot['id']) self.assertEqual(200, resp_get.status) self.assertEqual(status, snapshot_get['status']) self.assertEqual(progress, snapshot_get[progress_alias]) @test.attr(type='gate') def test_snapshot_force_delete_when_snapshot_is_creating(self): # test force delete when status of snapshot is creating self._create_reset_and_force_delete_temp_snapshot('creating') @test.attr(type='gate') def test_snapshot_force_delete_when_snapshot_is_deleting(self): # test force delete when status of snapshot is deleting self._create_reset_and_force_delete_temp_snapshot('deleting') @test.attr(type='gate') def test_snapshot_force_delete_when_snapshot_is_error(self): # test force delete when status of snapshot is error self._create_reset_and_force_delete_temp_snapshot('error') @test.attr(type='gate') def test_snapshot_force_delete_when_snapshot_is_error_deleting(self): # test force delete when status of snapshot is error_deleting self._create_reset_and_force_delete_temp_snapshot('error_deleting') class SnapshotsActionsTestXML(SnapshotsActionsTest): _interface = "xml" def _get_progress_alias(self): return '{http://docs.openstack.org/volume/ext' \ '/extended_snapshot_attributes/api/v1}progress' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_hosts.py0000664000175000017500000000220312332757070027063 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest import test class VolumeHostsAdminTestsJSON(base.BaseVolumeV1AdminTest): _interface = "json" @test.attr(type='gate') def test_list_hosts(self): resp, hosts = self.hosts_client.list_hosts() self.assertEqual(200, resp.status) self.assertTrue(len(hosts) >= 2, "No. of hosts are < 2," "response of list hosts is: % s" % hosts) class VolumeHostsAdminTestsXML(VolumeHostsAdminTestsJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_types_extra_specs.py0000664000175000017500000000712412332757070031476 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import test class VolumeTypesExtraSpecsTest(base.BaseVolumeV1AdminTest): _interface = "json" @classmethod def setUpClass(cls): super(VolumeTypesExtraSpecsTest, cls).setUpClass() vol_type_name = data_utils.rand_name('Volume-type-') resp, cls.volume_type = cls.client.create_volume_type(vol_type_name) @classmethod def tearDownClass(cls): cls.client.delete_volume_type(cls.volume_type['id']) super(VolumeTypesExtraSpecsTest, cls).tearDownClass() @test.attr(type='smoke') def test_volume_type_extra_specs_list(self): # List Volume types extra specs. extra_specs = {"spec1": "val1"} resp, body = self.client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") resp, body = self.client.list_volume_types_extra_specs( self.volume_type['id']) self.assertEqual(200, resp.status) self.assertIsInstance(body, dict) self.assertIn('spec1', body) @test.attr(type='gate') def test_volume_type_extra_specs_update(self): # Update volume type extra specs extra_specs = {"spec2": "val1"} resp, body = self.client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") extra_spec = {"spec2": "val2"} resp, body = self.client.update_volume_type_extra_specs( self.volume_type['id'], extra_spec.keys()[0], extra_spec) self.assertEqual(200, resp.status) self.assertIn('spec2', body) self.assertEqual(extra_spec['spec2'], body['spec2'], "Volume type extra spec incorrectly updated") @test.attr(type='smoke') def test_volume_type_extra_spec_create_get_delete(self): # Create/Get/Delete volume type extra spec. extra_specs = {"spec3": "val1"} resp, body = self.client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") resp, _ = self.client.get_volume_type_extra_specs( self.volume_type['id'], extra_specs.keys()[0]) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly fetched") resp, _ = self.client.delete_volume_type_extra_specs( self.volume_type['id'], extra_specs.keys()[0]) self.assertEqual(202, resp.status) tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_types.py0000664000175000017500000001560312332757070027077 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class VolumeTypesTest(base.BaseVolumeV1AdminTest): _interface = "json" def _delete_volume(self, volume_id): resp, _ = self.volumes_client.delete_volume(volume_id) self.assertEqual(202, resp.status) self.volumes_client.wait_for_resource_deletion(volume_id) def _delete_volume_type(self, volume_type_id): resp, _ = self.client.delete_volume_type(volume_type_id) self.assertEqual(202, resp.status) @test.attr(type='smoke') def test_volume_type_list(self): # List Volume types. resp, body = self.client.list_volume_types() self.assertEqual(200, resp.status) self.assertIsInstance(body, list) @test.attr(type='smoke') def test_create_get_delete_volume_with_volume_type_and_extra_specs(self): # Create/get/delete volume with volume_type and extra spec. volume = {} vol_name = data_utils.rand_name("volume-") vol_type_name = data_utils.rand_name("volume-type-") proto = CONF.volume.storage_protocol vendor = CONF.volume.vendor_name extra_specs = {"storage_protocol": proto, "vendor_name": vendor} body = {} resp, body = self.client.create_volume_type( vol_type_name, extra_specs=extra_specs) self.assertEqual(200, resp.status) self.assertIn('id', body) self.addCleanup(self._delete_volume_type, body['id']) self.assertIn('name', body) resp, volume = self.volumes_client.create_volume( size=1, display_name=vol_name, volume_type=vol_type_name) self.assertEqual(200, resp.status) self.assertIn('id', volume) self.addCleanup(self._delete_volume, volume['id']) self.assertIn('display_name', volume) self.assertEqual(volume['display_name'], vol_name, "The created volume name is not equal " "to the requested name") self.assertTrue(volume['id'] is not None, "Field volume id is empty or not found.") self.volumes_client.wait_for_volume_status(volume['id'], 'available') resp, fetched_volume = self.volumes_client.get_volume(volume['id']) self.assertEqual(200, resp.status) self.assertEqual(vol_name, fetched_volume['display_name'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(vol_type_name, fetched_volume['volume_type'], 'The fetched Volume is different ' 'from the created Volume') @test.attr(type='smoke') def test_volume_type_create_get_delete(self): # Create/get volume type. body = {} name = data_utils.rand_name("volume-type-") proto = CONF.volume.storage_protocol vendor = CONF.volume.vendor_name extra_specs = {"storage_protocol": proto, "vendor_name": vendor} resp, body = self.client.create_volume_type( name, extra_specs=extra_specs) self.assertEqual(200, resp.status) self.assertIn('id', body) self.addCleanup(self._delete_volume_type, body['id']) self.assertIn('name', body) self.assertEqual(body['name'], name, "The created volume_type name is not equal " "to the requested name") self.assertTrue(body['id'] is not None, "Field volume_type id is empty or not found.") resp, fetched_volume_type = self.client.get_volume_type(body['id']) self.assertEqual(200, resp.status) self.assertEqual(name, fetched_volume_type['name'], 'The fetched Volume_type is different ' 'from the created Volume_type') self.assertEqual(str(body['id']), fetched_volume_type['id'], 'The fetched Volume_type is different ' 'from the created Volume_type') self.assertEqual(extra_specs, fetched_volume_type['extra_specs'], 'The fetched Volume_type is different ' 'from the created Volume_type') @test.attr(type='smoke') def test_volume_type_encryption_create_get(self): # Create/get encryption type. provider = "LuksEncryptor" control_location = "front-end" name = data_utils.rand_name("volume-type-") resp, body = self.client.create_volume_type(name) self.assertEqual(200, resp.status) self.addCleanup(self._delete_volume_type, body['id']) resp, encryption_type = self.client.create_encryption_type( body['id'], provider=provider, control_location=control_location) self.assertEqual(200, resp.status) self.assertIn('volume_type_id', encryption_type) self.assertEqual(provider, encryption_type['provider'], "The created encryption_type provider is not equal " "to the requested provider") self.assertEqual(control_location, encryption_type['control_location'], "The created encryption_type control_location is not " "equal to the requested control_location") resp, fetched_encryption_type = self.client.get_encryption_type( encryption_type['volume_type_id']) self.assertEqual(200, resp.status) self.assertEqual(provider, fetched_encryption_type['provider'], 'The fetched encryption_type provider is different ' 'from the created encryption_type') self.assertEqual(control_location, fetched_encryption_type['control_location'], 'The fetched encryption_type control_location is ' 'different from the created encryption_type') tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volumes_backup.py0000664000175000017500000000631712332757070027365 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class VolumesBackupsTest(base.BaseVolumeV1AdminTest): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(VolumesBackupsTest, cls).setUpClass() if not CONF.volume_feature_enabled.backup: raise cls.skipException("Cinder backup feature disabled") cls.volumes_adm_client = cls.os_adm.volumes_client cls.backups_adm_client = cls.os_adm.backups_client cls.volume = cls.create_volume() @test.attr(type='smoke') def test_volume_backup_create_get_detailed_list_restore_delete(self): # Create backup backup_name = data_utils.rand_name('Backup') create_backup = self.backups_adm_client.create_backup resp, backup = create_backup(self.volume['id'], name=backup_name) self.assertEqual(202, resp.status) self.addCleanup(self.backups_adm_client.delete_backup, backup['id']) self.assertEqual(backup_name, backup['name']) self.volumes_adm_client.wait_for_volume_status(self.volume['id'], 'available') self.backups_adm_client.wait_for_backup_status(backup['id'], 'available') # Get a given backup resp, backup = self.backups_adm_client.get_backup(backup['id']) self.assertEqual(200, resp.status) self.assertEqual(backup_name, backup['name']) # Get all backups with detail resp, backups = self.backups_adm_client.list_backups_with_detail() self.assertEqual(200, resp.status) self.assertIn((backup['name'], backup['id']), [(m['name'], m['id']) for m in backups]) # Restore backup resp, restore = self.backups_adm_client.restore_backup(backup['id']) self.assertEqual(202, resp.status) # Delete backup self.addCleanup(self.volumes_adm_client.delete_volume, restore['volume_id']) self.assertEqual(backup['id'], restore['backup_id']) self.backups_adm_client.wait_for_backup_status(backup['id'], 'available') self.volumes_adm_client.wait_for_volume_status(restore['volume_id'], 'available') tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/__init__.py0000664000175000017500000000000012332757070025025 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_quotas_negative.py0000664000175000017500000000641012332757070031125 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest import exceptions from tempest import test class VolumeQuotasNegativeTestJSON(base.BaseVolumeV1AdminTest): _interface = "json" force_tenant_isolation = True @classmethod @test.safe_setup def setUpClass(cls): super(VolumeQuotasNegativeTestJSON, cls).setUpClass() demo_user = cls.isolated_creds.get_primary_user() cls.demo_tenant_id = demo_user.get('tenantId') cls.shared_quota_set = {'gigabytes': 3, 'volumes': 1, 'snapshots': 1} # NOTE(gfidente): no need to restore original quota set # after the tests as they only work with tenant isolation. resp, quota_set = cls.quotas_client.update_quota_set( cls.demo_tenant_id, **cls.shared_quota_set) # NOTE(gfidente): no need to delete in tearDown as # they are created using utility wrapper methods. cls.volume = cls.create_volume() cls.snapshot = cls.create_snapshot(cls.volume['id']) @test.attr(type='negative') def test_quota_volumes(self): self.assertRaises(exceptions.OverLimit, self.volumes_client.create_volume, size=1) @test.attr(type='negative') def test_quota_volume_snapshots(self): self.assertRaises(exceptions.OverLimit, self.snapshots_client.create_snapshot, self.volume['id']) @test.attr(type='negative') def test_quota_volume_gigabytes(self): # NOTE(gfidente): quota set needs to be changed for this test # or we may be limited by the volumes or snaps quota number, not by # actual gigs usage; next line ensures shared set is restored. self.addCleanup(self.quotas_client.update_quota_set, self.demo_tenant_id, **self.shared_quota_set) new_quota_set = {'gigabytes': 2, 'volumes': 2, 'snapshots': 1} resp, quota_set = self.quotas_client.update_quota_set( self.demo_tenant_id, **new_quota_set) self.assertRaises(exceptions.OverLimit, self.volumes_client.create_volume, size=1) new_quota_set = {'gigabytes': 2, 'volumes': 1, 'snapshots': 2} resp, quota_set = self.quotas_client.update_quota_set( self.demo_tenant_id, **self.shared_quota_set) self.assertRaises(exceptions.OverLimit, self.snapshots_client.create_snapshot, self.volume['id']) class VolumeQuotasNegativeTestXML(VolumeQuotasNegativeTestJSON): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py0000664000175000017500000001277512332757070033370 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.volume import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ExtraSpecsNegativeTest(base.BaseVolumeV1AdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(ExtraSpecsNegativeTest, cls).setUpClass() vol_type_name = data_utils.rand_name('Volume-type-') cls.extra_specs = {"spec1": "val1"} resp, cls.volume_type = cls.client.create_volume_type(vol_type_name, extra_specs= cls.extra_specs) @classmethod def tearDownClass(cls): cls.client.delete_volume_type(cls.volume_type['id']) super(ExtraSpecsNegativeTest, cls).tearDownClass() @test.attr(type='gate') def test_update_no_body(self): # Should not update volume type extra specs with no body extra_spec = {"spec1": "val2"} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], extra_spec.keys()[0], None) @test.attr(type='gate') def test_update_nonexistent_extra_spec_id(self): # Should not update volume type extra specs with nonexistent id. extra_spec = {"spec1": "val2"} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], str(uuid.uuid4()), extra_spec) @test.attr(type='gate') def test_update_none_extra_spec_id(self): # Should not update volume type extra specs with none id. extra_spec = {"spec1": "val2"} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], None, extra_spec) @test.attr(type='gate') def test_update_multiple_extra_spec(self): # Should not update volume type extra specs with multiple specs as # body. extra_spec = {"spec1": "val2", 'spec2': 'val1'} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], extra_spec.keys()[0], extra_spec) @test.attr(type='gate') def test_create_nonexistent_type_id(self): # Should not create volume type extra spec for nonexistent volume # type id. extra_specs = {"spec2": "val1"} self.assertRaises(exceptions.NotFound, self.client.create_volume_type_extra_specs, str(uuid.uuid4()), extra_specs) @test.attr(type='gate') def test_create_none_body(self): # Should not create volume type extra spec for none POST body. self.assertRaises(exceptions.BadRequest, self.client.create_volume_type_extra_specs, self.volume_type['id'], None) @test.attr(type='gate') def test_create_invalid_body(self): # Should not create volume type extra spec for invalid POST body. self.assertRaises(exceptions.BadRequest, self.client.create_volume_type_extra_specs, self.volume_type['id'], ['invalid']) @test.attr(type='gate') def test_delete_nonexistent_volume_type_id(self): # Should not delete volume type extra spec for nonexistent # type id. extra_specs = {"spec1": "val1"} self.assertRaises(exceptions.NotFound, self.client.delete_volume_type_extra_specs, str(uuid.uuid4()), extra_specs.keys()[0]) @test.attr(type='gate') def test_list_nonexistent_volume_type_id(self): # Should not list volume type extra spec for nonexistent type id. self.assertRaises(exceptions.NotFound, self.client.list_volume_types_extra_specs, str(uuid.uuid4())) @test.attr(type='gate') def test_get_nonexistent_volume_type_id(self): # Should not get volume type extra spec for nonexistent type id. extra_specs = {"spec1": "val1"} self.assertRaises(exceptions.NotFound, self.client.get_volume_type_extra_specs, str(uuid.uuid4()), extra_specs.keys()[0]) @test.attr(type='gate') def test_get_nonexistent_extra_spec_id(self): # Should not get volume type extra spec for nonexistent extra spec # id. self.assertRaises(exceptions.NotFound, self.client.get_volume_type_extra_specs, self.volume_type['id'], str(uuid.uuid4())) class ExtraSpecsNegativeTestXML(ExtraSpecsNegativeTest): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_services.py0000664000175000017500000000546312332757070027561 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.volume import base from tempest import test class VolumesServicesTestJSON(base.BaseVolumeV1AdminTest): """ Tests Volume Services API. volume service list requires admin privileges. """ _interface = "json" @classmethod def setUpClass(cls): super(VolumesServicesTestJSON, cls).setUpClass() cls.client = cls.os_adm.volume_services_client resp, cls.services = cls.client.list_services() cls.host_name = cls.services[0]['host'] cls.binary_name = cls.services[0]['binary'] @test.attr(type='gate') def test_list_services(self): resp, services = self.client.list_services() self.assertEqual(200, resp.status) self.assertNotEqual(0, len(services)) @test.attr(type='gate') def test_get_service_by_service_binary_name(self): params = {'binary': self.binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertNotEqual(0, len(services)) for service in services: self.assertEqual(self.binary_name, service['binary']) @test.attr(type='gate') def test_get_service_by_host_name(self): services_on_host = [service for service in self.services if service['host'] == self.host_name] params = {'host': self.host_name} resp, services = self.client.list_services(params) # we could have a periodic job checkin between the 2 service # lookups, so only compare binary lists. s1 = map(lambda x: x['binary'], services) s2 = map(lambda x: x['binary'], services_on_host) # sort the lists before comparing, to take out dependency # on order. self.assertEqual(sorted(s1), sorted(s2)) @test.attr(type='gate') def test_get_service_by_service_and_host_name(self): params = {'host': self.host_name, 'binary': self.binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(1, len(services)) self.assertEqual(self.host_name, services[0]['host']) self.assertEqual(self.binary_name, services[0]['binary']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_multi_backend.py0000664000175000017500000001151012332757070027136 0ustar chuckchuck00000000000000# 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 tempest.api.volume import base from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class VolumeMultiBackendTest(base.BaseVolumeV1AdminTest): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(VolumeMultiBackendTest, cls).setUpClass() if not CONF.volume_feature_enabled.multi_backend: raise cls.skipException("Cinder multi-backend feature disabled") cls.backend1_name = CONF.volume.backend1_name cls.backend2_name = CONF.volume.backend2_name cls.volume_client = cls.os_adm.volumes_client cls.volume_type_id_list = [] cls.volume_id_list = [] # Volume/Type creation (uses backend1_name) type1_name = data_utils.rand_name('Type-') vol1_name = data_utils.rand_name('Volume-') extra_specs1 = {"volume_backend_name": cls.backend1_name} resp, cls.type1 = cls.client.create_volume_type( type1_name, extra_specs=extra_specs1) cls.volume_type_id_list.append(cls.type1['id']) resp, cls.volume1 = cls.volume_client.create_volume( size=1, display_name=vol1_name, volume_type=type1_name) cls.volume_id_list.append(cls.volume1['id']) cls.volume_client.wait_for_volume_status(cls.volume1['id'], 'available') if cls.backend1_name != cls.backend2_name: # Volume/Type creation (uses backend2_name) type2_name = data_utils.rand_name('Type-') vol2_name = data_utils.rand_name('Volume-') extra_specs2 = {"volume_backend_name": cls.backend2_name} resp, cls.type2 = cls.client.create_volume_type( type2_name, extra_specs=extra_specs2) cls.volume_type_id_list.append(cls.type2['id']) resp, cls.volume2 = cls.volume_client.create_volume( size=1, display_name=vol2_name, volume_type=type2_name) cls.volume_id_list.append(cls.volume2['id']) cls.volume_client.wait_for_volume_status(cls.volume2['id'], 'available') @classmethod def tearDownClass(cls): # volumes deletion volume_id_list = getattr(cls, 'volume_id_list', []) for volume_id in volume_id_list: cls.volume_client.delete_volume(volume_id) cls.volume_client.wait_for_resource_deletion(volume_id) # volume types deletion volume_type_id_list = getattr(cls, 'volume_type_id_list', []) for volume_type_id in volume_type_id_list: cls.client.delete_volume_type(volume_type_id) super(VolumeMultiBackendTest, cls).tearDownClass() @test.attr(type='smoke') def test_backend_name_reporting(self): # this test checks if os-vol-attr:host is populated correctly after # the multi backend feature has been enabled # if multi-backend is enabled: os-vol-attr:host should be like: # host@backend_name resp, volume = self.volume_client.get_volume(self.volume1['id']) self.assertEqual(200, resp.status) volume1_host = volume['os-vol-host-attr:host'] msg = ("multi-backend reporting incorrect values for volume %s" % self.volume1['id']) self.assertTrue(len(volume1_host.split("@")) > 1, msg) @test.attr(type='gate') def test_backend_name_distinction(self): # this test checks that the two volumes created at setUp don't # belong to the same backend (if they are, than the # volume backend distinction is not working properly) if self.backend1_name == self.backend2_name: raise self.skipException("backends configured with same name") resp, volume = self.volume_client.get_volume(self.volume1['id']) volume1_host = volume['os-vol-host-attr:host'] resp, volume = self.volume_client.get_volume(self.volume2['id']) volume2_host = volume['os-vol-host-attr:host'] msg = ("volumes %s and %s were created in the same backend" % (self.volume1['id'], self.volume2['id'])) self.assertNotEqual(volume1_host, volume2_host, msg) tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_quotas.py0000664000175000017500000000766012332757070027253 0ustar chuckchuck00000000000000# Copyright (C) 2014 eNovance SAS # # Author: Sylvain Baubeau # # 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 tempest.api.volume import base from tempest import test QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes'] QUOTA_USAGE_KEYS = ['reserved', 'limit', 'in_use'] class VolumeQuotasAdminTestJSON(base.BaseVolumeV1AdminTest): _interface = "json" force_tenant_isolation = True @classmethod def setUpClass(cls): super(VolumeQuotasAdminTestJSON, cls).setUpClass() cls.admin_volume_client = cls.os_adm.volumes_client cls.demo_tenant_id = cls.isolated_creds.get_primary_creds().tenant_id @test.attr(type='gate') def test_list_quotas(self): resp, quotas = self.quotas_client.get_quota_set(self.demo_tenant_id) self.assertEqual(200, resp.status) for key in QUOTA_KEYS: self.assertIn(key, quotas) @test.attr(type='gate') def test_list_default_quotas(self): resp, quotas = self.quotas_client.get_default_quota_set( self.demo_tenant_id) self.assertEqual(200, resp.status) for key in QUOTA_KEYS: self.assertIn(key, quotas) @test.attr(type='gate') def test_update_all_quota_resources_for_tenant(self): # Admin can update all the resource quota limits for a tenant resp, default_quota_set = self.quotas_client.get_default_quota_set( self.demo_tenant_id) new_quota_set = {'gigabytes': 1009, 'volumes': 11, 'snapshots': 11} # Update limits for all quota resources resp, quota_set = self.quotas_client.update_quota_set( self.demo_tenant_id, **new_quota_set) cleanup_quota_set = dict( (k, v) for k, v in default_quota_set.iteritems() if k in QUOTA_KEYS) self.addCleanup(self.quotas_client.update_quota_set, self.demo_tenant_id, **cleanup_quota_set) self.assertEqual(200, resp.status) # test that the specific values we set are actually in # the final result. There is nothing here that ensures there # would be no other values in there. self.assertDictContainsSubset(new_quota_set, quota_set) @test.attr(type='gate') def test_show_quota_usage(self): resp, quota_usage = self.quotas_client.get_quota_usage(self.adm_tenant) self.assertEqual(200, resp.status) for key in QUOTA_KEYS: self.assertIn(key, quota_usage) for usage_key in QUOTA_USAGE_KEYS: self.assertIn(usage_key, quota_usage[key]) @test.attr(type='gate') def test_quota_usage(self): resp, quota_usage = self.quotas_client.get_quota_usage( self.demo_tenant_id) volume = self.create_volume(size=1) self.addCleanup(self.admin_volume_client.delete_volume, volume['id']) resp, new_quota_usage = self.quotas_client.get_quota_usage( self.demo_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(quota_usage['volumes']['in_use'] + 1, new_quota_usage['volumes']['in_use']) self.assertEqual(quota_usage['gigabytes']['in_use'] + 1, new_quota_usage['gigabytes']['in_use']) class VolumeQuotasAdminTestXML(VolumeQuotasAdminTestJSON): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/admin/test_volume_types_negative.py0000664000175000017500000000406612332757070030762 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.volume import base from tempest import exceptions from tempest import test class VolumeTypesNegativeTest(base.BaseVolumeV1AdminTest): _interface = 'json' @test.attr(type='gate') def test_create_with_nonexistent_volume_type(self): # Should not be able to create volume with nonexistent volume_type. self.assertRaises(exceptions.NotFound, self.volumes_client.create_volume, size=1, display_name=str(uuid.uuid4()), volume_type=str(uuid.uuid4())) @test.attr(type='gate') def test_create_with_empty_name(self): # Should not be able to create volume type with an empty name. self.assertRaises(exceptions.BadRequest, self.client.create_volume_type, '') @test.attr(type='gate') def test_get_nonexistent_type_id(self): # Should not be able to get volume type with nonexistent type id. self.assertRaises(exceptions.NotFound, self.client.get_volume_type, str(uuid.uuid4())) @test.attr(type='gate') def test_delete_nonexistent_type_id(self): # Should not be able to delete volume type with nonexistent type id. self.assertRaises(exceptions.NotFound, self.client.delete_volume_type, str(uuid.uuid4())) class VolumesTypesNegativeTestXML(VolumeTypesNegativeTest): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/v2/0000775000175000017500000000000012332757136022170 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/v2/test_volumes_list.py0000664000175000017500000002262112332757070026326 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import operator from tempest.api.volume import base from tempest.common.utils import data_utils from tempest.openstack.common import log as logging from tempest import test from testtools import matchers LOG = logging.getLogger(__name__) VOLUME_FIELDS = ('id', 'name') class VolumesV2ListTestJSON(base.BaseVolumeV2Test): """ This test creates a number of 1G volumes. To run successfully, ensure that the backing file for the volume group that Nova uses has space for at least 3 1G volumes! If you are running a Devstack environment, ensure that the VOLUME_BACKING_FILE_SIZE is at least 4G in your localrc """ _interface = 'json' def assertVolumesIn(self, fetched_list, expected_list, fields=None): if fields: expected_list = map(operator.itemgetter(*fields), expected_list) fetched_list = map(operator.itemgetter(*fields), fetched_list) missing_vols = [v for v in expected_list if v not in fetched_list] if len(missing_vols) == 0: return def str_vol(vol): return "%s:%s" % (vol['id'], vol['name']) raw_msg = "Could not find volumes %s in expected list %s; fetched %s" self.fail(raw_msg % ([str_vol(v) for v in missing_vols], [str_vol(v) for v in expected_list], [str_vol(v) for v in fetched_list])) @classmethod @test.safe_setup def setUpClass(cls): super(VolumesV2ListTestJSON, cls).setUpClass() cls.client = cls.volumes_client # Create 3 test volumes cls.volume_list = [] cls.volume_id_list = [] cls.metadata = {'Type': 'work'} for i in range(3): volume = cls.create_volume(metadata=cls.metadata) resp, volume = cls.client.get_volume(volume['id']) cls.volume_list.append(volume) cls.volume_id_list.append(volume['id']) @classmethod def tearDownClass(cls): # Delete the created volumes for volid in cls.volume_id_list: resp, _ = cls.client.delete_volume(volid) cls.client.wait_for_resource_deletion(volid) super(VolumesV2ListTestJSON, cls).tearDownClass() def _list_by_param_value_and_assert(self, params, expected_list=None, with_detail=False): """ Perform list or list_details action with given params and validates result. """ if with_detail: resp, fetched_vol_list = \ self.client.list_volumes_with_detail(params=params) else: resp, fetched_vol_list = self.client.list_volumes(params=params) self.assertEqual(200, resp.status) if expected_list is None: expected_list = self.volume_list self.assertVolumesIn(fetched_vol_list, expected_list, fields=VOLUME_FIELDS) # Validating params of fetched volumes if with_detail: for volume in fetched_vol_list: for key in params: msg = "Failed to list volumes %s by %s" % \ ('details' if with_detail else '', key) if key == 'metadata': self.assertThat(volume[key].items(), matchers.ContainsAll( params[key].items()), msg) else: self.assertEqual(params[key], volume[key], msg) @test.attr(type='smoke') def test_volume_list(self): # Get a list of Volumes # Fetch all volumes resp, fetched_list = self.client.list_volumes() self.assertEqual(200, resp.status) self.assertVolumesIn(fetched_list, self.volume_list, fields=VOLUME_FIELDS) @test.attr(type='gate') def test_volume_list_with_details(self): # Get a list of Volumes with details # Fetch all Volumes resp, fetched_list = self.client.list_volumes_with_detail() self.assertEqual(200, resp.status) self.assertVolumesIn(fetched_list, self.volume_list) @test.attr(type='gate') def test_volume_list_by_name(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name']} resp, fetched_vol = self.client.list_volumes(params) self.assertEqual(200, resp.status) self.assertEqual(1, len(fetched_vol), str(fetched_vol)) self.assertEqual(fetched_vol[0]['name'], volume['name']) @test.attr(type='gate') def test_volume_list_details_by_name(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name']} resp, fetched_vol = self.client.list_volumes_with_detail(params) self.assertEqual(200, resp.status) self.assertEqual(1, len(fetched_vol), str(fetched_vol)) self.assertEqual(fetched_vol[0]['name'], volume['name']) @test.attr(type='gate') def test_volumes_list_by_status(self): params = {'status': 'available'} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_volumes_list_details_by_status(self): params = {'status': 'available'} self._list_by_param_value_and_assert(params, with_detail=True) @test.attr(type='gate') def test_volumes_list_by_availability_zone(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] zone = volume['availability_zone'] params = {'availability_zone': zone} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_volumes_list_details_by_availability_zone(self): volume = self.volume_list[data_utils.rand_int_id(0, 2)] zone = volume['availability_zone'] params = {'availability_zone': zone} self._list_by_param_value_and_assert(params, with_detail=True) @test.attr(type='gate') def test_volume_list_with_param_metadata(self): # Test to list volumes when metadata param is given params = {'metadata': self.metadata} self._list_by_param_value_and_assert(params) @test.attr(type='gate') def test_volume_list_with_details_param_metadata(self): # Test to list volumes details when metadata param is given params = {'metadata': self.metadata} self._list_by_param_value_and_assert(params, with_detail=True) @test.attr(type='gate') def test_volume_list_param_display_name_and_status(self): # Test to list volume when display name and status param is given volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name'], 'status': 'available'} self._list_by_param_value_and_assert(params, expected_list=[volume]) @test.attr(type='gate') def test_volume_list_with_details_param_display_name_and_status(self): # Test to list volume when name and status param is given volume = self.volume_list[data_utils.rand_int_id(0, 2)] params = {'name': volume['name'], 'status': 'available'} self._list_by_param_value_and_assert(params, expected_list=[volume], with_detail=True) @test.attr(type='gate') def test_volume_list_details_with_multiple_params(self): # List volumes detail using combined condition def _list_details_with_multiple_params(limit=2, status='available', sort_dir='asc', sort_key='created_at'): params = {'limit': limit, 'status': status, 'sort_dir': sort_dir, 'sort_key': sort_key } resp, fetched_volume = self.client.list_volumes_with_detail(params) self.assertEqual(200, resp.status) self.assertEqual(limit, len(fetched_volume), "The count of volumes is %s, expected:%s " % (len(fetched_volume), limit)) self.assertEqual(status, fetched_volume[0]['status']) self.assertEqual(status, fetched_volume[1]['status']) val0 = fetched_volume[0][sort_key] val1 = fetched_volume[1][sort_key] if sort_dir == 'asc': self.assertTrue(val0 < val1, "%s < %s" % (val0, val1)) elif sort_dir == 'desc': self.assertTrue(val0 > val1, "%s > %s" % (val0, val1)) _list_details_with_multiple_params() _list_details_with_multiple_params(sort_dir='desc') class VolumesV2ListTestXML(VolumesV2ListTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/v2/__init__.py0000664000175000017500000000000012332757070024264 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/test_volume_metadata.py0000664000175000017500000001121012332757070026411 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from testtools import matchers from tempest.api.volume import base from tempest import test class VolumeMetadataTest(base.BaseVolumeV1Test): _interface = "json" @classmethod @test.safe_setup def setUpClass(cls): super(VolumeMetadataTest, cls).setUpClass() # Create a volume cls.volume = cls.create_volume() cls.volume_id = cls.volume['id'] def tearDown(self): # Update the metadata to {} self.volumes_client.update_volume_metadata(self.volume_id, {}) super(VolumeMetadataTest, self).tearDown() @test.attr(type='gate') def test_create_get_delete_volume_metadata(self): # Create metadata for the volume metadata = {"key1": "value1", "key2": "value2", "key3": "value3", "key4": ""} rsp, body = self.volumes_client.create_volume_metadata(self.volume_id, metadata) self.assertEqual(200, rsp.status) # Get the metadata of the volume resp, body = self.volumes_client.get_volume_metadata(self.volume_id) self.assertEqual(200, resp.status) self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) # Delete one item metadata of the volume rsp, body = self.volumes_client.delete_volume_metadata_item( self.volume_id, "key1") self.assertEqual(200, rsp.status) resp, body = self.volumes_client.get_volume_metadata(self.volume_id) self.assertNotIn("key1", body) del metadata["key1"] self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) @test.attr(type='gate') def test_update_volume_metadata(self): # Update metadata for the volume metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} update = {"key4": "value4", "key1": "value1_update"} # Create metadata for the volume resp, body = self.volumes_client.create_volume_metadata( self.volume_id, metadata) self.assertEqual(200, resp.status) # Get the metadata of the volume resp, body = self.volumes_client.get_volume_metadata(self.volume_id) self.assertEqual(200, resp.status) self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) # Update metadata resp, body = self.volumes_client.update_volume_metadata( self.volume_id, update) self.assertEqual(200, resp.status) # Get the metadata of the volume resp, body = self.volumes_client.get_volume_metadata(self.volume_id) self.assertEqual(200, resp.status) self.assertThat(body.items(), matchers.ContainsAll(update.items())) @test.attr(type='gate') def test_update_volume_metadata_item(self): # Update metadata item for the volume metadata = {"key1": "value1", "key2": "value2", "key3": "value3"} update_item = {"key3": "value3_update"} expect = {"key1": "value1", "key2": "value2", "key3": "value3_update"} # Create metadata for the volume resp, body = self.volumes_client.create_volume_metadata( self.volume_id, metadata) self.assertEqual(200, resp.status) self.assertThat(body.items(), matchers.ContainsAll(metadata.items())) # Update metadata item resp, body = self.volumes_client.update_volume_metadata_item( self.volume_id, "key3", update_item) self.assertEqual(200, resp.status) # Get the metadata of the volume resp, body = self.volumes_client.get_volume_metadata(self.volume_id) self.assertEqual(200, resp.status) self.assertThat(body.items(), matchers.ContainsAll(expect.items())) class VolumeMetadataTestXML(VolumeMetadataTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/volume/base.py0000664000175000017500000001420612332757070023125 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseVolumeTest(tempest.test.BaseTestCase): """Base test case class for all Cinder API tests.""" @classmethod def setUpClass(cls): cls.set_network_resources() super(BaseVolumeTest, cls).setUpClass() if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.os = cls.get_client_manager() cls.servers_client = cls.os.servers_client cls.image_ref = CONF.compute.image_ref cls.flavor_ref = CONF.compute.flavor_ref cls.build_interval = CONF.volume.build_interval cls.build_timeout = CONF.volume.build_timeout cls.snapshots = [] cls.volumes = [] @classmethod def tearDownClass(cls): cls.clear_snapshots() cls.clear_volumes() cls.clear_isolated_creds() super(BaseVolumeTest, cls).tearDownClass() @classmethod def create_snapshot(cls, volume_id=1, **kwargs): """Wrapper utility that returns a test snapshot.""" resp, snapshot = cls.snapshots_client.create_snapshot(volume_id, **kwargs) assert 200 == resp.status cls.snapshots.append(snapshot) cls.snapshots_client.wait_for_snapshot_status(snapshot['id'], 'available') return snapshot # NOTE(afazekas): these create_* and clean_* could be defined # only in a single location in the source, and could be more general. @classmethod def clear_volumes(cls): for volume in cls.volumes: try: cls.volumes_client.delete_volume(volume['id']) except Exception: pass for volume in cls.volumes: try: cls.volumes_client.wait_for_resource_deletion(volume['id']) except Exception: pass @classmethod def clear_snapshots(cls): for snapshot in cls.snapshots: try: cls.snapshots_client.delete_snapshot(snapshot['id']) except Exception: pass for snapshot in cls.snapshots: try: cls.snapshots_client.wait_for_resource_deletion(snapshot['id']) except Exception: pass class BaseVolumeV1Test(BaseVolumeTest): @classmethod def setUpClass(cls): if not CONF.volume_feature_enabled.api_v1: msg = "Volume API v1 not supported" raise cls.skipException(msg) super(BaseVolumeV1Test, cls).setUpClass() cls.snapshots_client = cls.os.snapshots_client cls.volumes_client = cls.os.volumes_client cls.backups_client = cls.os.backups_client cls.volume_services_client = cls.os.volume_services_client cls.volumes_extension_client = cls.os.volumes_extension_client @classmethod def create_volume(cls, size=1, **kwargs): """Wrapper utility that returns a test volume.""" vol_name = data_utils.rand_name('Volume') resp, volume = cls.volumes_client.create_volume(size, display_name=vol_name, **kwargs) assert 200 == resp.status cls.volumes.append(volume) cls.volumes_client.wait_for_volume_status(volume['id'], 'available') return volume class BaseVolumeV1AdminTest(BaseVolumeV1Test): """Base test case class for all Volume Admin API tests.""" @classmethod def setUpClass(cls): super(BaseVolumeV1AdminTest, cls).setUpClass() cls.adm_user = CONF.identity.admin_username cls.adm_pass = CONF.identity.admin_password cls.adm_tenant = CONF.identity.admin_tenant_name if not all((cls.adm_user, cls.adm_pass, cls.adm_tenant)): msg = ("Missing Volume Admin API credentials " "in configuration.") raise cls.skipException(msg) if CONF.compute.allow_tenant_isolation: cls.os_adm = clients.Manager(cls.isolated_creds.get_admin_creds(), interface=cls._interface) else: cls.os_adm = clients.AdminManager(interface=cls._interface) cls.client = cls.os_adm.volume_types_client cls.hosts_client = cls.os_adm.volume_hosts_client cls.quotas_client = cls.os_adm.volume_quotas_client class BaseVolumeV2Test(BaseVolumeTest): @classmethod def setUpClass(cls): if not CONF.volume_feature_enabled.api_v2: msg = "Volume API v2 not supported" raise cls.skipException(msg) super(BaseVolumeV2Test, cls).setUpClass() cls.volumes_client = cls.os.volumes_v2_client @classmethod def create_volume(cls, size=1, **kwargs): """Wrapper utility that returns a test volume.""" vol_name = data_utils.rand_name('Volume') resp, volume = cls.volumes_client.create_volume(size, name=vol_name, **kwargs) assert 202 == resp.status cls.volumes.append(volume) cls.volumes_client.wait_for_volume_status(volume['id'], 'available') return volume tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/0000775000175000017500000000000012332757136022163 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/__init__.py0000664000175000017500000000152512332757070024274 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) # All identity tests -- single setup function def setup_package(): LOG.debug("Entering tempest.api.identity.setup_package") tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/0000775000175000017500000000000012332757136023253 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/0000775000175000017500000000000012332757136023603 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_projects.py0000664000175000017500000001726312332757070027053 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack, LLC # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from six import moves from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ProjectsTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' def _delete_project(self, project_id): resp, _ = self.client.delete_project(project_id) self.assertEqual(resp['status'], '204') self.assertRaises( exceptions.NotFound, self.client.get_project, project_id) @test.attr(type='gate') def test_project_list_delete(self): # Create several projects and delete them for _ in moves.xrange(3): resp, project = self.client.create_project( data_utils.rand_name('project-new')) self.addCleanup(self._delete_project, project['id']) resp, list_projects = self.client.list_projects() self.assertEqual(resp['status'], '200') resp, get_project = self.client.get_project(project['id']) self.assertIn(get_project, list_projects) @test.attr(type='gate') def test_project_create_with_description(self): # Create project with a description project_name = data_utils.rand_name('project-') project_desc = data_utils.rand_name('desc-') resp, project = self.client.create_project( project_name, description=project_desc) self.data.projects.append(project) st1 = resp['status'] project_id = project['id'] desc1 = project['description'] self.assertEqual(st1, '201') self.assertEqual(desc1, project_desc, 'Description should have ' 'been sent in response for create') resp, body = self.client.get_project(project_id) desc2 = body['description'] self.assertEqual(desc2, project_desc, 'Description does not appear' 'to be set') @test.attr(type='gate') def test_project_create_enabled(self): # Create a project that is enabled project_name = data_utils.rand_name('project-') resp, project = self.client.create_project( project_name, enabled=True) self.data.projects.append(project) project_id = project['id'] st1 = resp['status'] en1 = project['enabled'] self.assertEqual(st1, '201') self.assertTrue(en1, 'Enable should be True in response') resp, body = self.client.get_project(project_id) en2 = body['enabled'] self.assertTrue(en2, 'Enable should be True in lookup') @test.attr(type='gate') def test_project_create_not_enabled(self): # Create a project that is not enabled project_name = data_utils.rand_name('project-') resp, project = self.client.create_project( project_name, enabled=False) self.data.projects.append(project) st1 = resp['status'] en1 = project['enabled'] self.assertEqual(st1, '201') self.assertEqual('false', str(en1).lower(), 'Enable should be False in response') resp, body = self.client.get_project(project['id']) en2 = body['enabled'] self.assertEqual('false', str(en2).lower(), 'Enable should be False in lookup') @test.attr(type='gate') def test_project_update_name(self): # Update name attribute of a project p_name1 = data_utils.rand_name('project-') resp, project = self.client.create_project(p_name1) self.data.projects.append(project) resp1_name = project['name'] p_name2 = data_utils.rand_name('project2-') resp, body = self.client.update_project(project['id'], name=p_name2) st2 = resp['status'] resp2_name = body['name'] self.assertEqual(st2, '200') self.assertNotEqual(resp1_name, resp2_name) resp, body = self.client.get_project(project['id']) resp3_name = body['name'] self.assertNotEqual(resp1_name, resp3_name) self.assertEqual(p_name1, resp1_name) self.assertEqual(resp2_name, resp3_name) @test.attr(type='gate') def test_project_update_desc(self): # Update description attribute of a project p_name = data_utils.rand_name('project-') p_desc = data_utils.rand_name('desc-') resp, project = self.client.create_project( p_name, description=p_desc) self.data.projects.append(project) resp1_desc = project['description'] p_desc2 = data_utils.rand_name('desc2-') resp, body = self.client.update_project( project['id'], description=p_desc2) st2 = resp['status'] resp2_desc = body['description'] self.assertEqual(st2, '200') self.assertNotEqual(resp1_desc, resp2_desc) resp, body = self.client.get_project(project['id']) resp3_desc = body['description'] self.assertNotEqual(resp1_desc, resp3_desc) self.assertEqual(p_desc, resp1_desc) self.assertEqual(resp2_desc, resp3_desc) @test.attr(type='gate') def test_project_update_enable(self): # Update the enabled attribute of a project p_name = data_utils.rand_name('project-') p_en = False resp, project = self.client.create_project(p_name, enabled=p_en) self.data.projects.append(project) resp1_en = project['enabled'] p_en2 = True resp, body = self.client.update_project( project['id'], enabled=p_en2) st2 = resp['status'] resp2_en = body['enabled'] self.assertEqual(st2, '200') self.assertNotEqual(resp1_en, resp2_en) resp, body = self.client.get_project(project['id']) resp3_en = body['enabled'] self.assertNotEqual(resp1_en, resp3_en) self.assertEqual('false', str(resp1_en).lower()) self.assertEqual(resp2_en, resp3_en) @test.attr(type='gate') def test_associate_user_to_project(self): #Associate a user to a project #Create a Project p_name = data_utils.rand_name('project-') resp, project = self.client.create_project(p_name) self.data.projects.append(project) #Create a User u_name = data_utils.rand_name('user-') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = data_utils.rand_name('pass-') resp, user = self.client.create_user( u_name, description=u_desc, password=u_password, email=u_email, project_id=project['id']) self.assertEqual(resp['status'], '201') # Delete the User at the end of this method self.addCleanup(self.client.delete_user, user['id']) # Get User To validate the user details resp, new_user_get = self.client.get_user(user['id']) #Assert response body of GET self.assertEqual(u_name, new_user_get['name']) self.assertEqual(u_desc, new_user_get['description']) self.assertEqual(project['id'], new_user_get['project_id']) self.assertEqual(u_email, new_user_get['email']) class ProjectsTestXML(ProjectsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_endpoints_negative.py0000664000175000017500000000724512332757070031106 0ustar chuckchuck00000000000000 # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class EndpointsNegativeTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(EndpointsNegativeTestJSON, cls).setUpClass() cls.identity_client = cls.client cls.client = cls.endpoints_client cls.service_ids = list() s_name = data_utils.rand_name('service-') s_type = data_utils.rand_name('type--') s_description = data_utils.rand_name('description-') resp, cls.service_data = ( cls.service_client.create_service(s_name, s_type, description=s_description)) cls.service_id = cls.service_data['id'] cls.service_ids.append(cls.service_id) @classmethod def tearDownClass(cls): for s in cls.service_ids: cls.service_client.delete_service(s) super(EndpointsNegativeTestJSON, cls).tearDownClass() @attr(type=['negative', 'gate']) def test_create_with_enabled_False(self): # Enabled should be a boolean, not a string like 'False' interface = 'public' url = data_utils.rand_name('url') region = data_utils.rand_name('region') self.assertRaises(exceptions.BadRequest, self.client.create_endpoint, self.service_id, interface, url, region=region, force_enabled='False') @attr(type=['negative', 'gate']) def test_create_with_enabled_True(self): # Enabled should be a boolean, not a string like 'True' interface = 'public' url = data_utils.rand_name('url') region = data_utils.rand_name('region') self.assertRaises(exceptions.BadRequest, self.client.create_endpoint, self.service_id, interface, url, region=region, force_enabled='True') def _assert_update_raises_bad_request(self, enabled): # Create an endpoint region1 = data_utils.rand_name('region') url1 = data_utils.rand_name('url') interface1 = 'public' resp, endpoint_for_update = ( self.client.create_endpoint(self.service_id, interface1, url1, region=region1, enabled=True)) self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id']) self.assertRaises(exceptions.BadRequest, self.client.update_endpoint, endpoint_for_update['id'], force_enabled=enabled) @attr(type=['negative', 'gate']) def test_update_with_enabled_False(self): # Enabled should be a boolean, not a string like 'False' self._assert_update_raises_bad_request('False') @attr(type=['negative', 'gate']) def test_update_with_enabled_True(self): # Enabled should be a boolean, not a string like 'True' self._assert_update_raises_bad_request('True') class EndpointsNegativeTestXML(EndpointsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_roles.py0000664000175000017500000001772112332757070026345 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import test class RolesV3TestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(RolesV3TestJSON, cls).setUpClass() cls.fetched_role_ids = list() u_name = data_utils.rand_name('user-') u_desc = '%s description' % u_name u_email = '%s@testmail.tm' % u_name cls.u_password = data_utils.rand_name('pass-') resp = [None] * 5 resp[0], cls.domain = cls.client.create_domain( data_utils.rand_name('domain-'), description=data_utils.rand_name('domain-desc-')) resp[1], cls.project = cls.client.create_project( data_utils.rand_name('project-'), description=data_utils.rand_name('project-desc-'), domain_id=cls.domain['id']) resp[2], cls.group_body = cls.client.create_group( data_utils.rand_name('Group-'), project_id=cls.project['id'], domain_id=cls.domain['id']) resp[3], cls.user_body = cls.client.create_user( u_name, description=u_desc, password=cls.u_password, email=u_email, project_id=cls.project['id'], domain_id=cls.domain['id']) resp[4], cls.role = cls.client.create_role( data_utils.rand_name('Role-')) for r in resp: assert r['status'] == '201', ( "Expected 201, but got: %s" % r['status']) @classmethod def tearDownClass(cls): resp = [None] * 5 resp[0], _ = cls.client.delete_role(cls.role['id']) resp[1], _ = cls.client.delete_group(cls.group_body['id']) resp[2], _ = cls.client.delete_user(cls.user_body['id']) resp[3], _ = cls.client.delete_project(cls.project['id']) # NOTE(harika-vakadi): It is necessary to disable the domain # before deleting,or else it would result in unauthorized error cls.client.update_domain(cls.domain['id'], enabled=False) resp[4], _ = cls.client.delete_domain(cls.domain['id']) for r in resp: assert r['status'] == '204', ( "Expected 204, but got: %s" % r['status']) super(RolesV3TestJSON, cls).tearDownClass() def _list_assertions(self, resp, body, fetched_role_ids, role_id): self.assertEqual(resp['status'], '200') self.assertEqual(len(body), 1) self.assertIn(role_id, fetched_role_ids) @test.attr(type='smoke') def test_role_create_update_get_list(self): r_name = data_utils.rand_name('Role-') resp, role = self.client.create_role(r_name) self.addCleanup(self.client.delete_role, role['id']) self.assertEqual(resp['status'], '201') self.assertIn('name', role) self.assertEqual(role['name'], r_name) new_name = data_utils.rand_name('NewRole-') resp, updated_role = self.client.update_role(new_name, role['id']) self.assertEqual(resp['status'], '200') self.assertIn('name', updated_role) self.assertIn('id', updated_role) self.assertIn('links', updated_role) self.assertNotEqual(r_name, updated_role['name']) resp, new_role = self.client.get_role(role['id']) self.assertEqual(resp['status'], '200') self.assertEqual(new_name, new_role['name']) self.assertEqual(updated_role['id'], new_role['id']) resp, roles = self.client.list_roles() self.assertEqual(resp['status'], '200') self.assertIn(role['id'], [r['id'] for r in roles]) @test.attr(type='smoke') def test_grant_list_revoke_role_to_user_on_project(self): resp, _ = self.client.assign_user_role_on_project( self.project['id'], self.user_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') resp, roles = self.client.list_user_roles_on_project( self.project['id'], self.user_body['id']) for i in roles: self.fetched_role_ids.append(i['id']) self._list_assertions(resp, roles, self.fetched_role_ids, self.role['id']) resp, _ = self.client.revoke_role_from_user_on_project( self.project['id'], self.user_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') @test.attr(type='smoke') def test_grant_list_revoke_role_to_user_on_domain(self): resp, _ = self.client.assign_user_role_on_domain( self.domain['id'], self.user_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') resp, roles = self.client.list_user_roles_on_domain( self.domain['id'], self.user_body['id']) for i in roles: self.fetched_role_ids.append(i['id']) self._list_assertions(resp, roles, self.fetched_role_ids, self.role['id']) resp, _ = self.client.revoke_role_from_user_on_domain( self.domain['id'], self.user_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') @test.attr(type='smoke') def test_grant_list_revoke_role_to_group_on_project(self): # Grant role to group on project resp, _ = self.client.assign_group_role_on_project( self.project['id'], self.group_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') # List group roles on project resp, roles = self.client.list_group_roles_on_project( self.project['id'], self.group_body['id']) for i in roles: self.fetched_role_ids.append(i['id']) self._list_assertions(resp, roles, self.fetched_role_ids, self.role['id']) # Add user to group, and insure user has role on project self.client.add_group_user(self.group_body['id'], self.user_body['id']) self.addCleanup(self.client.delete_group_user, self.group_body['id'], self.user_body['id']) resp, body = self.token.auth(self.user_body['id'], self.u_password, self.project['name'], domain=self.domain['name']) roles = body['token']['roles'] self.assertEqual(resp['status'], '201') self.assertEqual(len(roles), 1) self.assertEqual(roles[0]['id'], self.role['id']) # Revoke role to group on project resp, _ = self.client.revoke_role_from_group_on_project( self.project['id'], self.group_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') @test.attr(type='smoke') def test_grant_list_revoke_role_to_group_on_domain(self): resp, _ = self.client.assign_group_role_on_domain( self.domain['id'], self.group_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') resp, roles = self.client.list_group_roles_on_domain( self.domain['id'], self.group_body['id']) for i in roles: self.fetched_role_ids.append(i['id']) self._list_assertions(resp, roles, self.fetched_role_ids, self.role['id']) resp, _ = self.client.revoke_role_from_group_on_domain( self.domain['id'], self.group_body['id'], self.role['id']) self.assertEqual(resp['status'], '204') class RolesV3TestXML(RolesV3TestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_projects_negative.py0000664000175000017500000000630712332757070030732 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack, LLC # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ProjectsNegativeTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @test.attr(type=['negative', 'gate']) def test_list_projects_by_unauthorized_user(self): # Non-admin user should not be able to list projects self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_projects) @test.attr(type=['negative', 'gate']) def test_project_create_duplicate(self): # Project names should be unique project_name = data_utils.rand_name('project-dup-') resp, project = self.client.create_project(project_name) self.data.projects.append(project) self.assertRaises( exceptions.Conflict, self.client.create_project, project_name) @test.attr(type=['negative', 'gate']) def test_create_project_by_unauthorized_user(self): # Non-admin user should not be authorized to create a project project_name = data_utils.rand_name('project-') self.assertRaises( exceptions.Unauthorized, self.non_admin_client.create_project, project_name) @test.attr(type=['negative', 'gate']) def test_create_project_with_empty_name(self): # Project name should not be empty self.assertRaises(exceptions.BadRequest, self.client.create_project, name='') @test.attr(type=['negative', 'gate']) def test_create_projects_name_length_over_64(self): # Project name length should not be greater than 64 characters project_name = 'a' * 65 self.assertRaises(exceptions.BadRequest, self.client.create_project, project_name) @test.attr(type=['negative', 'gate']) def test_project_delete_by_unauthorized_user(self): # Non-admin user should not be able to delete a project project_name = data_utils.rand_name('project-') resp, project = self.client.create_project(project_name) self.data.projects.append(project) self.assertRaises( exceptions.Unauthorized, self.non_admin_client.delete_project, project['id']) @test.attr(type=['negative', 'gate']) def test_delete_non_existent_project(self): # Attempt to delete a non existent project should fail self.assertRaises(exceptions.NotFound, self.client.delete_project, data_utils.rand_uuid_hex()) class ProjectsNegativeTestXML(ProjectsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_endpoints.py0000664000175000017500000001363212332757070027221 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import test class EndPointsTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(EndPointsTestJSON, cls).setUpClass() cls.identity_client = cls.client cls.client = cls.endpoints_client cls.service_ids = list() s_name = data_utils.rand_name('service-') s_type = data_utils.rand_name('type--') s_description = data_utils.rand_name('description-') resp, cls.service_data =\ cls.service_client.create_service(s_name, s_type, description=s_description) cls.service_id = cls.service_data['id'] cls.service_ids.append(cls.service_id) # Create endpoints so as to use for LIST and GET test cases cls.setup_endpoints = list() for i in range(2): region = data_utils.rand_name('region') url = data_utils.rand_name('url') interface = 'public' resp, endpoint = cls.client.create_endpoint( cls.service_id, interface, url, region=region, enabled=True) cls.setup_endpoints.append(endpoint) @classmethod def tearDownClass(cls): for e in cls.setup_endpoints: cls.client.delete_endpoint(e['id']) for s in cls.service_ids: cls.service_client.delete_service(s) super(EndPointsTestJSON, cls).tearDownClass() @test.attr(type='gate') def test_list_endpoints(self): # Get a list of endpoints resp, fetched_endpoints = self.client.list_endpoints() # Asserting LIST endpoints self.assertEqual(resp['status'], '200') missing_endpoints =\ [e for e in self.setup_endpoints if e not in fetched_endpoints] self.assertEqual(0, len(missing_endpoints), "Failed to find endpoint %s in fetched list" % ', '.join(str(e) for e in missing_endpoints)) @test.attr(type='gate') def test_create_list_delete_endpoint(self): region = data_utils.rand_name('region') url = data_utils.rand_name('url') interface = 'public' resp, endpoint =\ self.client.create_endpoint(self.service_id, interface, url, region=region, enabled=True) # Asserting Create Endpoint response body self.assertEqual(resp['status'], '201') self.assertIn('id', endpoint) self.assertEqual(region, endpoint['region']) self.assertEqual(url, endpoint['url']) # Checking if created endpoint is present in the list of endpoints resp, fetched_endpoints = self.client.list_endpoints() fetched_endpoints_id = [e['id'] for e in fetched_endpoints] self.assertIn(endpoint['id'], fetched_endpoints_id) # Deleting the endpoint created in this method resp, body = self.client.delete_endpoint(endpoint['id']) self.assertEqual(resp['status'], '204') self.assertEqual(body, '') # Checking whether endpoint is deleted successfully resp, fetched_endpoints = self.client.list_endpoints() fetched_endpoints_id = [e['id'] for e in fetched_endpoints] self.assertNotIn(endpoint['id'], fetched_endpoints_id) @test.attr(type='smoke') def test_update_endpoint(self): # Creating an endpoint so as to check update endpoint # with new values region1 = data_utils.rand_name('region') url1 = data_utils.rand_name('url') interface1 = 'public' resp, endpoint_for_update =\ self.client.create_endpoint(self.service_id, interface1, url1, region=region1, enabled=True) self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id']) # Creating service so as update endpoint with new service ID s_name = data_utils.rand_name('service-') s_type = data_utils.rand_name('type--') s_description = data_utils.rand_name('description-') resp, self.service2 =\ self.service_client.create_service(s_name, s_type, description=s_description) self.service_ids.append(self.service2['id']) # Updating endpoint with new values region2 = data_utils.rand_name('region') url2 = data_utils.rand_name('url') interface2 = 'internal' resp, endpoint = \ self.client.update_endpoint(endpoint_for_update['id'], service_id=self.service2['id'], interface=interface2, url=url2, region=region2, enabled=False) self.assertEqual(resp['status'], '200') # Asserting if the attributes of endpoint are updated self.assertEqual(self.service2['id'], endpoint['service_id']) self.assertEqual(interface2, endpoint['interface']) self.assertEqual(url2, endpoint['url']) self.assertEqual(region2, endpoint['region']) self.assertEqual('false', str(endpoint['enabled']).lower()) class EndPointsTestXML(EndPointsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_policies.py0000664000175000017500000000644012332757070027024 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest.test import attr class PoliciesTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' def _delete_policy(self, policy_id): resp, _ = self.policy_client.delete_policy(policy_id) self.assertEqual(204, resp.status) @attr(type='smoke') def test_list_policies(self): # Test to list policies policy_ids = list() fetched_ids = list() for _ in range(3): blob = data_utils.rand_name('BlobName-') policy_type = data_utils.rand_name('PolicyType-') resp, policy = self.policy_client.create_policy(blob, policy_type) # Delete the Policy at the end of this method self.addCleanup(self._delete_policy, policy['id']) policy_ids.append(policy['id']) # List and Verify Policies resp, body = self.policy_client.list_policies() self.assertEqual(resp['status'], '200') for p in body: fetched_ids.append(p['id']) missing_pols = [p for p in policy_ids if p not in fetched_ids] self.assertEqual(0, len(missing_pols)) @attr(type='smoke') def test_create_update_delete_policy(self): # Test to update policy blob = data_utils.rand_name('BlobName-') policy_type = data_utils.rand_name('PolicyType-') resp, policy = self.policy_client.create_policy(blob, policy_type) self.addCleanup(self._delete_policy, policy['id']) self.assertIn('id', policy) self.assertIn('type', policy) self.assertIn('blob', policy) self.assertIsNotNone(policy['id']) self.assertEqual(blob, policy['blob']) self.assertEqual(policy_type, policy['type']) resp, fetched_policy = self.policy_client.get_policy(policy['id']) self.assertEqual(resp['status'], '200') # Update policy update_type = data_utils.rand_name('UpdatedPolicyType-') resp, data = self.policy_client.update_policy( policy['id'], type=update_type) self.assertIn('type', data) # Assertion for updated value with fetched value resp, fetched_policy = self.policy_client.get_policy(policy['id']) self.assertIn('id', fetched_policy) self.assertIn('blob', fetched_policy) self.assertIn('type', fetched_policy) self.assertEqual(fetched_policy['id'], policy['id']) self.assertEqual(fetched_policy['blob'], policy['blob']) self.assertEqual(update_type, fetched_policy['type']) class PoliciesTestXML(PoliciesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_users.py0000664000175000017500000001361212332757070026355 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest.test import attr class UsersV3TestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @attr(type='gate') def test_user_update(self): # Test case to check if updating of user attributes is successful. # Creating first user u_name = data_utils.rand_name('user-') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = data_utils.rand_name('pass-') resp, user = self.client.create_user( u_name, description=u_desc, password=u_password, email=u_email, enabled=False) # Delete the User at the end of this method self.addCleanup(self.client.delete_user, user['id']) # Creating second project for updation resp, project = self.client.create_project( data_utils.rand_name('project-'), description=data_utils.rand_name('project-desc-')) # Delete the Project at the end of this method self.addCleanup(self.client.delete_project, project['id']) # Updating user details with new values u_name2 = data_utils.rand_name('user2-') u_email2 = u_name2 + '@testmail.tm' u_description2 = u_name2 + ' description' resp, update_user = self.client.update_user( user['id'], name=u_name2, description=u_description2, project_id=project['id'], email=u_email2, enabled=False) # Assert response body of update user. self.assertEqual(200, resp.status) self.assertEqual(u_name2, update_user['name']) self.assertEqual(u_description2, update_user['description']) self.assertEqual(project['id'], update_user['project_id']) self.assertEqual(u_email2, update_user['email']) self.assertEqual('false', str(update_user['enabled']).lower()) # GET by id after updation resp, new_user_get = self.client.get_user(user['id']) # Assert response body of GET after updation self.assertEqual(u_name2, new_user_get['name']) self.assertEqual(u_description2, new_user_get['description']) self.assertEqual(project['id'], new_user_get['project_id']) self.assertEqual(u_email2, new_user_get['email']) self.assertEqual('false', str(new_user_get['enabled']).lower()) @attr(type='gate') def test_list_user_projects(self): # List the projects that a user has access upon assigned_project_ids = list() fetched_project_ids = list() _, u_project = self.client.create_project( data_utils.rand_name('project-'), description=data_utils.rand_name('project-desc-')) # Delete the Project at the end of this method self.addCleanup(self.client.delete_project, u_project['id']) # Create a user. u_name = data_utils.rand_name('user-') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = data_utils.rand_name('pass-') _, user_body = self.client.create_user( u_name, description=u_desc, password=u_password, email=u_email, enabled=False, project_id=u_project['id']) # Delete the User at the end of this method self.addCleanup(self.client.delete_user, user_body['id']) # Creating Role _, role_body = self.client.create_role( data_utils.rand_name('role-')) # Delete the Role at the end of this method self.addCleanup(self.client.delete_role, role_body['id']) _, user = self.client.get_user(user_body['id']) _, role = self.client.get_role(role_body['id']) for i in range(2): # Creating project so as to assign role _, project_body = self.client.create_project( data_utils.rand_name('project-'), description=data_utils.rand_name('project-desc-')) _, project = self.client.get_project(project_body['id']) # Delete the Project at the end of this method self.addCleanup(self.client.delete_project, project_body['id']) # Assigning roles to user on project self.client.assign_user_role(project['id'], user['id'], role['id']) assigned_project_ids.append(project['id']) resp, body = self.client.list_user_projects(user['id']) self.assertEqual(200, resp.status) for i in body: fetched_project_ids.append(i['id']) # verifying the project ids in list missing_projects =\ [p for p in assigned_project_ids if p not in fetched_project_ids] self.assertEqual(0, len(missing_projects), "Failed to find project %s in fetched list" % ', '.join(m_project for m_project in missing_projects)) @attr(type='gate') def test_get_user(self): # Get a user detail self.data.setup_test_v3_user() resp, user = self.client.get_user(self.data.v3_user['id']) self.assertEqual(self.data.v3_user['id'], user['id']) class UsersV3TestXML(UsersV3TestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/__init__.py0000664000175000017500000000000012332757070025677 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_domains.py0000664000175000017500000000767012332757070026655 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest.test import attr class DomainsTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' def _delete_domain(self, domain_id): # It is necessary to disable the domain before deleting, # or else it would result in unauthorized error _, body = self.client.update_domain(domain_id, enabled=False) resp, _ = self.client.delete_domain(domain_id) self.assertEqual(204, resp.status) @attr(type='smoke') def test_list_domains(self): # Test to list domains domain_ids = list() fetched_ids = list() for _ in range(3): _, domain = self.client.create_domain( data_utils.rand_name('domain-'), description=data_utils.rand_name('domain-desc-')) # Delete the domain at the end of this method self.addCleanup(self._delete_domain, domain['id']) domain_ids.append(domain['id']) # List and Verify Domains resp, body = self.client.list_domains() self.assertEqual(resp['status'], '200') for d in body: fetched_ids.append(d['id']) missing_doms = [d for d in domain_ids if d not in fetched_ids] self.assertEqual(0, len(missing_doms)) @attr(type='smoke') def test_create_update_delete_domain(self): d_name = data_utils.rand_name('domain-') d_desc = data_utils.rand_name('domain-desc-') resp_1, domain = self.client.create_domain( d_name, description=d_desc) self.assertEqual(resp_1['status'], '201') self.addCleanup(self._delete_domain, domain['id']) self.assertIn('id', domain) self.assertIn('description', domain) self.assertIn('name', domain) self.assertIn('enabled', domain) self.assertIn('links', domain) self.assertIsNotNone(domain['id']) self.assertEqual(d_name, domain['name']) self.assertEqual(d_desc, domain['description']) if self._interface == "json": self.assertEqual(True, domain['enabled']) else: self.assertEqual('true', str(domain['enabled']).lower()) new_desc = data_utils.rand_name('new-desc-') new_name = data_utils.rand_name('new-name-') resp_2, updated_domain = self.client.update_domain( domain['id'], name=new_name, description=new_desc) self.assertEqual(resp_2['status'], '200') self.assertIn('id', updated_domain) self.assertIn('description', updated_domain) self.assertIn('name', updated_domain) self.assertIn('enabled', updated_domain) self.assertIn('links', updated_domain) self.assertIsNotNone(updated_domain['id']) self.assertEqual(new_name, updated_domain['name']) self.assertEqual(new_desc, updated_domain['description']) self.assertEqual('true', str(updated_domain['enabled']).lower()) resp_3, fetched_domain = self.client.get_domain(domain['id']) self.assertEqual(resp_3['status'], '200') self.assertEqual(new_name, fetched_domain['name']) self.assertEqual(new_desc, fetched_domain['description']) self.assertEqual('true', str(fetched_domain['enabled']).lower()) class DomainsTestXML(DomainsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_credentials.py0000664000175000017500000001226412332757070027513 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import test class CredentialsTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(CredentialsTestJSON, cls).setUpClass() cls.projects = list() cls.creds_list = [['project_id', 'user_id', 'id'], ['access', 'secret']] u_name = data_utils.rand_name('user-') u_desc = '%s description' % u_name u_email = '%s@testmail.tm' % u_name u_password = data_utils.rand_name('pass-') for i in range(2): resp, cls.project = cls.client.create_project( data_utils.rand_name('project-'), description=data_utils.rand_name('project-desc-')) assert resp['status'] == '201', ( "Expected 201, but got: %s" % resp['status']) cls.projects.append(cls.project['id']) resp, cls.user_body = cls.client.create_user( u_name, description=u_desc, password=u_password, email=u_email, project_id=cls.projects[0]) assert resp['status'] == '201', ( "Expected 201, but got: %s" % resp['status']) @classmethod def tearDownClass(cls): resp, _ = cls.client.delete_user(cls.user_body['id']) assert resp['status'] == '204', ( "Expected 204, but got: %s" % resp['status']) for p in cls.projects: resp, _ = cls.client.delete_project(p) assert resp['status'] == '204', ( "Expected 204, but got: %s" % resp['status']) super(CredentialsTestJSON, cls).tearDownClass() def _delete_credential(self, cred_id): resp, body = self.creds_client.delete_credential(cred_id) self.assertEqual(resp['status'], '204') @test.attr(type='smoke') def test_credentials_create_get_update_delete(self): keys = [data_utils.rand_name('Access-'), data_utils.rand_name('Secret-')] resp, cred = self.creds_client.create_credential( keys[0], keys[1], self.user_body['id'], self.projects[0]) self.addCleanup(self._delete_credential, cred['id']) self.assertEqual(resp['status'], '201') for value1 in self.creds_list[0]: self.assertIn(value1, cred) for value2 in self.creds_list[1]: self.assertIn(value2, cred['blob']) new_keys = [data_utils.rand_name('NewAccess-'), data_utils.rand_name('NewSecret-')] resp, update_body = self.creds_client.update_credential( cred['id'], access_key=new_keys[0], secret_key=new_keys[1], project_id=self.projects[1]) self.assertEqual(resp['status'], '200') self.assertEqual(cred['id'], update_body['id']) self.assertEqual(self.projects[1], update_body['project_id']) self.assertEqual(self.user_body['id'], update_body['user_id']) self.assertEqual(update_body['blob']['access'], new_keys[0]) self.assertEqual(update_body['blob']['secret'], new_keys[1]) resp, get_body = self.creds_client.get_credential(cred['id']) self.assertEqual(resp['status'], '200') for value1 in self.creds_list[0]: self.assertEqual(update_body[value1], get_body[value1]) for value2 in self.creds_list[1]: self.assertEqual(update_body['blob'][value2], get_body['blob'][value2]) @test.attr(type='smoke') def test_credentials_list_delete(self): created_cred_ids = list() fetched_cred_ids = list() for i in range(2): resp, cred = self.creds_client.create_credential( data_utils.rand_name('Access-'), data_utils.rand_name('Secret-'), self.user_body['id'], self.projects[0]) self.assertEqual(resp['status'], '201') created_cred_ids.append(cred['id']) self.addCleanup(self._delete_credential, cred['id']) resp, creds = self.creds_client.list_credentials() self.assertEqual(resp['status'], '200') for i in creds: fetched_cred_ids.append(i['id']) missing_creds = [c for c in created_cred_ids if c not in fetched_cred_ids] self.assertEqual(0, len(missing_creds), "Failed to find cred %s in fetched list" % ', '.join(m_cred for m_cred in missing_creds)) class CredentialsTestXML(CredentialsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_trusts.py0000664000175000017500000002502612332757070026562 0ustar chuckchuck00000000000000# 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 datetime import re from tempest.api.identity import base from tempest import auth from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import timeutils from tempest import test CONF = config.CONF class BaseTrustsV3Test(base.BaseIdentityV3AdminTest): def setUp(self): super(BaseTrustsV3Test, self).setUp() # Use alt_username as the trustee if not CONF.identity_feature_enabled.trust: raise self.skipException("Trusts aren't enabled") self.trustee_username = CONF.identity.alt_username self.trust_id = None def tearDown(self): if self.trust_id: # Do the delete in tearDown not addCleanup - we want the test to # fail in the event there is a bug which causes undeletable trusts self.delete_trust() super(BaseTrustsV3Test, self).tearDown() def create_trustor_and_roles(self): # Get trustor project ID, use the admin project self.trustor_project_name = self.client.tenant_name self.trustor_project_id = self.get_tenant_by_name( self.trustor_project_name)['id'] self.assertIsNotNone(self.trustor_project_id) # Create a trustor User self.trustor_username = data_utils.rand_name('user-') u_desc = self.trustor_username + 'description' u_email = self.trustor_username + '@testmail.xx' self.trustor_password = data_utils.rand_name('pass-') resp, user = self.client.create_user( self.trustor_username, description=u_desc, password=self.trustor_password, email=u_email, project_id=self.trustor_project_id) self.assertEqual(resp['status'], '201') self.trustor_user_id = user['id'] # And two roles, one we'll delegate and one we won't self.delegated_role = data_utils.rand_name('DelegatedRole-') self.not_delegated_role = data_utils.rand_name('NotDelegatedRole-') resp, role = self.client.create_role(self.delegated_role) self.assertEqual(resp['status'], '201') self.delegated_role_id = role['id'] resp, role = self.client.create_role(self.not_delegated_role) self.assertEqual(resp['status'], '201') self.not_delegated_role_id = role['id'] # Assign roles to trustor self.client.assign_user_role(self.trustor_project_id, self.trustor_user_id, self.delegated_role_id) self.client.assign_user_role(self.trustor_project_id, self.trustor_user_id, self.not_delegated_role_id) # Get trustee user ID, use the demo user trustee_username = self.non_admin_client.user self.trustee_user_id = self.get_user_by_name(trustee_username)['id'] self.assertIsNotNone(self.trustee_user_id) # Initialize a new client with the trustor credentials creds = auth.get_credentials( username=self.trustor_username, password=self.trustor_password, tenant_name=self.trustor_project_name) os = clients.Manager( credentials=creds, interface=self._interface) self.trustor_client = os.identity_v3_client def cleanup_user_and_roles(self): if self.trustor_user_id: self.client.delete_user(self.trustor_user_id) if self.delegated_role_id: self.client.delete_role(self.delegated_role_id) if self.not_delegated_role_id: self.client.delete_role(self.not_delegated_role_id) def create_trust(self, impersonate=True, expires=None): resp, trust_create = self.trustor_client.create_trust( trustor_user_id=self.trustor_user_id, trustee_user_id=self.trustee_user_id, project_id=self.trustor_project_id, role_names=[self.delegated_role], impersonation=impersonate, expires_at=expires) self.assertEqual('201', resp['status']) self.trust_id = trust_create['id'] return trust_create def validate_trust(self, trust, impersonate=True, expires=None, summary=False): self.assertIsNotNone(trust['id']) self.assertEqual(impersonate, trust['impersonation']) # FIXME(shardy): ref bug #1246383 we can't check the # microsecond component of the expiry time, because mysql # <5.6.4 doesn't support microseconds. # expected format 2013-12-20T16:08:36.036987Z if expires is not None: expires_nousec = re.sub(r'\.([0-9]){6}Z', '', expires) self.assertTrue(trust['expires_at'].startswith(expires_nousec)) else: self.assertIsNone(trust['expires_at']) self.assertEqual(self.trustor_user_id, trust['trustor_user_id']) self.assertEqual(self.trustee_user_id, trust['trustee_user_id']) self.assertIn('v3/OS-TRUST/trusts', trust['links']['self']) self.assertEqual(self.trustor_project_id, trust['project_id']) if not summary: self.assertEqual(self.delegated_role, trust['roles'][0]['name']) self.assertEqual(1, len(trust['roles'])) def get_trust(self): resp, trust_get = self.trustor_client.get_trust(self.trust_id) self.assertEqual('200', resp['status']) return trust_get def validate_role(self, role): self.assertEqual(self.delegated_role_id, role['id']) self.assertEqual(self.delegated_role, role['name']) self.assertIn('v3/roles/%s' % self.delegated_role_id, role['links']['self']) self.assertNotEqual(self.not_delegated_role_id, role['id']) self.assertNotEqual(self.not_delegated_role, role['name']) self.assertNotIn('v3/roles/%s' % self.not_delegated_role_id, role['links']['self']) def check_trust_roles(self): # Check we find the delegated role resp, roles_get = self.trustor_client.get_trust_roles( self.trust_id) self.assertEqual('200', resp['status']) self.assertEqual(1, len(roles_get)) self.validate_role(roles_get[0]) resp, role_get = self.trustor_client.get_trust_role( self.trust_id, self.delegated_role_id) self.assertEqual('200', resp['status']) self.validate_role(role_get) resp, role_get = self.trustor_client.check_trust_role( self.trust_id, self.delegated_role_id) self.assertEqual('204', resp['status']) # And that we don't find not_delegated_role self.assertRaises(exceptions.NotFound, self.trustor_client.get_trust_role, self.trust_id, self.not_delegated_role_id) self.assertRaises(exceptions.NotFound, self.trustor_client.check_trust_role, self.trust_id, self.not_delegated_role_id) def delete_trust(self): resp, trust_delete = self.trustor_client.delete_trust(self.trust_id) self.assertEqual('204', resp['status']) self.assertRaises(exceptions.NotFound, self.trustor_client.get_trust, self.trust_id) self.trust_id = None class TrustsV3TestJSON(BaseTrustsV3Test): _interface = 'json' def setUp(self): super(TrustsV3TestJSON, self).setUp() self.create_trustor_and_roles() self.addCleanup(self.cleanup_user_and_roles) @test.attr(type='smoke') def test_trust_impersonate(self): # Test case to check we can create, get and delete a trust # updates are not supported for trusts trust = self.create_trust() self.validate_trust(trust) trust_get = self.get_trust() self.validate_trust(trust_get) self.check_trust_roles() @test.attr(type='smoke') def test_trust_noimpersonate(self): # Test case to check we can create, get and delete a trust # with impersonation=False trust = self.create_trust(impersonate=False) self.validate_trust(trust, impersonate=False) trust_get = self.get_trust() self.validate_trust(trust_get, impersonate=False) self.check_trust_roles() @test.attr(type='smoke') def test_trust_expire(self): # Test case to check we can create, get and delete a trust # with an expiry specified expires_at = timeutils.utcnow() + datetime.timedelta(hours=1) expires_str = timeutils.isotime(at=expires_at, subsecond=True) trust = self.create_trust(expires=expires_str) self.validate_trust(trust, expires=expires_str) trust_get = self.get_trust() self.validate_trust(trust_get, expires=expires_str) self.check_trust_roles() @test.attr(type='smoke') def test_trust_expire_invalid(self): # Test case to check we can check an invlaid expiry time # is rejected with the correct error # with an expiry specified expires_str = 'bad.123Z' self.assertRaises(exceptions.BadRequest, self.create_trust, expires=expires_str) @test.attr(type='smoke') def test_get_trusts_query(self): self.create_trust() resp, trusts_get = self.trustor_client.get_trusts( trustor_user_id=self.trustor_user_id) self.assertEqual('200', resp['status']) self.assertEqual(1, len(trusts_get)) self.validate_trust(trusts_get[0], summary=True) @test.attr(type='smoke') def test_get_trusts_all(self): self.create_trust() resp, trusts_get = self.client.get_trusts() self.assertEqual('200', resp['status']) trusts = [t for t in trusts_get if t['id'] == self.trust_id] self.assertEqual(1, len(trusts)) self.validate_trust(trusts[0], summary=True) tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_groups.py0000664000175000017500000001040212332757070026525 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import test class GroupsV3TestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(GroupsV3TestJSON, cls).setUpClass() @test.attr(type='smoke') def test_group_create_update_get(self): name = data_utils.rand_name('Group') description = data_utils.rand_name('Description') resp, group = self.client.create_group(name, description=description) self.addCleanup(self.client.delete_group, group['id']) self.assertEqual(resp['status'], '201') self.assertEqual(group['name'], name) self.assertEqual(group['description'], description) new_name = data_utils.rand_name('UpdateGroup') new_desc = data_utils.rand_name('UpdateDescription') resp, updated_group = self.client.update_group(group['id'], name=new_name, description=new_desc) self.assertEqual(resp['status'], '200') self.assertEqual(updated_group['name'], new_name) self.assertEqual(updated_group['description'], new_desc) resp, new_group = self.client.get_group(group['id']) self.assertEqual(resp['status'], '200') self.assertEqual(group['id'], new_group['id']) self.assertEqual(new_name, new_group['name']) self.assertEqual(new_desc, new_group['description']) @test.attr(type='smoke') def test_group_users_add_list_delete(self): name = data_utils.rand_name('Group') resp, group = self.client.create_group(name) self.addCleanup(self.client.delete_group, group['id']) # add user into group users = [] for i in range(3): name = data_utils.rand_name('User') resp, user = self.client.create_user(name) users.append(user) self.addCleanup(self.client.delete_user, user['id']) self.client.add_group_user(group['id'], user['id']) # list users in group resp, group_users = self.client.list_group_users(group['id']) self.assertEqual(resp['status'], '200') self.assertEqual(sorted(users), sorted(group_users)) # delete user in group for user in users: resp, body = self.client.delete_group_user(group['id'], user['id']) self.assertEqual(resp['status'], '204') resp, group_users = self.client.list_group_users(group['id']) self.assertEqual(len(group_users), 0) @test.attr(type='smoke') def test_list_user_groups(self): # create a user resp, user = self.client.create_user( data_utils.rand_name('User-'), password=data_utils.rand_name('Pass-')) self.addCleanup(self.client.delete_user, user['id']) # create two groups, and add user into them groups = [] for i in range(2): name = data_utils.rand_name('Group-') resp, group = self.client.create_group(name) groups.append(group) self.addCleanup(self.client.delete_group, group['id']) self.client.add_group_user(group['id'], user['id']) # list groups which user belongs to resp, user_groups = self.client.list_user_groups(user['id']) self.assertEqual('200', resp['status']) self.assertEqual(sorted(groups), sorted(user_groups)) self.assertEqual(2, len(user_groups)) class GroupsV3TestXML(GroupsV3TestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_tokens.py0000664000175000017500000001632712332757070026525 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class TokensV3TestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @attr(type='smoke') def test_tokens(self): # Valid user's token is authenticated # Create a User u_name = data_utils.rand_name('user-') u_desc = '%s-description' % u_name u_email = '%s@testmail.tm' % u_name u_password = data_utils.rand_name('pass-') resp, user = self.client.create_user( u_name, description=u_desc, password=u_password, email=u_email) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_user, user['id']) # Perform Authentication resp, body = self.token.auth(user['id'], u_password) self.assertEqual(201, resp.status) subject_token = resp['x-subject-token'] # Perform GET Token resp, token_details = self.client.get_token(subject_token) self.assertEqual(200, resp.status) self.assertEqual(resp['x-subject-token'], subject_token) self.assertEqual(token_details['user']['id'], user['id']) self.assertEqual(token_details['user']['name'], u_name) # Perform Delete Token resp, _ = self.client.delete_token(subject_token) self.assertRaises(exceptions.NotFound, self.client.get_token, subject_token) @attr(type='gate') def test_rescope_token(self): """Rescope a token. An unscoped token can be requested, that token can be used to request a scoped token. The scoped token can be revoked, and the original token used to get a token in a different project. """ # Create a user. user_name = data_utils.rand_name(name='user-') user_password = data_utils.rand_name(name='pass-') resp, user = self.client.create_user(user_name, password=user_password) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_user, user['id']) # Create a couple projects project1_name = data_utils.rand_name(name='project-') resp, project1 = self.client.create_project(project1_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_project, project1['id']) project2_name = data_utils.rand_name(name='project-') resp, project2 = self.client.create_project(project2_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_project, project2['id']) # Create a role role_name = data_utils.rand_name(name='role-') resp, role = self.client.create_role(role_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_role, role['id']) # Grant the user the role on both projects. resp, _ = self.client.assign_user_role(project1['id'], user['id'], role['id']) self.assertEqual(204, resp.status) resp, _ = self.client.assign_user_role(project2['id'], user['id'], role['id']) self.assertEqual(204, resp.status) # Get an unscoped token. resp, token_auth = self.token.auth(user=user['id'], password=user_password) self.assertEqual(201, resp.status) token_id = resp['x-subject-token'] orig_expires_at = token_auth['token']['expires_at'] orig_issued_at = token_auth['token']['issued_at'] orig_user = token_auth['token']['user'] self.assertIsInstance(token_auth['token']['expires_at'], unicode) self.assertIsInstance(token_auth['token']['issued_at'], unicode) self.assertEqual(['password'], token_auth['token']['methods']) self.assertEqual(user['id'], token_auth['token']['user']['id']) self.assertEqual(user['name'], token_auth['token']['user']['name']) self.assertEqual('default', token_auth['token']['user']['domain']['id']) self.assertEqual('Default', token_auth['token']['user']['domain']['name']) self.assertNotIn('catalog', token_auth['token']) self.assertNotIn('project', token_auth['token']) self.assertNotIn('roles', token_auth['token']) # Use the unscoped token to get a scoped token. resp, token_auth = self.token.auth(token=token_id, tenant=project1_name, domain='Default') token1_id = resp['x-subject-token'] self.assertEqual(201, resp.status) self.assertEqual(orig_expires_at, token_auth['token']['expires_at'], 'Expiration time should match original token') self.assertIsInstance(token_auth['token']['issued_at'], unicode) self.assertNotEqual(orig_issued_at, token_auth['token']['issued_at']) self.assertEqual(set(['password', 'token']), set(token_auth['token']['methods'])) self.assertEqual(orig_user, token_auth['token']['user'], 'User should match original token') self.assertIsInstance(token_auth['token']['catalog'], list) self.assertEqual(project1['id'], token_auth['token']['project']['id']) self.assertEqual(project1['name'], token_auth['token']['project']['name']) self.assertEqual('default', token_auth['token']['project']['domain']['id']) self.assertEqual('Default', token_auth['token']['project']['domain']['name']) self.assertEqual(1, len(token_auth['token']['roles'])) self.assertEqual(role['id'], token_auth['token']['roles'][0]['id']) self.assertEqual(role['name'], token_auth['token']['roles'][0]['name']) # Revoke the unscoped token. resp, _ = self.client.delete_token(token1_id) self.assertEqual(204, resp.status) # Now get another scoped token using the unscoped token. resp, token_auth = self.token.auth(token=token_id, tenant=project2_name, domain='Default') self.assertEqual(201, resp.status) self.assertEqual(project2['id'], token_auth['token']['project']['id']) self.assertEqual(project2['name'], token_auth['token']['project']['name']) class TokensV3TestXML(TokensV3TestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/v3/test_services.py0000664000175000017500000000401012332757070027027 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest.test import attr class ServicesTestJSON(base.BaseIdentityV3AdminTest): _interface = 'json' @attr(type='gate') def test_update_service(self): # Update description attribute of service name = data_utils.rand_name('service-') serv_type = data_utils.rand_name('type--') desc = data_utils.rand_name('description-') resp, body = self.service_client.create_service(name, serv_type, description=desc) self.assertEqual('201', resp['status']) # Deleting the service created in this method self.addCleanup(self.service_client.delete_service, body['id']) s_id = body['id'] resp1_desc = body['description'] s_desc2 = data_utils.rand_name('desc2-') resp, body = self.service_client.update_service( s_id, description=s_desc2) resp2_desc = body['description'] self.assertEqual('200', resp['status']) self.assertNotEqual(resp1_desc, resp2_desc) # Get service resp, body = self.service_client.get_service(s_id) resp3_desc = body['description'] self.assertNotEqual(resp1_desc, resp3_desc) self.assertEqual(resp2_desc, resp3_desc) class ServicesTestXML(ServicesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_roles.py0000664000175000017500000001064612332757070026014 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from six import moves from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import test class RolesTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' @classmethod @test.safe_setup def setUpClass(cls): super(RolesTestJSON, cls).setUpClass() for _ in moves.xrange(5): role_name = data_utils.rand_name(name='role-') resp, role = cls.client.create_role(role_name) cls.data.roles.append(role) def _get_role_params(self): self.data.setup_test_user() self.data.setup_test_role() user = self.get_user_by_name(self.data.test_user) tenant = self.get_tenant_by_name(self.data.test_tenant) role = self.get_role_by_name(self.data.test_role) return (user, tenant, role) def assert_role_in_role_list(self, role, roles): found = False for user_role in roles: if user_role['id'] == role['id']: found = True self.assertTrue(found, "assigned role was not in list") @test.attr(type='gate') def test_list_roles(self): # Return a list of all roles resp, body = self.client.list_roles() found = [role for role in body if role in self.data.roles] self.assertTrue(any(found)) self.assertEqual(len(found), len(self.data.roles)) @test.attr(type='gate') def test_role_create_delete(self): # Role should be created, verified, and deleted role_name = data_utils.rand_name(name='role-test-') resp, body = self.client.create_role(role_name) self.assertEqual(200, resp.status) self.assertEqual(role_name, body['name']) resp, body = self.client.list_roles() found = [role for role in body if role['name'] == role_name] self.assertTrue(any(found)) resp, body = self.client.delete_role(found[0]['id']) self.assertEqual(204, resp.status) resp, body = self.client.list_roles() found = [role for role in body if role['name'] == role_name] self.assertFalse(any(found)) @test.attr(type='gate') def test_get_role_by_id(self): # Get a role by its id self.data.setup_test_role() role_id = self.data.role['id'] role_name = self.data.role['name'] resp, body = self.client.get_role(role_id) self.assertIn('status', resp) self.assertTrue('200', resp['status']) self.assertEqual(role_id, body['id']) self.assertEqual(role_name, body['name']) @test.attr(type='gate') def test_assign_user_role(self): # Assign a role to a user on a tenant (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) resp, roles = self.client.list_user_roles(tenant['id'], user['id']) self.assert_role_in_role_list(role, roles) @test.attr(type='gate') def test_remove_user_role(self): # Remove a role assigned to a user on a tenant (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) resp, body = self.client.remove_user_role(tenant['id'], user['id'], user_role['id']) self.assertEqual(204, resp.status) @test.attr(type='gate') def test_list_user_roles(self): # List roles assigned to a user on tenant (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) resp, roles = self.client.list_user_roles(tenant['id'], user['id']) self.assert_role_in_role_list(role, roles) class RolesTestXML(RolesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_roles_negative.py0000664000175000017500000002501212332757070027667 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class RolesNegativeTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' def _get_role_params(self): self.data.setup_test_user() self.data.setup_test_role() user = self.get_user_by_name(self.data.test_user) tenant = self.get_tenant_by_name(self.data.test_tenant) role = self.get_role_by_name(self.data.test_role) return (user, tenant, role) @attr(type=['negative', 'gate']) def test_list_roles_by_unauthorized_user(self): # Non-administrator user should not be able to list roles self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_roles) @attr(type=['negative', 'gate']) def test_list_roles_request_without_token(self): # Request to list roles without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.list_roles) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_role_create_blank_name(self): # Should not be able to create a role with a blank name self.assertRaises(exceptions.BadRequest, self.client.create_role, '') @attr(type=['negative', 'gate']) def test_create_role_by_unauthorized_user(self): # Non-administrator user should not be able to create role role_name = data_utils.rand_name(name='role-') self.assertRaises(exceptions.Unauthorized, self.non_admin_client.create_role, role_name) @attr(type=['negative', 'gate']) def test_create_role_request_without_token(self): # Request to create role without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) role_name = data_utils.rand_name(name='role-') self.assertRaises(exceptions.Unauthorized, self.client.create_role, role_name) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_role_create_duplicate(self): # Role names should be unique role_name = data_utils.rand_name(name='role-dup-') resp, body = self.client.create_role(role_name) role1_id = body.get('id') self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_role, role1_id) self.assertRaises(exceptions.Conflict, self.client.create_role, role_name) @attr(type=['negative', 'gate']) def test_delete_role_by_unauthorized_user(self): # Non-administrator user should not be able to delete role role_name = data_utils.rand_name(name='role-') resp, body = self.client.create_role(role_name) self.assertEqual(200, resp.status) self.data.roles.append(body) role_id = body.get('id') self.assertRaises(exceptions.Unauthorized, self.non_admin_client.delete_role, role_id) @attr(type=['negative', 'gate']) def test_delete_role_request_without_token(self): # Request to delete role without a valid token should fail role_name = data_utils.rand_name(name='role-') resp, body = self.client.create_role(role_name) self.assertEqual(200, resp.status) self.data.roles.append(body) role_id = body.get('id') token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.delete_role, role_id) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_delete_role_non_existent(self): # Attempt to delete a non existent role should fail non_existent_role = str(uuid.uuid4().hex) self.assertRaises(exceptions.NotFound, self.client.delete_role, non_existent_role) @attr(type=['negative', 'gate']) def test_assign_user_role_by_unauthorized_user(self): # Non-administrator user should not be authorized to # assign a role to user (user, tenant, role) = self._get_role_params() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.assign_user_role, tenant['id'], user['id'], role['id']) @attr(type=['negative', 'gate']) def test_assign_user_role_request_without_token(self): # Request to assign a role to a user without a valid token (user, tenant, role) = self._get_role_params() token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.assign_user_role, tenant['id'], user['id'], role['id']) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_assign_user_role_for_non_existent_role(self): # Attempt to assign a non existent role to user should fail (user, tenant, role) = self._get_role_params() non_existent_role = str(uuid.uuid4().hex) self.assertRaises(exceptions.NotFound, self.client.assign_user_role, tenant['id'], user['id'], non_existent_role) @attr(type=['negative', 'gate']) def test_assign_user_role_for_non_existent_tenant(self): # Attempt to assign a role on a non existent tenant should fail (user, tenant, role) = self._get_role_params() non_existent_tenant = str(uuid.uuid4().hex) self.assertRaises(exceptions.NotFound, self.client.assign_user_role, non_existent_tenant, user['id'], role['id']) @attr(type=['negative', 'gate']) def test_assign_duplicate_user_role(self): # Duplicate user role should not get assigned (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.Conflict, self.client.assign_user_role, tenant['id'], user['id'], role['id']) @attr(type=['negative', 'gate']) def test_remove_user_role_by_unauthorized_user(self): # Non-administrator user should not be authorized to # remove a user's role (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.Unauthorized, self.non_admin_client.remove_user_role, tenant['id'], user['id'], role['id']) @attr(type=['negative', 'gate']) def test_remove_user_role_request_without_token(self): # Request to remove a user's role without a valid token (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.remove_user_role, tenant['id'], user['id'], role['id']) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_remove_user_role_non_existent_role(self): # Attempt to delete a non existent role from a user should fail (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) non_existent_role = str(uuid.uuid4().hex) self.assertRaises(exceptions.NotFound, self.client.remove_user_role, tenant['id'], user['id'], non_existent_role) @attr(type=['negative', 'gate']) def test_remove_user_role_non_existent_tenant(self): # Attempt to remove a role from a non existent tenant should fail (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) non_existent_tenant = str(uuid.uuid4().hex) self.assertRaises(exceptions.NotFound, self.client.remove_user_role, non_existent_tenant, user['id'], role['id']) @attr(type=['negative', 'gate']) def test_list_user_roles_by_unauthorized_user(self): # Non-administrator user should not be authorized to list # a user's roles (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_user_roles, tenant['id'], user['id']) @attr(type=['negative', 'gate']) def test_list_user_roles_request_without_token(self): # Request to list user's roles without a valid token should fail (user, tenant, role) = self._get_role_params() token = self.client.auth_provider.get_token() self.client.delete_token(token) try: self.assertRaises(exceptions.Unauthorized, self.client.list_user_roles, tenant['id'], user['id']) finally: self.client.auth_provider.clear_auth() class RolesTestXML(RolesNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_users.py0000664000175000017500000002300312332757070026020 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from testtools import matchers from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import test class UsersTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(UsersTestJSON, cls).setUpClass() cls.alt_user = data_utils.rand_name('test_user_') cls.alt_password = data_utils.rand_name('pass_') cls.alt_email = cls.alt_user + '@testmail.tm' @test.attr(type='smoke') def test_create_user(self): # Create a user self.data.setup_test_tenant() resp, user = self.client.create_user(self.alt_user, self.alt_password, self.data.tenant['id'], self.alt_email) self.data.users.append(user) self.assertEqual('200', resp['status']) self.assertEqual(self.alt_user, user['name']) @test.attr(type='smoke') def test_create_user_with_enabled(self): # Create a user with enabled : False self.data.setup_test_tenant() name = data_utils.rand_name('test_user_') resp, user = self.client.create_user(name, self.alt_password, self.data.tenant['id'], self.alt_email, enabled=False) self.data.users.append(user) self.assertEqual('200', resp['status']) self.assertEqual(name, user['name']) self.assertEqual('false', str(user['enabled']).lower()) self.assertEqual(self.alt_email, user['email']) @test.attr(type='smoke') def test_update_user(self): # Test case to check if updating of user attributes is successful. test_user = data_utils.rand_name('test_user_') self.data.setup_test_tenant() resp, user = self.client.create_user(test_user, self.alt_password, self.data.tenant['id'], self.alt_email) # Delete the User at the end of this method self.addCleanup(self.client.delete_user, user['id']) # Updating user details with new values u_name2 = data_utils.rand_name('user2-') u_email2 = u_name2 + '@testmail.tm' resp, update_user = self.client.update_user(user['id'], name=u_name2, email=u_email2, enabled=False) # Assert response body of update user. self.assertEqual(200, resp.status) self.assertEqual(u_name2, update_user['name']) self.assertEqual(u_email2, update_user['email']) self.assertEqual('false', str(update_user['enabled']).lower()) # GET by id after updating resp, updated_user = self.client.get_user(user['id']) # Assert response body of GET after updating self.assertEqual(u_name2, updated_user['name']) self.assertEqual(u_email2, updated_user['email']) self.assertEqual('false', str(updated_user['enabled']).lower()) @test.attr(type='smoke') def test_delete_user(self): # Delete a user test_user = data_utils.rand_name('test_user_') self.data.setup_test_tenant() resp, user = self.client.create_user(test_user, self.alt_password, self.data.tenant['id'], self.alt_email) self.assertEqual('200', resp['status']) resp, body = self.client.delete_user(user['id']) self.assertEqual('204', resp['status']) @test.attr(type='smoke') def test_user_authentication(self): # Valid user's token is authenticated self.data.setup_test_user() # Get a token self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) # Re-auth resp, body = self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) self.assertEqual('200', resp['status']) @test.attr(type='gate') def test_authentication_request_without_token(self): # Request for token authentication with a valid token in header self.data.setup_test_user() self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) # Re-auth resp, body = self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) self.assertEqual('200', resp['status']) self.client.auth_provider.clear_auth() @test.attr(type='smoke') def test_get_users(self): # Get a list of users and find the test user self.data.setup_test_user() resp, users = self.client.get_users() self.assertThat([u['name'] for u in users], matchers.Contains(self.data.test_user), "Could not find %s" % self.data.test_user) @test.attr(type='gate') def test_list_users_for_tenant(self): # Return a list of all users for a tenant self.data.setup_test_tenant() user_ids = list() fetched_user_ids = list() alt_tenant_user1 = data_utils.rand_name('tenant_user1_') resp, user1 = self.client.create_user(alt_tenant_user1, 'password1', self.data.tenant['id'], 'user1@123') self.assertEqual('200', resp['status']) user_ids.append(user1['id']) self.data.users.append(user1) alt_tenant_user2 = data_utils.rand_name('tenant_user2_') resp, user2 = self.client.create_user(alt_tenant_user2, 'password2', self.data.tenant['id'], 'user2@123') self.assertEqual('200', resp['status']) user_ids.append(user2['id']) self.data.users.append(user2) # List of users for the respective tenant ID resp, body = self.client.list_users_for_tenant(self.data.tenant['id']) self.assertIn(resp['status'], ('200', '203')) for i in body: fetched_user_ids.append(i['id']) # verifying the user Id in the list missing_users =\ [user for user in user_ids if user not in fetched_user_ids] self.assertEqual(0, len(missing_users), "Failed to find user %s in fetched list" % ', '.join(m_user for m_user in missing_users)) @test.attr(type='gate') def test_list_users_with_roles_for_tenant(self): # Return list of users on tenant when roles are assigned to users self.data.setup_test_user() self.data.setup_test_role() user = self.get_user_by_name(self.data.test_user) tenant = self.get_tenant_by_name(self.data.test_tenant) role = self.get_role_by_name(self.data.test_role) # Assigning roles to two users user_ids = list() fetched_user_ids = list() user_ids.append(user['id']) resp, role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertEqual('200', resp['status']) alt_user2 = data_utils.rand_name('second_user_') resp, second_user = self.client.create_user(alt_user2, 'password1', self.data.tenant['id'], 'user2@123') self.assertEqual('200', resp['status']) user_ids.append(second_user['id']) self.data.users.append(second_user) resp, role = self.client.assign_user_role(tenant['id'], second_user['id'], role['id']) self.assertEqual('200', resp['status']) # List of users with roles for the respective tenant ID resp, body = self.client.list_users_for_tenant(self.data.tenant['id']) self.assertEqual('200', resp['status']) for i in body: fetched_user_ids.append(i['id']) # verifying the user Id in the list missing_users = [missing_user for missing_user in user_ids if missing_user not in fetched_user_ids] self.assertEqual(0, len(missing_users), "Failed to find user %s in fetched list" % ', '.join(m_user for m_user in missing_users)) class UsersTestXML(UsersTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_tenant_negative.py0000664000175000017500000001442212332757070030037 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class TenantsNegativeTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' @attr(type=['negative', 'gate']) def test_list_tenants_by_unauthorized_user(self): # Non-administrator user should not be able to list tenants self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_tenants) @attr(type=['negative', 'gate']) def test_list_tenant_request_without_token(self): # Request to list tenants without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.list_tenants) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_tenant_delete_by_unauthorized_user(self): # Non-administrator user should not be able to delete a tenant tenant_name = data_utils.rand_name(name='tenant-') resp, tenant = self.client.create_tenant(tenant_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant) self.assertRaises(exceptions.Unauthorized, self.non_admin_client.delete_tenant, tenant['id']) @attr(type=['negative', 'gate']) def test_tenant_delete_request_without_token(self): # Request to delete a tenant without a valid token should fail tenant_name = data_utils.rand_name(name='tenant-') resp, tenant = self.client.create_tenant(tenant_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant) token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.delete_tenant, tenant['id']) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_delete_non_existent_tenant(self): # Attempt to delete a non existent tenant should fail self.assertRaises(exceptions.NotFound, self.client.delete_tenant, str(uuid.uuid4().hex)) @attr(type=['negative', 'gate']) def test_tenant_create_duplicate(self): # Tenant names should be unique tenant_name = data_utils.rand_name(name='tenant-') resp, body = self.client.create_tenant(tenant_name) self.assertEqual(200, resp.status) tenant = body self.data.tenants.append(tenant) tenant1_id = body.get('id') self.addCleanup(self.client.delete_tenant, tenant1_id) self.addCleanup(self.data.tenants.remove, tenant) self.assertRaises(exceptions.Conflict, self.client.create_tenant, tenant_name) @attr(type=['negative', 'gate']) def test_create_tenant_by_unauthorized_user(self): # Non-administrator user should not be authorized to create a tenant tenant_name = data_utils.rand_name(name='tenant-') self.assertRaises(exceptions.Unauthorized, self.non_admin_client.create_tenant, tenant_name) @attr(type=['negative', 'gate']) def test_create_tenant_request_without_token(self): # Create tenant request without a token should not be authorized tenant_name = data_utils.rand_name(name='tenant-') token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.create_tenant, tenant_name) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_create_tenant_with_empty_name(self): # Tenant name should not be empty self.assertRaises(exceptions.BadRequest, self.client.create_tenant, name='') @attr(type=['negative', 'gate']) def test_create_tenants_name_length_over_64(self): # Tenant name length should not be greater than 64 characters tenant_name = 'a' * 65 self.assertRaises(exceptions.BadRequest, self.client.create_tenant, tenant_name) @attr(type=['negative', 'gate']) def test_update_non_existent_tenant(self): # Attempt to update a non existent tenant should fail self.assertRaises(exceptions.NotFound, self.client.update_tenant, str(uuid.uuid4().hex)) @attr(type=['negative', 'gate']) def test_tenant_update_by_unauthorized_user(self): # Non-administrator user should not be able to update a tenant tenant_name = data_utils.rand_name(name='tenant-') resp, tenant = self.client.create_tenant(tenant_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant) self.assertRaises(exceptions.Unauthorized, self.non_admin_client.update_tenant, tenant['id']) @attr(type=['negative', 'gate']) def test_tenant_update_request_without_token(self): # Request to update a tenant without a valid token should fail tenant_name = data_utils.rand_name(name='tenant-') resp, tenant = self.client.create_tenant(tenant_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant) token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.update_tenant, tenant['id']) self.client.auth_provider.clear_auth() class TenantsNegativeTestXML(TenantsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/__init__.py0000664000175000017500000000000012332757070025347 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_users_negative.py0000664000175000017500000002412612332757070027711 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class UsersNegativeTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(UsersNegativeTestJSON, cls).setUpClass() cls.alt_user = data_utils.rand_name('test_user_') cls.alt_password = data_utils.rand_name('pass_') cls.alt_email = cls.alt_user + '@testmail.tm' @attr(type=['negative', 'gate']) def test_create_user_by_unauthorized_user(self): # Non-administrator should not be authorized to create a user self.data.setup_test_tenant() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.create_user, self.alt_user, self.alt_password, self.data.tenant['id'], self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_with_empty_name(self): # User with an empty name should not be created self.data.setup_test_tenant() self.assertRaises(exceptions.BadRequest, self.client.create_user, '', self.alt_password, self.data.tenant['id'], self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_with_name_length_over_255(self): # Length of user name filed should be restricted to 255 characters self.data.setup_test_tenant() self.assertRaises(exceptions.BadRequest, self.client.create_user, 'a' * 256, self.alt_password, self.data.tenant['id'], self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_with_duplicate_name(self): # Duplicate user should not be created self.data.setup_test_user() self.assertRaises(exceptions.Conflict, self.client.create_user, self.data.test_user, self.data.test_password, self.data.tenant['id'], self.data.test_email) @attr(type=['negative', 'gate']) def test_create_user_for_non_existent_tenant(self): # Attempt to create a user in a non-existent tenant should fail self.assertRaises(exceptions.NotFound, self.client.create_user, self.alt_user, self.alt_password, '49ffgg99999', self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_request_without_a_token(self): # Request to create a user without a valid token should fail self.data.setup_test_tenant() # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.create_user, self.alt_user, self.alt_password, self.data.tenant['id'], self.alt_email) # Unset the token to allow further tests to generate a new token self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_create_user_with_enabled_non_bool(self): # Attempt to create a user with valid enabled para should fail self.data.setup_test_tenant() name = data_utils.rand_name('test_user_') self.assertRaises(exceptions.BadRequest, self.client.create_user, name, self.alt_password, self.data.tenant['id'], self.alt_email, enabled=3) @attr(type=['negative', 'gate']) def test_update_user_for_non_existent_user(self): # Attempt to update a user non-existent user should fail user_name = data_utils.rand_name('user-') non_existent_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.update_user, non_existent_id, name=user_name) @attr(type=['negative', 'gate']) def test_update_user_request_without_a_token(self): # Request to update a user without a valid token should fail # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.update_user, self.alt_user) # Unset the token to allow further tests to generate a new token self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_update_user_by_unauthorized_user(self): # Non-administrator should not be authorized to update user self.data.setup_test_tenant() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.update_user, self.alt_user) @attr(type=['negative', 'gate']) def test_delete_users_by_unauthorized_user(self): # Non-administrator user should not be authorized to delete a user self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.delete_user, self.data.user['id']) @attr(type=['negative', 'gate']) def test_delete_non_existent_user(self): # Attempt to delete a non-existent user should fail self.assertRaises(exceptions.NotFound, self.client.delete_user, 'junk12345123') @attr(type=['negative', 'gate']) def test_delete_user_request_without_a_token(self): # Request to delete a user without a valid token should fail # Get the token of the current client token = self.client.auth_provider.get_token() # Delete the token from database self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.delete_user, self.alt_user) # Unset the token to allow further tests to generate a new token self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_authentication_for_disabled_user(self): # Disabled user's token should not get authenticated self.data.setup_test_user() self.disable_user(self.data.test_user) self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, self.data.test_password, self.data.test_tenant) @attr(type=['negative', 'gate']) def test_authentication_when_tenant_is_disabled(self): # User's token for a disabled tenant should not be authenticated self.data.setup_test_user() self.disable_tenant(self.data.test_tenant) self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, self.data.test_password, self.data.test_tenant) @attr(type=['negative', 'gate']) def test_authentication_with_invalid_tenant(self): # User's token for an invalid tenant should not be authenticated self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, self.data.test_password, 'junktenant1234') @attr(type=['negative', 'gate']) def test_authentication_with_invalid_username(self): # Non-existent user's token should not get authenticated self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.token_client.auth, 'junkuser123', self.data.test_password, self.data.test_tenant) @attr(type=['negative', 'gate']) def test_authentication_with_invalid_password(self): # User's token with invalid password should not be authenticated self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, 'junkpass1234', self.data.test_tenant) @attr(type=['negative', 'gate']) def test_get_users_by_unauthorized_user(self): # Non-administrator user should not be authorized to get user list self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.get_users) @attr(type=['negative', 'gate']) def test_get_users_request_without_token(self): # Request to get list of users without a valid token should fail token = self.client.auth_provider.get_token() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.get_users) self.client.auth_provider.clear_auth() @attr(type=['negative', 'gate']) def test_list_users_with_invalid_tenant(self): # Should not be able to return a list of all # users for a non-existent tenant # Assign invalid tenant ids invalid_id = list() invalid_id.append(data_utils.rand_name('999')) invalid_id.append('alpha') invalid_id.append(data_utils.rand_name("dddd@#%%^$")) invalid_id.append('!@#()$%^&*?<>{}[]') # List the users with invalid tenant id for invalid in invalid_id: self.assertRaises(exceptions.NotFound, self.client.list_users_for_tenant, invalid) class UsersNegativeTestXML(UsersNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_tenants.py0000664000175000017500000001645312332757070026346 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from six import moves from tempest.api.identity import base from tempest.common.utils import data_utils from tempest.test import attr class TenantsTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' @attr(type='gate') def test_tenant_list_delete(self): # Create several tenants and delete them tenants = [] for _ in moves.xrange(3): tenant_name = data_utils.rand_name(name='tenant-new') resp, tenant = self.client.create_tenant(tenant_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant) tenants.append(tenant) tenant_ids = map(lambda x: x['id'], tenants) resp, body = self.client.list_tenants() self.assertEqual(200, resp.status) found = [tenant for tenant in body if tenant['id'] in tenant_ids] self.assertEqual(len(found), len(tenants), 'Tenants not created') for tenant in tenants: resp, body = self.client.delete_tenant(tenant['id']) self.assertEqual(204, resp.status) self.data.tenants.remove(tenant) resp, body = self.client.list_tenants() found = [tenant for tenant in body if tenant['id'] in tenant_ids] self.assertFalse(any(found), 'Tenants failed to delete') @attr(type='gate') def test_tenant_create_with_description(self): # Create tenant with a description tenant_name = data_utils.rand_name(name='tenant-') tenant_desc = data_utils.rand_name(name='desc-') resp, body = self.client.create_tenant(tenant_name, description=tenant_desc) tenant = body self.data.tenants.append(tenant) tenant_id = body['id'] desc1 = body['description'] self.assertEqual(200, resp.status) self.assertEqual(desc1, tenant_desc, 'Description should have ' 'been sent in response for create') resp, body = self.client.get_tenant(tenant_id) desc2 = body['description'] self.assertEqual(desc2, tenant_desc, 'Description does not appear' 'to be set') self.client.delete_tenant(tenant_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_create_enabled(self): # Create a tenant that is enabled tenant_name = data_utils.rand_name(name='tenant-') resp, body = self.client.create_tenant(tenant_name, enabled=True) tenant = body self.data.tenants.append(tenant) tenant_id = body['id'] en1 = body['enabled'] self.assertEqual(200, resp.status) self.assertTrue(en1, 'Enable should be True in response') resp, body = self.client.get_tenant(tenant_id) en2 = body['enabled'] self.assertTrue(en2, 'Enable should be True in lookup') self.client.delete_tenant(tenant_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_create_not_enabled(self): # Create a tenant that is not enabled tenant_name = data_utils.rand_name(name='tenant-') resp, body = self.client.create_tenant(tenant_name, enabled=False) tenant = body self.data.tenants.append(tenant) tenant_id = body['id'] en1 = body['enabled'] self.assertEqual(200, resp.status) self.assertEqual('false', str(en1).lower(), 'Enable should be False in response') resp, body = self.client.get_tenant(tenant_id) en2 = body['enabled'] self.assertEqual('false', str(en2).lower(), 'Enable should be False in lookup') self.client.delete_tenant(tenant_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_update_name(self): # Update name attribute of a tenant t_name1 = data_utils.rand_name(name='tenant-') resp, body = self.client.create_tenant(t_name1) self.assertEqual(200, resp.status) tenant = body self.data.tenants.append(tenant) t_id = body['id'] resp1_name = body['name'] t_name2 = data_utils.rand_name(name='tenant2-') resp, body = self.client.update_tenant(t_id, name=t_name2) resp2_name = body['name'] self.assertEqual(200, resp.status) self.assertNotEqual(resp1_name, resp2_name) resp, body = self.client.get_tenant(t_id) resp3_name = body['name'] self.assertNotEqual(resp1_name, resp3_name) self.assertEqual(t_name1, resp1_name) self.assertEqual(resp2_name, resp3_name) self.client.delete_tenant(t_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_update_desc(self): # Update description attribute of a tenant t_name = data_utils.rand_name(name='tenant-') t_desc = data_utils.rand_name(name='desc-') resp, body = self.client.create_tenant(t_name, description=t_desc) self.assertEqual(200, resp.status) tenant = body self.data.tenants.append(tenant) t_id = body['id'] resp1_desc = body['description'] t_desc2 = data_utils.rand_name(name='desc2-') resp, body = self.client.update_tenant(t_id, description=t_desc2) resp2_desc = body['description'] self.assertEqual(200, resp.status) self.assertNotEqual(resp1_desc, resp2_desc) resp, body = self.client.get_tenant(t_id) resp3_desc = body['description'] self.assertNotEqual(resp1_desc, resp3_desc) self.assertEqual(t_desc, resp1_desc) self.assertEqual(resp2_desc, resp3_desc) self.client.delete_tenant(t_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_update_enable(self): # Update the enabled attribute of a tenant t_name = data_utils.rand_name(name='tenant-') t_en = False resp, body = self.client.create_tenant(t_name, enabled=t_en) self.assertEqual(200, resp.status) tenant = body self.data.tenants.append(tenant) t_id = body['id'] resp1_en = body['enabled'] t_en2 = True resp, body = self.client.update_tenant(t_id, enabled=t_en2) resp2_en = body['enabled'] self.assertEqual(200, resp.status) self.assertNotEqual(resp1_en, resp2_en) resp, body = self.client.get_tenant(t_id) resp3_en = body['enabled'] self.assertNotEqual(resp1_en, resp3_en) self.assertEqual('false', str(resp1_en).lower()) self.assertEqual(resp2_en, resp3_en) self.client.delete_tenant(t_id) self.data.tenants.remove(tenant) class TenantsTestXML(TenantsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_tokens.py0000664000175000017500000001165512332757070026174 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.identity import base from tempest.common.utils import data_utils from tempest.test import attr class TokensTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' @attr(type='gate') def test_create_get_delete_token(self): # get a token by username and password user_name = data_utils.rand_name(name='user-') user_password = data_utils.rand_name(name='pass-') # first:create a tenant tenant_name = data_utils.rand_name(name='tenant-') resp, tenant = self.client.create_tenant(tenant_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant) # second:create a user resp, user = self.client.create_user(user_name, user_password, tenant['id'], '') self.assertEqual(200, resp.status) self.data.users.append(user) # then get a token for the user rsp, body = self.token_client.auth(user_name, user_password, tenant['name']) self.assertEqual(rsp['status'], '200') self.assertEqual(body['token']['tenant']['name'], tenant['name']) # Perform GET Token token_id = body['token']['id'] resp, token_details = self.client.get_token(token_id) self.assertEqual(resp['status'], '200') self.assertEqual(token_id, token_details['token']['id']) self.assertEqual(user['id'], token_details['user']['id']) self.assertEqual(user_name, token_details['user']['name']) self.assertEqual(tenant['name'], token_details['token']['tenant']['name']) # then delete the token resp, body = self.client.delete_token(token_id) self.assertEqual(resp['status'], '204') @attr(type='gate') def test_rescope_token(self): """An unscoped token can be requested, that token can be used to request a scoped token. """ # Create a user. user_name = data_utils.rand_name(name='user-') user_password = data_utils.rand_name(name='pass-') tenant_id = None # No default tenant so will get unscoped token. email = '' resp, user = self.client.create_user(user_name, user_password, tenant_id, email) self.assertEqual(200, resp.status) self.data.users.append(user) # Create a couple tenants. tenant1_name = data_utils.rand_name(name='tenant-') resp, tenant1 = self.client.create_tenant(tenant1_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant1) tenant2_name = data_utils.rand_name(name='tenant-') resp, tenant2 = self.client.create_tenant(tenant2_name) self.assertEqual(200, resp.status) self.data.tenants.append(tenant2) # Create a role role_name = data_utils.rand_name(name='role-') resp, role = self.client.create_role(role_name) self.assertEqual(200, resp.status) self.data.roles.append(role) # Grant the user the role on the tenants. resp, _ = self.client.assign_user_role(tenant1['id'], user['id'], role['id']) self.assertEqual(200, resp.status) resp, _ = self.client.assign_user_role(tenant2['id'], user['id'], role['id']) self.assertEqual(200, resp.status) # Get an unscoped token. rsp, body = self.token_client.auth(user_name, user_password) self.assertEqual(200, resp.status) token_id = body['token']['id'] # Use the unscoped token to get a token scoped to tenant1 rsp, body = self.token_client.auth_token(token_id, tenant=tenant1_name) self.assertEqual(200, resp.status) scoped_token_id = body['token']['id'] # Revoke the scoped token resp, body = self.client.delete_token(scoped_token_id) self.assertEqual(204, resp.status) # Use the unscoped token to get a token scoped to tenant2 rsp, body = self.token_client.auth_token(token_id, tenant=tenant2_name) self.assertEqual(204, resp.status) class TokensTestXML(TokensTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/admin/test_services.py0000664000175000017500000001071212332757070026505 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from six import moves from tempest.api.identity import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class ServicesTestJSON(base.BaseIdentityV2AdminTest): _interface = 'json' def _del_service(self, service_id): # Deleting the service created in this method resp, _ = self.client.delete_service(service_id) self.assertEqual(204, resp.status) # Checking whether service is deleted successfully self.assertRaises(exceptions.NotFound, self.client.get_service, service_id) @attr(type='smoke') def test_create_get_delete_service(self): # GET Service # Creating a Service name = data_utils.rand_name('service-') type = data_utils.rand_name('type--') description = data_utils.rand_name('description-') resp, service_data = self.client.create_service( name, type, description=description) self.assertFalse(service_data['id'] is None) self.addCleanup(self._del_service, service_data['id']) self.assertEqual(200, resp.status) # Verifying response body of create service self.assertIn('id', service_data) self.assertIn('name', service_data) self.assertEqual(name, service_data['name']) self.assertIn('type', service_data) self.assertEqual(type, service_data['type']) self.assertIn('description', service_data) self.assertEqual(description, service_data['description']) # Get service resp, fetched_service = self.client.get_service(service_data['id']) self.assertEqual(200, resp.status) # verifying the existence of service created self.assertIn('id', fetched_service) self.assertEqual(fetched_service['id'], service_data['id']) self.assertIn('name', fetched_service) self.assertEqual(fetched_service['name'], service_data['name']) self.assertIn('type', fetched_service) self.assertEqual(fetched_service['type'], service_data['type']) self.assertIn('description', fetched_service) self.assertEqual(fetched_service['description'], service_data['description']) @attr(type='gate') def test_create_service_without_description(self): # Create a service only with name and type name = data_utils.rand_name('service-') type = data_utils.rand_name('type--') resp, service = self.client.create_service(name, type) self.assertIn('id', service) self.assertTrue('200', resp['status']) self.addCleanup(self._del_service, service['id']) self.assertIn('name', service) self.assertEqual(name, service['name']) self.assertIn('type', service) self.assertEqual(type, service['type']) @attr(type='smoke') def test_list_services(self): # Create, List, Verify and Delete Services services = [] for _ in moves.xrange(3): name = data_utils.rand_name('service-') type = data_utils.rand_name('type--') description = data_utils.rand_name('description-') resp, service = self.client.create_service( name, type, description=description) services.append(service) service_ids = map(lambda x: x['id'], services) def delete_services(): for service_id in service_ids: self.client.delete_service(service_id) self.addCleanup(delete_services) # List and Verify Services resp, body = self.client.list_services() self.assertEqual(200, resp.status) found = [service for service in body if service['id'] in service_ids] self.assertEqual(len(found), len(services), 'Services not found') class ServicesTestXML(ServicesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/identity/base.py0000664000175000017500000001670112332757070023451 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import auth from tempest import clients from tempest.common.utils import data_utils from tempest import config import tempest.test CONF = config.CONF class BaseIdentityAdminTest(tempest.test.BaseTestCase): @classmethod def setUpClass(cls): super(BaseIdentityAdminTest, cls).setUpClass() cls.os_adm = clients.AdminManager(interface=cls._interface) cls.os = clients.Manager(interface=cls._interface) @classmethod def disable_user(cls, user_name): user = cls.get_user_by_name(user_name) cls.client.enable_disable_user(user['id'], False) @classmethod def disable_tenant(cls, tenant_name): tenant = cls.get_tenant_by_name(tenant_name) cls.client.update_tenant(tenant['id'], enabled=False) @classmethod def get_user_by_name(cls, name): _, users = cls.client.get_users() user = [u for u in users if u['name'] == name] if len(user) > 0: return user[0] @classmethod def get_tenant_by_name(cls, name): try: _, tenants = cls.client.list_tenants() except AttributeError: _, tenants = cls.client.list_projects() tenant = [t for t in tenants if t['name'] == name] if len(tenant) > 0: return tenant[0] @classmethod def get_role_by_name(cls, name): _, roles = cls.client.list_roles() role = [r for r in roles if r['name'] == name] if len(role) > 0: return role[0] class BaseIdentityV2AdminTest(BaseIdentityAdminTest): @classmethod def setUpClass(cls): if not CONF.identity_feature_enabled.api_v2: raise cls.skipException("Identity api v2 is not enabled") super(BaseIdentityV2AdminTest, cls).setUpClass() cls.client = cls.os_adm.identity_client cls.token_client = cls.os_adm.token_client if not cls.client.has_admin_extensions(): raise cls.skipException("Admin extensions disabled") cls.data = DataGenerator(cls.client) cls.non_admin_client = cls.os.identity_client @classmethod def tearDownClass(cls): cls.data.teardown_all() super(BaseIdentityV2AdminTest, cls).tearDownClass() class BaseIdentityV3AdminTest(BaseIdentityAdminTest): @classmethod def setUpClass(cls): if not CONF.identity_feature_enabled.api_v3: raise cls.skipException("Identity api v3 is not enabled") super(BaseIdentityV3AdminTest, cls).setUpClass() cls.client = cls.os_adm.identity_v3_client cls.token = cls.os_adm.token_v3_client cls.endpoints_client = cls.os_adm.endpoints_client cls.data = DataGenerator(cls.client) cls.non_admin_client = cls.os.identity_v3_client cls.service_client = cls.os_adm.service_client cls.policy_client = cls.os_adm.policy_client cls.creds_client = cls.os_adm.credentials_client cls.non_admin_client = cls.os.identity_v3_client @classmethod def tearDownClass(cls): cls.data.teardown_all() super(BaseIdentityV3AdminTest, cls).tearDownClass() class DataGenerator(object): def __init__(self, client): self.client = client self.users = [] self.tenants = [] self.roles = [] self.role_name = None self.v3_users = [] self.projects = [] self.v3_roles = [] @property def test_credentials(self): return auth.get_credentials(username=self.test_user, user_id=self.user['id'], password=self.test_password, tenant_name=self.test_tenant, tenant_id=self.tenant['id']) def setup_test_user(self): """Set up a test user.""" self.setup_test_tenant() self.test_user = data_utils.rand_name('test_user_') self.test_password = data_utils.rand_name('pass_') self.test_email = self.test_user + '@testmail.tm' resp, self.user = self.client.create_user(self.test_user, self.test_password, self.tenant['id'], self.test_email) self.users.append(self.user) def setup_test_tenant(self): """Set up a test tenant.""" self.test_tenant = data_utils.rand_name('test_tenant_') self.test_description = data_utils.rand_name('desc_') resp, self.tenant = self.client.create_tenant( name=self.test_tenant, description=self.test_description) self.tenants.append(self.tenant) def setup_test_role(self): """Set up a test role.""" self.test_role = data_utils.rand_name('role') resp, self.role = self.client.create_role(self.test_role) self.roles.append(self.role) def setup_test_v3_user(self): """Set up a test v3 user.""" self.setup_test_project() self.test_user = data_utils.rand_name('test_user_') self.test_password = data_utils.rand_name('pass_') self.test_email = self.test_user + '@testmail.tm' resp, self.v3_user = self.client.create_user( self.test_user, password=self.test_password, project_id=self.project['id'], email=self.test_email) self.v3_users.append(self.v3_user) def setup_test_project(self): """Set up a test project.""" self.test_project = data_utils.rand_name('test_project_') self.test_description = data_utils.rand_name('desc_') resp, self.project = self.client.create_project( name=self.test_project, description=self.test_description) self.projects.append(self.project) def setup_test_v3_role(self): """Set up a test v3 role.""" self.test_role = data_utils.rand_name('role') resp, self.v3_role = self.client.create_role(self.test_role) self.v3_roles.append(self.v3_role) def teardown_all(self): for user in self.users: self.client.delete_user(user['id']) for tenant in self.tenants: self.client.delete_tenant(tenant['id']) for role in self.roles: self.client.delete_role(role['id']) for v3_user in self.v3_users: self.client.delete_user(v3_user['id']) for v3_project in self.projects: self.client.delete_project(v3_project['id']) for v3_role in self.v3_roles: self.client.delete_role(v3_role['id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/__init__.py0000664000175000017500000000000012332757070022426 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/telemetry/0000775000175000017500000000000012332757136022344 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/telemetry/__init__.py0000664000175000017500000000000012332757070024440 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/telemetry/test_telemetry_alarming_api.py0000664000175000017500000001201012332757070030461 0ustar chuckchuck00000000000000# 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 tempest.api.telemetry import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class TelemetryAlarmingAPITestJSON(base.BaseTelemetryTest): _interface = 'json' @classmethod def setUpClass(cls): super(TelemetryAlarmingAPITestJSON, cls).setUpClass() cls.rule = {'meter_name': 'cpu_util', 'comparison_operator': 'gt', 'threshold': 80.0, 'period': 70} for i in range(2): cls.create_alarm(threshold_rule=cls.rule) @attr(type="gate") def test_alarm_list(self): # List alarms resp, alarm_list = self.telemetry_client.list_alarms() self.assertEqual(200, resp.status) # Verify created alarm in the list fetched_ids = [a['alarm_id'] for a in alarm_list] missing_alarms = [a for a in self.alarm_ids if a not in fetched_ids] self.assertEqual(0, len(missing_alarms), "Failed to find the following created alarm(s)" " in a fetched list: %s" % ', '.join(str(a) for a in missing_alarms)) @attr(type="gate") def test_create_update_get_delete_alarm(self): # Create an alarm alarm_name = data_utils.rand_name('telemetry_alarm') resp, body = self.telemetry_client.create_alarm( name=alarm_name, type='threshold', threshold_rule=self.rule) self.assertEqual(201, resp.status) self.assertEqual(alarm_name, body['name']) alarm_id = body['alarm_id'] self.assertDictContainsSubset(self.rule, body['threshold_rule']) # Update alarm with new rule and new name new_rule = {'meter_name': 'cpu', 'comparison_operator': 'eq', 'threshold': 70.0, 'period': 60} alarm_name = data_utils.rand_name('telemetry-alarm-update') resp, body = self.telemetry_client.update_alarm( alarm_id, threshold_rule=new_rule, name=alarm_name, type='threshold') self.assertEqual(200, resp.status) self.assertEqual(alarm_name, body['name']) self.assertDictContainsSubset(new_rule, body['threshold_rule']) # Get and verify details of an alarm after update resp, body = self.telemetry_client.get_alarm(alarm_id) self.assertEqual(200, resp.status) self.assertEqual(alarm_name, body['name']) self.assertDictContainsSubset(new_rule, body['threshold_rule']) # Delete alarm and verify if deleted resp, _ = self.telemetry_client.delete_alarm(alarm_id) self.assertEqual(204, resp.status) self.assertRaises(exceptions.NotFound, self.telemetry_client.get_alarm, alarm_id) @attr(type="gate") def test_set_get_alarm_state(self): alarm_states = ['ok', 'alarm', 'insufficient data'] _, alarm = self.create_alarm(threshold_rule=self.rule) # Set alarm state and verify new_state =\ [elem for elem in alarm_states if elem != alarm['state']][0] resp, state = self.telemetry_client.alarm_set_state(alarm['alarm_id'], new_state) self.assertEqual(200, resp.status) self.assertEqual(new_state, state) # Get alarm state and verify resp, state = self.telemetry_client.alarm_get_state(alarm['alarm_id']) self.assertEqual(200, resp.status) self.assertEqual(new_state, state) @attr(type="gate") def test_create_delete_alarm_with_combination_rule(self): rule = {"alarm_ids": self.alarm_ids, "operator": "or"} # Verifies alarm create alarm_name = data_utils.rand_name('combination_alarm') resp, body = self.telemetry_client.create_alarm(name=alarm_name, combination_rule=rule, type='combination') self.assertEqual(201, resp.status) self.assertEqual(alarm_name, body['name']) alarm_id = body['alarm_id'] self.assertDictContainsSubset(rule, body['combination_rule']) # Verify alarm delete resp, _ = self.telemetry_client.delete_alarm(alarm_id) self.assertEqual(204, resp.status) self.assertRaises(exceptions.NotFound, self.telemetry_client.get_alarm, alarm_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/telemetry/base.py0000664000175000017500000000340212332757070023624 0ustar chuckchuck00000000000000# 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 tempest.common.utils import data_utils from tempest import config from tempest import exceptions import tempest.test CONF = config.CONF class BaseTelemetryTest(tempest.test.BaseTestCase): """Base test case class for all Telemetry API tests.""" @classmethod def setUpClass(cls): if not CONF.service_available.ceilometer: raise cls.skipException("Ceilometer support is required") super(BaseTelemetryTest, cls).setUpClass() os = cls.get_client_manager() cls.telemetry_client = os.telemetry_client cls.alarm_ids = [] @classmethod def create_alarm(cls, **kwargs): resp, body = cls.telemetry_client.create_alarm( name=data_utils.rand_name('telemetry_alarm'), type='threshold', **kwargs) if resp['status'] == '201': cls.alarm_ids.append(body['alarm_id']) return resp, body @classmethod def tearDownClass(cls): for alarm_id in cls.alarm_ids: try: cls.telemetry_client.delete_alarm(alarm_id) except exceptions.NotFound: pass cls.clear_isolated_creds() super(BaseTelemetryTest, cls).tearDownClass() tempest-2014.1.dev4108.gf22b6cc/tempest/api/README.rst0000664000175000017500000000335112332757070022020 0ustar chuckchuck00000000000000Tempest Field Guide to API tests ================================ What are these tests? --------------------- One of Tempest's prime function is to ensure that your OpenStack cloud works with the OpenStack API as documented. The current largest portion of Tempest code is devoted to test cases that do exactly this. It's also important to test not only the expected positive path on APIs, but also to provide them with invalid data to ensure they fail in expected and documented ways. Over the course of the OpenStack project Tempest has discovered many fundamental bugs by doing just this. In order for some APIs to return meaningful results, there must be enough data in the system. This means these tests might start by spinning up a server, image, etc, then operating on it. Why are these tests in tempest? ------------------------------- This is one of the core missions for the Tempest project, and where it started. Many people use this bit of function in Tempest to ensure their clouds haven't broken the OpenStack API. It could be argued that some of the negative testing could be done back in the projects themselves, and we might evolve there over time, but currently in the OpenStack gate this is a fundamentally important place to keep things. Scope of these tests -------------------- API tests should always use the Tempest implementation of the OpenStack API, as we want to ensure that bugs aren't hidden by the official clients. They should test specific API calls, and can build up complex state if it's needed for the API call to be meaningful. They should send not only good data, but bad data at the API and look for error codes. They should all be able to be run on their own, not depending on the state created by a previous test. tempest-2014.1.dev4108.gf22b6cc/tempest/api/queuing/0000775000175000017500000000000012332757136022007 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/queuing/__init__.py0000664000175000017500000000000012332757070024103 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/queuing/test_queues.py0000664000175000017500000000337212332757070024731 0ustar chuckchuck00000000000000# Copyright (c) 2014 Rackspace, 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 logging from tempest.api.queuing import base from tempest.common.utils import data_utils from tempest import test LOG = logging.getLogger(__name__) class TestQueues(base.BaseQueuingTest): @test.attr(type='smoke') def test_create_queue(self): # Create Queue queue_name = data_utils.rand_name('test-') resp, body = self.create_queue(queue_name) self.addCleanup(self.client.delete_queue, queue_name) self.assertEqual('201', resp['status']) self.assertEqual('', body) class TestManageQueue(base.BaseQueuingTest): _interface = 'json' @classmethod def setUpClass(cls): super(TestManageQueue, cls).setUpClass() cls.queue_name = data_utils.rand_name('Queues-Test') # Create Queue cls.client.create_queue(cls.queue_name) @test.attr(type='smoke') def test_delete_queue(self): # Delete Queue resp, body = self.delete_queue(self.queue_name) self.assertEqual('204', resp['status']) self.assertEqual('', body) @classmethod def tearDownClass(cls): cls.client.delete_queue(cls.queue_name) super(TestManageQueue, cls).tearDownClass() tempest-2014.1.dev4108.gf22b6cc/tempest/api/queuing/base.py0000664000175000017500000000331312332757070023270 0ustar chuckchuck00000000000000# Copyright (c) 2014 Rackspace, 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 tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseQueuingTest(test.BaseTestCase): """ Base class for the Queuing tests that use the Tempest Marconi REST client It is assumed that the following option is defined in the [service_available] section of etc/tempest.conf queuing as True """ @classmethod def setUpClass(cls): super(BaseQueuingTest, cls).setUpClass() if not CONF.service_available.marconi: raise cls.skipException("Marconi support is required") os = cls.get_client_manager() cls.queuing_cfg = CONF.queuing cls.client = os.queuing_client @classmethod def create_queue(cls, queue_name): """Wrapper utility that returns a test queue.""" resp, body = cls.client.create_queue(queue_name) return resp, body @classmethod def delete_queue(cls, queue_name): """Wrapper utility that returns a test queue.""" resp, body = cls.client.delete_queue(queue_name) return resp, body tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/0000775000175000017500000000000012332757136022006 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/0000775000175000017500000000000012332757136022336 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/test_version.py0000664000175000017500000000177312332757070025441 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class VersionV3Test(base.BaseV3ComputeTest): @test.attr(type='gate') def test_version(self): # Get version information resp, version = self.version_client.get_version() self.assertEqual(200, resp.status) self.assertIn("id", version) self.assertEqual("v3.0", version["id"]) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/0000775000175000017500000000000012332757136024027 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_metadata_negative.py0000664000175000017500000001527012332757070032472 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ServerMetadataV3NegativeTest(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(ServerMetadataV3NegativeTest, cls).setUpClass() cls.client = cls.servers_client cls.quotas = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE') cls.server_id = server['id'] @test.skip_because(bug="1273948") @test.attr(type=['gate', 'negative']) def test_server_create_metadata_key_too_long(self): # Attempt to start a server with a meta-data key that is > 255 # characters # Tryset_server_metadata_item a few values for sz in [256, 257, 511, 1023]: key = "k" * sz meta = {key: 'data1'} self.assertRaises(exceptions.BadRequest, self.create_test_server, meta=meta) # no teardown - all creates should fail @test.attr(type=['negative', 'gate']) def test_create_server_metadata_blank_key(self): # Blank key should trigger an error. meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.create_test_server, meta=meta) @test.attr(type=['negative', 'gate']) def test_server_metadata_non_existent_server(self): # GET on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_server_metadata_item, non_existent_server_id, 'test2') @test.attr(type=['negative', 'gate']) def test_list_server_metadata_non_existent_server(self): # List metadata on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.list_server_metadata, non_existent_server_id) @test.attr(type=['negative', 'gate']) def test_wrong_key_passed_in_body(self): # Raise BadRequest if key in uri does not match # the key passed in body. meta = {'testkey': 'testvalue'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata_item, self.server_id, 'key', meta) @test.attr(type=['negative', 'gate']) def test_set_metadata_non_existent_server(self): # Set metadata on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() meta = {'meta1': 'data1'} self.assertRaises(exceptions.NotFound, self.client.set_server_metadata, non_existent_server_id, meta) @test.attr(type=['negative', 'gate']) def test_update_metadata_non_existent_server(self): # An update should not happen for a non-existent server non_existent_server_id = data_utils.rand_uuid() meta = {'key1': 'value1', 'key2': 'value2'} self.assertRaises(exceptions.NotFound, self.client.update_server_metadata, non_existent_server_id, meta) @test.attr(type=['negative', 'gate']) def test_update_metadata_with_blank_key(self): # Blank key should trigger an error meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.update_server_metadata, self.server_id, meta=meta) @test.attr(type=['negative', 'gate']) def test_delete_metadata_non_existent_server(self): # Should not be able to delete metadata item from a non-existent server non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.delete_server_metadata_item, non_existent_server_id, 'd') @test.attr(type=['negative', 'gate']) def test_metadata_items_limit(self): # Raise a 413 OverLimit exception while exceeding metadata items limit # for tenant. _, quota_set = self.quotas.get_quota_set(self.tenant_id) quota_metadata = quota_set['metadata_items'] req_metadata = {} for num in range(1, quota_metadata + 2): req_metadata['key' + str(num)] = 'val' + str(num) self.assertRaises(exceptions.OverLimit, self.client.set_server_metadata, self.server_id, req_metadata) # Raise a 413 OverLimit exception while exceeding metadata items limit # for tenant (update). self.assertRaises(exceptions.OverLimit, self.client.update_server_metadata, self.server_id, req_metadata) @test.attr(type=['negative', 'gate']) def test_set_server_metadata_blank_key(self): # Raise a bad request error for blank key. # set_server_metadata will replace all metadata with new value meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata, self.server_id, meta=meta) @test.attr(type=['negative', 'gate']) def test_set_server_metadata_missing_metadata(self): # Raise a bad request error for a missing metadata field # set_server_metadata will replace all metadata with new value meta = {'meta1': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata, self.server_id, meta=meta, no_metadata_field=True) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_attach_interfaces.py0000664000175000017500000001407012332757070031106 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest import exceptions from tempest.test import attr import time CONF = config.CONF class AttachInterfacesV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): if not CONF.service_available.neutron: raise cls.skipException("Neutron is required") # This test class requires network and subnet cls.set_network_resources(network=True, subnet=True) super(AttachInterfacesV3Test, cls).setUpClass() cls.client = cls.interfaces_client def _check_interface(self, iface, port_id=None, network_id=None, fixed_ip=None): self.assertIn('port_state', iface) if port_id: self.assertEqual(iface['port_id'], port_id) if network_id: self.assertEqual(iface['net_id'], network_id) if fixed_ip: self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip) def _create_server_get_interfaces(self): resp, server = self.create_test_server(wait_until='ACTIVE') resp, ifs = self.client.list_interfaces(server['id']) self.assertEqual(200, resp.status) resp, body = self.client.wait_for_interface_status( server['id'], ifs[0]['port_id'], 'ACTIVE') ifs[0]['port_state'] = body['port_state'] return server, ifs def _test_create_interface(self, server): resp, iface = self.client.create_interface(server['id']) self.assertEqual(200, resp.status) resp, iface = self.client.wait_for_interface_status( server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface) return iface def _test_create_interface_by_network_id(self, server, ifs): network_id = ifs[0]['net_id'] resp, iface = self.client.create_interface(server['id'], network_id=network_id) self.assertEqual(200, resp.status) resp, iface = self.client.wait_for_interface_status( server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface, network_id=network_id) return iface def _test_show_interface(self, server, ifs): iface = ifs[0] resp, _iface = self.client.show_interface(server['id'], iface['port_id']) self.assertEqual(200, resp.status) self.assertEqual(iface, _iface) def _test_delete_interface(self, server, ifs): # NOTE(danms): delete not the first or last, but one in the middle iface = ifs[1] resp, _ = self.client.delete_interface(server['id'], iface['port_id']) self.assertEqual(202, resp.status) _ifs = self.client.list_interfaces(server['id'])[1] start = int(time.time()) while len(ifs) == len(_ifs): time.sleep(self.build_interval) _ifs = self.client.list_interfaces(server['id'])[1] timed_out = int(time.time()) - start >= self.build_timeout if len(ifs) == len(_ifs) and timed_out: message = ('Failed to delete interface within ' 'the required time: %s sec.' % self.build_timeout) raise exceptions.TimeoutException(message) self.assertNotIn(iface['port_id'], [i['port_id'] for i in _ifs]) return _ifs def _compare_iface_list(self, list1, list2): # NOTE(danms): port_state will likely have changed, so just # confirm the port_ids are the same at least list1 = [x['port_id'] for x in list1] list2 = [x['port_id'] for x in list2] self.assertEqual(sorted(list1), sorted(list2)) @attr(type='smoke') def test_create_list_show_delete_interfaces(self): server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertTrue(interface_count > 0) self._check_interface(ifs[0]) iface = self._test_create_interface(server) ifs.append(iface) iface = self._test_create_interface_by_network_id(server, ifs) ifs.append(iface) resp, _ifs = self.client.list_interfaces(server['id']) self._compare_iface_list(ifs, _ifs) self._test_show_interface(server, ifs) _ifs = self._test_delete_interface(server, ifs) self.assertEqual(len(ifs) - 1, len(_ifs)) @attr(type='smoke') def test_add_remove_fixed_ip(self): # Add and Remove the fixed IP to server. server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertGreater(interface_count, 0) self._check_interface(ifs[0]) network_id = ifs[0]['net_id'] resp, body = self.client.add_fixed_ip(server['id'], network_id) self.assertEqual(202, resp.status) server_resp, server_detail = self.servers_client.get_server( server['id']) # Get the Fixed IP from server. fixed_ip = None for ip_set in server_detail['addresses']: for ip in server_detail['addresses'][ip_set]: if ip['type'] == 'fixed': fixed_ip = ip['addr'] break if fixed_ip is not None: break # Remove the fixed IP from server. resp, body = self.client.remove_fixed_ip(server['id'], fixed_ip) self.assertEqual(202, resp.status) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_create_server.py0000664000175000017500000002104612332757070030271 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import netaddr import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config from tempest import test CONF = config.CONF class ServersV3Test(base.BaseV3ComputeTest): disk_config = 'AUTO' @classmethod def setUpClass(cls): cls.prepare_instance_network() super(ServersV3Test, cls).setUpClass() cls.meta = {'hello': 'world'} cls.accessIPv4 = '1.1.1.1' cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2' cls.name = data_utils.rand_name('server') file_contents = 'This is a test file.' personality = [{'path': '/test.txt', 'contents': base64.b64encode(file_contents)}] cls.client = cls.servers_client cli_resp = cls.create_test_server(name=cls.name, meta=cls.meta, access_ip_v4=cls.accessIPv4, access_ip_v6=cls.accessIPv6, personality=personality, disk_config=cls.disk_config) cls.resp, cls.server_initial = cli_resp cls.password = cls.server_initial['admin_password'] cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE') resp, cls.server = cls.client.get_server(cls.server_initial['id']) @test.attr(type='smoke') def test_verify_server_details(self): # Verify the specified server attributes are set correctly self.assertEqual(self.accessIPv4, self.server['os-access-ips:access_ip_v4']) # NOTE(maurosr): See http://tools.ietf.org/html/rfc5952 (section 4) # Here we compare directly with the canonicalized format. self.assertEqual(self.server['os-access-ips:access_ip_v6'], str(netaddr.IPAddress(self.accessIPv6))) self.assertEqual(self.name, self.server['name']) self.assertEqual(self.image_ref, self.server['image']['id']) self.assertEqual(self.flavor_ref, self.server['flavor']['id']) self.assertEqual(self.meta, self.server['metadata']) @test.attr(type='smoke') def test_list_servers(self): # The created server should be in the list of all servers resp, body = self.client.list_servers() servers = body['servers'] found = any([i for i in servers if i['id'] == self.server['id']]) self.assertTrue(found) @test.attr(type='smoke') def test_list_servers_with_detail(self): # The created server should be in the detailed list of all servers resp, body = self.client.list_servers_with_detail() servers = body['servers'] found = any([i for i in servers if i['id'] == self.server['id']]) self.assertTrue(found) @testtools.skipUnless(CONF.compute.run_ssh, 'Instance validation tests are disabled.') @test.attr(type='gate') def test_verify_created_server_vcpus(self): # Verify that the number of vcpus reported by the instance matches # the amount stated by the flavor resp, flavor = self.flavors_client.get_flavor_details(self.flavor_ref) linux_client = remote_client.RemoteClient(self.server, self.ssh_user, self.password) self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus()) @testtools.skipUnless(CONF.compute.run_ssh, 'Instance validation tests are disabled.') @test.attr(type='gate') def test_host_name_is_same_as_server_name(self): # Verify the instance host name is the same as the server name linux_client = remote_client.RemoteClient(self.server, self.ssh_user, self.password) self.assertTrue(linux_client.hostname_equals_servername(self.name)) class ServersWithSpecificFlavorV3Test(base.BaseV3ComputeAdminTest): disk_config = 'AUTO' @classmethod def setUpClass(cls): cls.prepare_instance_network() super(ServersWithSpecificFlavorV3Test, cls).setUpClass() cls.client = cls.servers_client cls.flavor_client = cls.flavors_admin_client @testtools.skipUnless(CONF.compute.run_ssh, 'Instance validation tests are disabled.') @test.attr(type='gate') def test_verify_created_server_ephemeral_disk(self): # Verify that the ephemeral disk is created when creating server def create_flavor_with_extra_specs(): flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor') flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000) ram = 512 vcpus = 1 disk = 10 # Create a flavor with extra specs resp, flavor = (self.flavor_client. create_flavor(flavor_with_eph_disk_name, ram, vcpus, disk, flavor_with_eph_disk_id, ephemeral=1, rxtx=1)) self.addCleanup(flavor_clean_up, flavor['id']) self.assertEqual(201, resp.status) return flavor['id'] def create_flavor_without_extra_specs(): flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor') flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000) ram = 512 vcpus = 1 disk = 10 # Create a flavor without extra specs resp, flavor = (self.flavor_client. create_flavor(flavor_no_eph_disk_name, ram, vcpus, disk, flavor_no_eph_disk_id)) self.addCleanup(flavor_clean_up, flavor['id']) self.assertEqual(201, resp.status) return flavor['id'] def flavor_clean_up(flavor_id): resp, body = self.flavor_client.delete_flavor(flavor_id) self.assertEqual(resp.status, 204) self.flavor_client.wait_for_resource_deletion(flavor_id) flavor_with_eph_disk_id = create_flavor_with_extra_specs() flavor_no_eph_disk_id = create_flavor_without_extra_specs() admin_pass = self.image_ssh_password resp, server_no_eph_disk = (self.create_test_server( wait_until='ACTIVE', adminPass=admin_pass, flavor=flavor_no_eph_disk_id)) resp, server_with_eph_disk = (self.create_test_server( wait_until='ACTIVE', adminPass=admin_pass, flavor=flavor_with_eph_disk_id)) # Get partition number of server without extra specs. _, server_no_eph_disk = self.client.get_server( server_no_eph_disk['id']) linux_client = remote_client.RemoteClient(server_no_eph_disk, self.ssh_user, admin_pass) partition_num = len(linux_client.get_partitions().split('\n')) _, server_with_eph_disk = self.client.get_server( server_with_eph_disk['id']) linux_client = remote_client.RemoteClient(server_with_eph_disk, self.ssh_user, admin_pass) partition_num_emph = len(linux_client.get_partitions().split('\n')) self.assertEqual(partition_num + 1, partition_num_emph) class ServersV3TestManualDisk(ServersV3Test): disk_config = 'MANUAL' @classmethod def setUpClass(cls): if not CONF.compute_feature_enabled.disk_config: msg = "DiskConfig extension not enabled." raise cls.skipException(msg) super(ServersV3TestManualDisk, cls).setUpClass() tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_addresses_negative.py0000664000175000017500000000355512332757070032672 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ServerAddressesV3NegativeTest(base.BaseV3ComputeTest): _interface = 'json' @classmethod def setUpClass(cls): # This test module might use a network and a subnet cls.set_network_resources(network=True, subnet=True) super(ServerAddressesV3NegativeTest, cls).setUpClass() cls.client = cls.servers_client resp, cls.server = cls.create_test_server(wait_until='ACTIVE') @test.attr(type=['negative', 'gate']) def test_list_server_addresses_nonexistent_server_id(self): # List addresses request should fail if server id not in system non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.list_addresses, non_existent_server_id) @test.attr(type=['negative', 'gate']) def test_list_server_addresses_by_network_neg(self): # List addresses by network should fail if network name not valid self.assertRaises(exceptions.NotFound, self.client.list_addresses_by_network, self.server['id'], 'invalid') tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_rescue_negative.py0000664000175000017500000001355112332757070032200 0ustar chuckchuck00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ServerRescueNegativeV3Test(base.BaseV3ComputeTest): @classmethod @test.safe_setup def setUpClass(cls): super(ServerRescueNegativeV3Test, cls).setUpClass() cls.device = 'vdf' # Create a volume and wait for it to become ready for attach resp, cls.volume = cls.volumes_client.create_volume( 1, display_name=data_utils.rand_name(cls.__name__ + '_volume')) cls.volumes_client.wait_for_volume_status( cls.volume['id'], 'available') # Server for negative tests resp, server = cls.create_test_server(wait_until='BUILD') resp, resc_server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] cls.password = server['admin_password'] cls.rescue_id = resc_server['id'] cls.rescue_password = resc_server['admin_password'] cls.servers_client.rescue_server( cls.rescue_id, admin_password=cls.rescue_password) cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE') cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE') @classmethod def tearDownClass(cls): cls.delete_volume(cls.volume['id']) super(ServerRescueNegativeV3Test, cls).tearDownClass() def _detach(self, server_id, volume_id): self.servers_client.detach_volume(server_id, volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') def _unrescue(self, server_id): resp, body = self.servers_client.unrescue_server(server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') def _unpause(self, server_id): resp, body = self.servers_client.unpause_server(server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type=['negative', 'gate']) def test_rescue_paused_instance(self): # Rescue a paused server resp, body = self.servers_client.pause_server( self.server_id) self.addCleanup(self._unpause, self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'PAUSED') self.assertRaises(exceptions.Conflict, self.servers_client.rescue_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_rescued_vm_reboot(self): self.assertRaises(exceptions.Conflict, self.servers_client.reboot, self.rescue_id, 'HARD') @test.attr(type=['negative', 'gate']) def test_rescue_non_existent_server(self): # Rescue a non-existing server self.assertRaises(exceptions.NotFound, self.servers_client.rescue_server, data_utils.rand_uuid()) @test.attr(type=['negative', 'gate']) def test_rescued_vm_rebuild(self): self.assertRaises(exceptions.Conflict, self.servers_client.rebuild, self.rescue_id, self.image_ref_alt) @test.attr(type=['negative', 'gate']) def test_rescued_vm_attach_volume(self): # Rescue the server self.servers_client.rescue_server(self.server_id, admin_password=self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') self.addCleanup(self._unrescue, self.server_id) # Attach the volume to the server self.assertRaises(exceptions.Conflict, self.servers_client.attach_volume, self.server_id, self.volume['id'], device='/dev/%s' % self.device) @test.attr(type=['negative', 'gate']) def test_rescued_vm_detach_volume(self): # Attach the volume to the server self.servers_client.attach_volume(self.server_id, self.volume['id'], device='/dev/%s' % self.device) self.volumes_client.wait_for_volume_status(self.volume['id'], 'in-use') # Rescue the server self.servers_client.rescue_server(self.server_id, admin_password=self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') # addCleanup is a LIFO queue self.addCleanup(self._detach, self.server_id, self.volume['id']) self.addCleanup(self._unrescue, self.server_id) # Detach the volume from the server expecting failure self.assertRaises(exceptions.Conflict, self.servers_client.detach_volume, self.server_id, self.volume['id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_password.py0000664000175000017500000000244412332757070030671 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class ServerPasswordV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(ServerPasswordV3Test, cls).setUpClass() cls.client = cls.servers_client resp, cls.server = cls.create_test_server(wait_until="ACTIVE") @test.attr(type='gate') def test_get_server_password(self): resp, body = self.client.get_password(self.server['id']) self.assertEqual(200, resp.status) @test.attr(type='gate') def test_delete_server_password(self): resp, body = self.client.delete_password(self.server['id']) self.assertEqual(204, resp.status) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_actions.py0000664000175000017500000004725712332757070030502 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ServerActionsV3Test(base.BaseV3ComputeTest): run_ssh = CONF.compute.run_ssh def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ServerActionsV3Test, self).setUp() # Check if the server is in a clean state after test try: self.client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: # Rebuild server if something happened to it during a test self.__class__.server_id = self.rebuild_server(self.server_id) def tearDown(self): _, server = self.client.get_server(self.server_id) self.assertEqual(self.image_ref, server['image']['id']) self.server_check_teardown() super(ServerActionsV3Test, self).tearDown() @classmethod def setUpClass(cls): cls.prepare_instance_network() super(ServerActionsV3Test, cls).setUpClass() cls.client = cls.servers_client cls.server_id = cls.rebuild_server(None) @testtools.skipUnless(CONF.compute_feature_enabled.change_password, 'Change password not available.') @test.attr(type='gate') def test_change_server_password(self): # The server's password should be set to the provided password new_password = 'Newpass1234' resp, body = self.client.change_password(self.server_id, new_password) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Verify that the user can authenticate with the new password resp, server = self.client.get_server(self.server_id) linux_client = remote_client.RemoteClient(server, self.ssh_user, new_password) linux_client.validate_authentication() @test.attr(type='smoke') def test_reboot_server_hard(self): # The server should be power cycled if self.run_ssh: # Get the time the server was last rebooted, resp, server = self.client.get_server(self.server_id) linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) boot_time = linux_client.get_boot_time() resp, body = self.client.reboot(self.server_id, 'HARD') self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Log in and verify the boot time has changed linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) new_boot_time = linux_client.get_boot_time() self.assertGreater(new_boot_time, boot_time) @test.skip_because(bug="1014647") @test.attr(type='smoke') def test_reboot_server_soft(self): # The server should be signaled to reboot gracefully if self.run_ssh: # Get the time the server was last rebooted, resp, server = self.client.get_server(self.server_id) linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) boot_time = linux_client.get_boot_time() resp, body = self.client.reboot(self.server_id, 'SOFT') self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Log in and verify the boot time has changed linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) new_boot_time = linux_client.get_boot_time() self.assertGreater(new_boot_time, boot_time) @test.attr(type='smoke') def test_rebuild_server(self): # The server should be rebuilt using the provided image and data meta = {'rebuild': 'server'} new_name = data_utils.rand_name('server') password = 'rebuildPassw0rd' resp, rebuilt_server = self.client.rebuild(self.server_id, self.image_ref_alt, name=new_name, metadata=meta, admin_password=password) # Verify the properties in the initial response are correct self.assertEqual(self.server_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) # Verify the server properties after the rebuild completes self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE') resp, server = self.client.get_server(rebuilt_server['id']) rebuilt_image_id = server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(new_name, server['name']) if self.run_ssh: # Verify that the user can authenticate with the provided password linux_client = remote_client.RemoteClient(server, self.ssh_user, password) linux_client.validate_authentication() if self.image_ref_alt != self.image_ref: self.client.rebuild(self.server_id, self.image_ref) @test.attr(type='gate') def test_rebuild_server_in_stop_state(self): # The server in stop state should be rebuilt using the provided # image and remain in SHUTOFF state resp, server = self.client.get_server(self.server_id) old_image = server['image']['id'] new_image = self.image_ref_alt \ if old_image == self.image_ref else self.image_ref resp, server = self.client.stop(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SHUTOFF') resp, rebuilt_server = self.client.rebuild(self.server_id, new_image) # Verify the properties in the initial response are correct self.assertEqual(self.server_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertEqual(new_image, rebuilt_image_id) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) # Verify the server properties after the rebuild completes self.client.wait_for_server_status(rebuilt_server['id'], 'SHUTOFF') resp, server = self.client.get_server(rebuilt_server['id']) rebuilt_image_id = server['image']['id'] self.assertEqual(new_image, rebuilt_image_id) # Restore to the original image (The tearDown will test it again) if self.image_ref_alt != self.image_ref: self.client.rebuild(self.server_id, old_image) self.client.wait_for_server_status(self.server_id, 'SHUTOFF') self.client.start(self.server_id) def _detect_server_image_flavor(self, server_id): # Detects the current server image flavor ref. resp, server = self.client.get_server(server_id) current_flavor = server['flavor']['id'] new_flavor_ref = self.flavor_ref_alt \ if current_flavor == self.flavor_ref else self.flavor_ref return current_flavor, new_flavor_ref @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='smoke') def test_resize_server_confirm(self): # The server's RAM and disk space should be modified to that of # the provided flavor previous_flavor_ref, new_flavor_ref = \ self._detect_server_image_flavor(self.server_id) resp, server = self.client.resize(self.server_id, new_flavor_ref) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.confirm_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, server = self.client.get_server(self.server_id) self.assertEqual(new_flavor_ref, server['flavor']['id']) @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_resize_server_revert(self): # The server's RAM and disk space should return to its original # values after a resize is reverted previous_flavor_ref, new_flavor_ref = \ self._detect_server_image_flavor(self.server_id) resp, server = self.client.resize(self.server_id, new_flavor_ref) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.revert_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, server = self.client.get_server(self.server_id) self.assertEqual(previous_flavor_ref, server['flavor']['id']) @test.attr(type='gate') def test_create_backup(self): # Positive test:create backup successfully and rotate backups correctly # create the first and the second backup backup1 = data_utils.rand_name('backup-1') resp, _ = self.servers_client.create_backup(self.server_id, 'daily', 2, backup1) oldest_backup_exist = True # the oldest one should be deleted automatically in this test def _clean_oldest_backup(oldest_backup): if oldest_backup_exist: self.images_client.delete_image(oldest_backup) image1_id = data_utils.parse_image_id(resp['location']) self.addCleanup(_clean_oldest_backup, image1_id) self.assertEqual(202, resp.status) self.images_client.wait_for_image_status(image1_id, 'active') backup2 = data_utils.rand_name('backup-2') self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') resp, _ = self.servers_client.create_backup(self.server_id, 'daily', 2, backup2) image2_id = data_utils.parse_image_id(resp['location']) self.addCleanup(self.images_client.delete_image, image2_id) self.assertEqual(202, resp.status) self.images_client.wait_for_image_status(image2_id, 'active') # verify they have been created properties = { 'image_type': 'backup', 'backup_type': "daily", 'instance_uuid': self.server_id, } resp, image_list = self.images_client.image_list_detail( properties, sort_key='created_at', sort_dir='asc') self.assertEqual(200, resp.status) self.assertEqual(2, len(image_list)) self.assertEqual((backup1, backup2), (image_list[0]['name'], image_list[1]['name'])) # create the third one, due to the rotation is 2, # the first one will be deleted backup3 = data_utils.rand_name('backup-3') self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') resp, _ = self.servers_client.create_backup(self.server_id, 'daily', 2, backup3) image3_id = data_utils.parse_image_id(resp['location']) self.addCleanup(self.images_client.delete_image, image3_id) self.assertEqual(202, resp.status) # the first back up should be deleted self.images_client.wait_for_resource_deletion(image1_id) oldest_backup_exist = False resp, image_list = self.images_client.image_list_detail( properties, sort_key='created_at', sort_dir='asc') self.assertEqual(200, resp.status) self.assertEqual(2, len(image_list), 'Unexpected number of images for ' 'v3:test_create_backup; was the oldest backup not ' 'yet deleted? Image list: %s' % [image['name'] for image in image_list]) self.assertEqual((backup2, backup3), (image_list[0]['name'], image_list[1]['name'])) def _get_output(self): resp, output = self.servers_client.get_console_output( self.server_id, 10) self.assertEqual(200, resp.status) self.assertTrue(output, "Console output was empty.") lines = len(output.split('\n')) self.assertEqual(lines, 10) @test.attr(type='gate') def test_get_console_output(self): # Positive test:Should be able to GET the console output # for a given server_id and number of lines # This reboot is necessary for outputting some console log after # creating a instance backup. If a instance backup, the console # log file is truncated and we cannot get any console log through # "console-log" API. # The detail is https://bugs.launchpad.net/nova/+bug/1251920 resp, body = self.servers_client.reboot(self.server_id, 'HARD') self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') self.wait_for(self._get_output) @test.attr(type='gate') def test_get_console_output_server_id_in_shutoff_status(self): # Positive test:Should be able to GET the console output # for a given server_id in SHUTOFF status # NOTE: SHUTOFF is irregular status. To avoid test instability, # one server is created only for this test without using # the server that was created in setupClass. resp, server = self.create_test_server(wait_until='ACTIVE') temp_server_id = server['id'] resp, server = self.servers_client.stop(temp_server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(temp_server_id, 'SHUTOFF') self.wait_for(self._get_output) @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type='gate') def test_pause_unpause_server(self): resp, server = self.client.pause_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'PAUSED') resp, server = self.client.unpause_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type='gate') def test_suspend_resume_server(self): resp, server = self.client.suspend_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SUSPENDED') resp, server = self.client.resume_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') @test.attr(type='gate') def test_shelve_unshelve_server(self): resp, server = self.client.shelve_server(self.server_id) self.assertEqual(202, resp.status) offload_time = CONF.compute.shelved_offload_time if offload_time >= 0: self.client.wait_for_server_status(self.server_id, 'SHELVED_OFFLOADED', extra_timeout=offload_time) else: self.client.wait_for_server_status(self.server_id, 'SHELVED') resp, server = self.client.shelve_offload_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SHELVED_OFFLOADED') resp, server = self.client.get_server(self.server_id) image_name = server['name'] + '-shelved' resp, images = self.images_client.image_list(name=image_name) self.assertEqual(1, len(images)) self.assertEqual(image_name, images[0]['name']) resp, server = self.client.unshelve_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') @test.attr(type='gate') def test_stop_start_server(self): resp, server = self.servers_client.stop(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF') resp, server = self.servers_client.start(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') @test.attr(type='gate') def test_lock_unlock_server(self): # Lock the server,try server stop(exceptions throw),unlock it and retry resp, server = self.servers_client.lock_server(self.server_id) self.assertEqual(202, resp.status) resp, server = self.servers_client.get_server(self.server_id) self.assertEqual(200, resp.status) self.assertEqual(server['status'], 'ACTIVE') # Locked server is not allowed to be stopped by non-admin user self.assertRaises(exceptions.Conflict, self.servers_client.stop, self.server_id) resp, server = self.servers_client.unlock_server(self.server_id) self.assertEqual(202, resp.status) resp, server = self.servers_client.stop(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF') resp, server = self.servers_client.start(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') @testtools.skipUnless(CONF.compute_feature_enabled.vnc_console, 'VNC Console feature is disabled') @test.attr(type='gate') def test_get_vnc_console(self): # Get the VNC console console_types = ['novnc', 'xvpvnc'] for console_type in console_types: resp, body = self.servers_client.get_vnc_console(self.server_id, console_type) self.assertEqual(200, resp.status) self.assertEqual(console_type, body['type']) self.assertNotEqual('', body['url']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_multiple_create.py0000664000175000017500000000442312332757070030616 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class MultipleCreateV3Test(base.BaseV3ComputeTest): _name = 'multiple-create-test' def _generate_name(self): return data_utils.rand_name(self._name) def _create_multiple_servers(self, name=None, wait_until=None, **kwargs): """ This is the right way to create_multiple servers and manage to get the created servers into the servers list to be cleaned up after all. """ kwargs['name'] = kwargs.get('name', self._generate_name()) resp, body = self.create_test_server(**kwargs) return resp, body @test.attr(type='gate') def test_multiple_create(self): resp, body = self._create_multiple_servers(wait_until='ACTIVE', min_count=1, max_count=2) # NOTE(maurosr): do status response check and also make sure that # reservation_id is not in the response body when the request send # contains return_reservation_id=False self.assertEqual('202', resp['status']) self.assertNotIn('reservation_id', body) @test.attr(type='gate') def test_multiple_create_with_reservation_return(self): resp, body = self._create_multiple_servers(wait_until='ACTIVE', min_count=1, max_count=2, return_reservation_id=True) self.assertEqual(resp['status'], '202') self.assertIn('reservation_id', body) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_servers_negative.py0000664000175000017500000004302512332757070031014 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import sys import testtools from tempest.api.compute import base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ServersNegativeV3Test(base.BaseV3ComputeTest): def setUp(self): super(ServersNegativeV3Test, self).setUp() try: self.client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: self.__class__.server_id = self.rebuild_server(self.server_id) def tearDown(self): self.server_check_teardown() super(ServersNegativeV3Test, self).tearDown() @classmethod def setUpClass(cls): super(ServersNegativeV3Test, cls).setUpClass() cls.client = cls.servers_client cls.alt_os = clients.AltManager() cls.alt_client = cls.alt_os.servers_v3_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] @test.attr(type=['negative', 'gate']) def test_server_name_blank(self): # Create a server with name parameter empty self.assertRaises(exceptions.BadRequest, self.create_test_server, name='') @test.attr(type=['negative', 'gate']) def test_create_with_invalid_image(self): # Create a server with an unknown image self.assertRaises(exceptions.BadRequest, self.create_test_server, image_id=-1) @test.attr(type=['negative', 'gate']) def test_create_with_invalid_flavor(self): # Create a server with an unknown flavor self.assertRaises(exceptions.BadRequest, self.create_test_server, flavor=-1,) @test.attr(type=['negative', 'gate']) def test_invalid_access_ip_v4_address(self): # An access IPv4 address must match a valid address pattern IPv4 = '1.1.1.1.1.1' self.assertRaises(exceptions.BadRequest, self.create_test_server, access_ip_v4=IPv4) @test.attr(type=['negative', 'gate']) def test_invalid_ip_v6_address(self): # An access IPv6 address must match a valid address pattern IPv6 = 'notvalid' self.assertRaises(exceptions.BadRequest, self.create_test_server, access_ip_v6=IPv6) @test.attr(type=['negative', 'gate']) def test_resize_nonexistent_server(self): # Resize a non-existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.resize, nonexistent_server, self.flavor_ref) @test.attr(type=['negative', 'gate']) def test_resize_server_with_non_existent_flavor(self): # Resize a server with non-existent flavor nonexistent_flavor = data_utils.rand_uuid() self.assertRaises(exceptions.BadRequest, self.client.resize, self.server_id, flavor_ref=nonexistent_flavor) @test.attr(type=['negative', 'gate']) def test_resize_server_with_null_flavor(self): # Resize a server with null flavor self.assertRaises(exceptions.BadRequest, self.client.resize, self.server_id, flavor_ref="") @test.attr(type=['negative', 'gate']) def test_reboot_non_existent_server(self): # Reboot a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.reboot, nonexistent_server, 'SOFT') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type=['negative', 'gate']) def test_pause_paused_server(self): # Pause a paused server. self.client.pause_server(self.server_id) self.client.wait_for_server_status(self.server_id, 'PAUSED') self.assertRaises(exceptions.Conflict, self.client.pause_server, self.server_id) self.client.unpause_server(self.server_id) @test.attr(type=['negative', 'gate']) def test_rebuild_reboot_deleted_server(self): # Rebuild and Reboot a deleted server _, server = self.create_test_server() self.client.delete_server(server['id']) self.client.wait_for_server_termination(server['id']) self.assertRaises(exceptions.NotFound, self.client.rebuild, server['id'], self.image_ref_alt) self.assertRaises(exceptions.NotFound, self.client.reboot, server['id'], 'SOFT') @test.attr(type=['negative', 'gate']) def test_rebuild_non_existent_server(self): # Rebuild a non existent server nonexistent_server = data_utils.rand_uuid() meta = {'rebuild': 'server'} new_name = data_utils.rand_name('server') file_contents = 'Test server rebuild.' personality = [{'path': '/etc/rebuild.txt', 'contents': base64.b64encode(file_contents)}] self.assertRaises(exceptions.NotFound, self.client.rebuild, nonexistent_server, self.image_ref_alt, name=new_name, meta=meta, personality=personality, adminPass='rebuild') @test.attr(type=['negative', 'gate']) def test_create_numeric_server_name(self): # Create a server with a numeric name server_name = 12345 self.assertRaises(exceptions.BadRequest, self.create_test_server, name=server_name) @test.attr(type=['negative', 'gate']) def test_create_server_name_length_exceeds_256(self): # Create a server with name length exceeding 256 characters server_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.create_test_server, name=server_name) @test.skip_because(bug="1208743") @test.attr(type=['negative', 'gate']) def test_create_with_invalid_network_uuid(self): # Pass invalid network uuid while creating a server networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}] self.assertRaises(exceptions.BadRequest, self.create_test_server, networks=networks) @test.attr(type=['negative', 'gate']) def test_create_with_non_existent_keypair(self): # Pass a non-existent keypair while creating a server key_name = data_utils.rand_name('key') self.assertRaises(exceptions.BadRequest, self.create_test_server, key_name=key_name) @test.skip_because(bug="1273948") @test.attr(type=['negative', 'gate']) def test_create_server_metadata_exceeds_length_limit(self): # Pass really long metadata while creating a server metadata = {'a': 'b' * 260} self.assertRaises(exceptions.BadRequest, self.create_test_server, meta=metadata) @test.attr(type=['negative', 'gate']) def test_update_name_of_non_existent_server(self): # Update name of a non-existent server server_name = data_utils.rand_name('server') new_name = data_utils.rand_name('server') + '_updated' self.assertRaises(exceptions.NotFound, self.client.update_server, server_name, name=new_name) @test.attr(type=['negative', 'gate']) def test_update_server_set_empty_name(self): # Update name of the server to an empty string server_name = data_utils.rand_name('server') new_name = '' self.assertRaises(exceptions.BadRequest, self.client.update_server, server_name, name=new_name) @test.attr(type=['negative', 'gate']) def test_update_server_of_another_tenant(self): # Update name of a server that belongs to another tenant new_name = self.server_id + '_new' self.assertRaises(exceptions.NotFound, self.alt_client.update_server, self.server_id, name=new_name) @test.attr(type=['negative', 'gate']) def test_update_server_name_length_exceeds_256(self): # Update name of server exceed the name length limit new_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.client.update_server, self.server_id, name=new_name) @test.attr(type=['negative', 'gate']) def test_delete_non_existent_server(self): # Delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.delete_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_delete_a_server_of_another_tenant(self): # Delete a server that belongs to another tenant self.assertRaises(exceptions.NotFound, self.alt_client.delete_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_delete_server_pass_negative_id(self): # Pass an invalid string parameter to delete server self.assertRaises(exceptions.NotFound, self.client.delete_server, -1) @test.attr(type=['negative', 'gate']) def test_delete_server_pass_id_exceeding_length_limit(self): # Pass a server ID that exceeds length limit to delete server self.assertRaises(exceptions.NotFound, self.client.delete_server, sys.maxint + 1) @test.attr(type=['negative', 'gate']) def test_create_with_nonexistent_security_group(self): # Create a server with a nonexistent security group security_groups = [{'name': 'does_not_exist'}] self.assertRaises(exceptions.BadRequest, self.create_test_server, security_groups=security_groups) @test.attr(type=['negative', 'gate']) def test_get_non_existent_server(self): # Get a non existent server details nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_stop_non_existent_server(self): # Stop a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.servers_client.stop, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_pause_non_existent_server(self): # pause a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.pause_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_unpause_non_existent_server(self): # unpause a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.unpause_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_unpause_server_invalid_state(self): # unpause an active server. self.assertRaises(exceptions.Conflict, self.client.unpause_server, self.server_id) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_suspend_non_existent_server(self): # suspend a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.suspend_server, nonexistent_server) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_suspend_server_invalid_state(self): # suspend a suspended server. resp, _ = self.client.suspend_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SUSPENDED') self.assertRaises(exceptions.Conflict, self.client.suspend_server, self.server_id) self.client.resume_server(self.server_id) @test.attr(type=['negative', 'gate']) def test_resume_non_existent_server(self): # resume a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.resume_server, nonexistent_server) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_resume_server_invalid_state(self): # resume an active server. self.assertRaises(exceptions.Conflict, self.client.resume_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_get_console_output_of_non_existent_server(self): # get the console output for a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_console_output, nonexistent_server, 10) @test.attr(type=['negative', 'gate']) def test_force_delete_nonexistent_server_id(self): # force-delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.force_delete_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_force_delete_server_invalid_state(self): # we can only force-delete a server in 'soft-delete' state self.assertRaises(exceptions.Conflict, self.client.force_delete_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_restore_nonexistent_server_id(self): # restore-delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.restore_soft_deleted_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_restore_server_invalid_state(self): # we can only restore-delete a server in 'soft-delete' state self.assertRaises(exceptions.Conflict, self.client.restore_soft_deleted_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_shelve_non_existent_server(self): # shelve a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.shelve_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_shelve_shelved_server(self): # shelve a shelved server. resp, server = self.client.shelve_server(self.server_id) self.assertEqual(202, resp.status) offload_time = CONF.compute.shelved_offload_time if offload_time >= 0: self.client.wait_for_server_status(self.server_id, 'SHELVED_OFFLOADED', extra_timeout=offload_time) else: self.client.wait_for_server_status(self.server_id, 'SHELVED') resp, server = self.client.get_server(self.server_id) image_name = server['name'] + '-shelved' resp, images = self.images_client.image_list(name=image_name) self.assertEqual(1, len(images)) self.assertEqual(image_name, images[0]['name']) self.assertRaises(exceptions.Conflict, self.client.shelve_server, self.server_id) self.client.unshelve_server(self.server_id) @test.attr(type=['negative', 'gate']) def test_unshelve_non_existent_server(self): # unshelve a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.unshelve_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_unshelve_server_invalid_state(self): # unshelve an active server. self.assertRaises(exceptions.Conflict, self.client.unshelve_server, self.server_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/__init__.py0000664000175000017500000000000012332757070026123 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_servers.py0000664000175000017500000001150412332757070027127 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class ServersV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(ServersV3Test, cls).setUpClass() cls.client = cls.servers_client def tearDown(self): self.clear_servers() super(ServersV3Test, self).tearDown() @test.attr(type='gate') def test_create_server_with_admin_password(self): # If an admin password is provided on server creation, the server's # root password should be set to that password. resp, server = self.create_test_server(admin_password='testpassword') # Verify the password is set correctly in the response self.assertEqual('testpassword', server['admin_password']) @test.attr(type='gate') def test_create_with_existing_server_name(self): # Creating a server with a name that already exists is allowed # TODO(sdague): clear out try, we do cleanup one layer up server_name = data_utils.rand_name('server') resp, server = self.create_test_server(name=server_name, wait_until='ACTIVE') id1 = server['id'] resp, server = self.create_test_server(name=server_name, wait_until='ACTIVE') id2 = server['id'] self.assertNotEqual(id1, id2, "Did not create a new server") resp, server = self.client.get_server(id1) name1 = server['name'] resp, server = self.client.get_server(id2) name2 = server['name'] self.assertEqual(name1, name2) @test.attr(type='gate') def test_create_specify_keypair(self): # Specify a keypair while creating a server key_name = data_utils.rand_name('key') resp, keypair = self.keypairs_client.create_keypair(key_name) resp, body = self.keypairs_client.list_keypairs() resp, server = self.create_test_server(key_name=key_name) self.assertEqual('202', resp['status']) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual(key_name, server['key_name']) @test.attr(type='gate') def test_update_server_name(self): # The server name should be changed to the the provided value resp, server = self.create_test_server(wait_until='ACTIVE') # Update the server with a new name resp, server = self.client.update_server(server['id'], name='newname') self.assertEqual(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the name of the server has changed resp, server = self.client.get_server(server['id']) self.assertEqual('newname', server['name']) @test.attr(type='gate') def test_update_access_server_address(self): # The server's access addresses should reflect the provided values resp, server = self.create_test_server(wait_until='ACTIVE') # Update the IPv4 and IPv6 access addresses resp, body = self.client.update_server(server['id'], access_ip_v4='1.1.1.1', access_ip_v6='::babe:202:202') self.assertEqual(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the access addresses have been updated resp, server = self.client.get_server(server['id']) self.assertEqual('1.1.1.1', server['os-access-ips:access_ip_v4']) self.assertEqual('::babe:202:202', server['os-access-ips:access_ip_v6']) @test.attr(type='gate') def test_create_server_with_ipv6_addr_only(self): # Create a server without an IPv4 address(only IPv6 address). resp, server = self.create_test_server(access_ip_v6='2001:2001::3') self.assertEqual('202', resp['status']) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual('2001:2001::3', server['os-access-ips:access_ip_v6']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_addresses.py0000664000175000017500000000533612332757070031007 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class ServerAddressesV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): # This test module might use a network and a subnet cls.set_network_resources(network=True, subnet=True) super(ServerAddressesV3Test, cls).setUpClass() cls.client = cls.servers_client resp, cls.server = cls.create_test_server(wait_until='ACTIVE') @test.skip_because(bug="1210483", condition=CONF.service_available.neutron) @test.attr(type='smoke') def test_list_server_addresses(self): # All public and private addresses for # a server should be returned resp, addresses = self.client.list_addresses(self.server['id']) self.assertEqual('200', resp['status']) # We do not know the exact network configuration, but an instance # should at least have a single public or private address self.assertTrue(len(addresses) >= 1) for network_name, network_addresses in addresses.iteritems(): self.assertTrue(len(network_addresses) >= 1) for address in network_addresses: self.assertTrue(address['addr']) self.assertTrue(address['version']) @test.attr(type='smoke') def test_list_server_addresses_by_network(self): # Providing a network type should filter # the addresses return by that type resp, addresses = self.client.list_addresses(self.server['id']) # Once again we don't know the environment's exact network config, # but the response for each individual network should be the same # as the partial result of the full address list id = self.server['id'] for addr_type in addresses: resp, addr = self.client.list_addresses_by_network(id, addr_type) self.assertEqual('200', resp['status']) addr = addr[addr_type] for address in addresses[addr_type]: self.assertTrue(any([a for a in addr if a == address])) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_list_server_filters.py0000664000175000017500000002732112332757070031533 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.api import utils from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ListServerFiltersV3Test(base.BaseV3ComputeTest): @classmethod @test.safe_setup def setUpClass(cls): cls.set_network_resources(network=True, subnet=True, dhcp=True) super(ListServerFiltersV3Test, cls).setUpClass() cls.client = cls.servers_client # Check to see if the alternate image ref actually exists... images_client = cls.images_client resp, images = images_client.image_list() if cls.image_ref != cls.image_ref_alt and \ any([image for image in images if image['id'] == cls.image_ref_alt]): cls.multiple_images = True else: cls.image_ref_alt = cls.image_ref # Do some sanity checks here. If one of the images does # not exist, fail early since the tests won't work... try: cls.images_client.get_image_meta(cls.image_ref) except exceptions.NotFound: raise RuntimeError("Image %s (image_ref) was not found!" % cls.image_ref) try: cls.images_client.get_image_meta(cls.image_ref_alt) except exceptions.NotFound: raise RuntimeError("Image %s (image_ref_alt) was not found!" % cls.image_ref_alt) cls.s1_name = data_utils.rand_name(cls.__name__ + '-instance') resp, cls.s1 = cls.create_test_server(name=cls.s1_name, wait_until='ACTIVE') cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance') resp, cls.s2 = cls.create_test_server(name=cls.s2_name, image_id=cls.image_ref_alt, wait_until='ACTIVE') cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance') resp, cls.s3 = cls.create_test_server(name=cls.s3_name, flavor=cls.flavor_ref_alt, wait_until='ACTIVE') if (CONF.service_available.neutron and CONF.compute.allow_tenant_isolation): network = cls.isolated_creds.get_primary_network() cls.fixed_network_name = network['name'] else: cls.fixed_network_name = CONF.compute.fixed_network_name @utils.skip_unless_attr('multiple_images', 'Only one image found') @test.attr(type='gate') def test_list_servers_filter_by_image(self): # Filter the list of servers by image params = {'image': self.image_ref} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_flavor(self): # Filter the list of servers by flavor params = {'flavor': self.flavor_ref_alt} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_server_name(self): # Filter the list of servers by server name params = {'name': self.s1_name} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_server_status(self): # Filter the list of servers by server status params = {'status': 'active'} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_limit(self): # Verify only the expected number of servers are returned params = {'limit': 1} resp, servers = self.client.list_servers(params) self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x])) @test.attr(type='gate') def test_list_servers_filter_by_zero_limit(self): # Verify only the expected number of servers are returned params = {'limit': 0} resp, servers = self.client.list_servers(params) self.assertEqual(0, len(servers['servers'])) @test.attr(type='gate') def test_list_servers_filter_by_exceed_limit(self): # Verify only the expected number of servers are returned params = {'limit': 100000} resp, servers = self.client.list_servers(params) resp, all_servers = self.client.list_servers() self.assertEqual(len([x for x in all_servers['servers'] if 'id' in x]), len([x for x in servers['servers'] if 'id' in x])) @utils.skip_unless_attr('multiple_images', 'Only one image found') @test.attr(type='gate') def test_list_servers_detailed_filter_by_image(self): # Filter the detailed list of servers by image params = {'image': self.image_ref} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_detailed_filter_by_flavor(self): # Filter the detailed list of servers by flavor params = {'flavor': self.flavor_ref_alt} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_detailed_filter_by_server_name(self): # Filter the detailed list of servers by server name params = {'name': self.s1_name} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_detailed_filter_by_server_status(self): # Filter the detailed list of servers by server status params = {'status': 'active'} resp, body = self.client.list_servers_with_detail(params) expected_servers = (self.s1['id'], self.s2['id'], self.s3['id']) servers = [x for x in body['servers'] if x['id'] in expected_servers] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers]) @test.attr(type='gate') def test_list_servers_filter_by_shutoff_status(self): # Filter the list of servers by server shutoff status params = {'status': 'shutoff'} self.client.stop(self.s1['id']) self.client.wait_for_server_status(self.s1['id'], 'SHUTOFF') resp, body = self.client.list_servers(params) self.client.start(self.s1['id']) self.client.wait_for_server_status(self.s1['id'], 'ACTIVE') servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filtered_by_name_wildcard(self): # List all servers that contains '-instance' in name params = {'name': '-instance'} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) # Let's take random part of name and try to search it part_name = self.s1_name[6:-1] params = {'name': part_name} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_filtered_by_ip(self): # Filter servers by ip # Here should be listed 1 server resp, self.s1 = self.client.get_server(self.s1['id']) ip = self.s1['addresses'][self.fixed_network_name][0]['addr'] params = {'ip': ip} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.skip_because(bug="1182883", condition=CONF.service_available.neutron) @test.attr(type='gate') def test_list_servers_filtered_by_ip_regex(self): # Filter servers by regex ip # List all servers filtered by part of ip address. # Here should be listed all servers resp, self.s1 = self.client.get_server(self.s1['id']) ip = self.s1['addresses'][self.fixed_network_name][0]['addr'][0:-3] params = {'ip': ip} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_detailed_limit_results(self): # Verify only the expected number of detailed results are returned params = {'limit': 1} resp, servers = self.client.list_servers_with_detail(params) self.assertEqual(1, len(servers['servers'])) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_delete_server.py0000664000175000017500000001555112332757070030274 0ustar chuckchuck00000000000000# Copyright 2012 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 testtools from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class DeleteServersV3Test(base.BaseV3ComputeTest): # NOTE: Server creations of each test class should be under 10 # for preventing "Quota exceeded for instances". @classmethod def setUpClass(cls): super(DeleteServersV3Test, cls).setUpClass() cls.client = cls.servers_client @test.attr(type='gate') def test_delete_server_while_in_building_state(self): # Delete a server while it's VM state is Building resp, server = self.create_test_server(wait_until='BUILD') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_active_server(self): # Delete a server while it's VM state is Active resp, server = self.create_test_server(wait_until='ACTIVE') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_server_while_in_shutoff_state(self): # Delete a server while it's VM state is Shutoff resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.stop(server['id']) self.client.wait_for_server_status(server['id'], 'SHUTOFF') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type='gate') def test_delete_server_while_in_pause_state(self): # Delete a server while it's VM state is Pause resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.pause_server(server['id']) self.client.wait_for_server_status(server['id'], 'PAUSED') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_server_while_in_shelved_state(self): # Delete a server while it's VM state is Shelved resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.shelve_server(server['id']) self.assertEqual(202, resp.status) offload_time = CONF.compute.shelved_offload_time if offload_time >= 0: self.client.wait_for_server_status(server['id'], 'SHELVED_OFFLOADED', extra_timeout=offload_time) else: self.client.wait_for_server_status(server['id'], 'SHELVED') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @testtools.skipIf(not CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_delete_server_while_in_verify_resize_state(self): # Delete a server while it's VM state is VERIFY_RESIZE resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.resize(server['id'], self.flavor_ref_alt) self.assertEqual(202, resp.status) self.client.wait_for_server_status(server['id'], 'VERIFY_RESIZE') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_server_while_in_attached_volume(self): # Delete a server while a volume is attached to it device = '/dev/%s' % CONF.compute.volume_device_name resp, server = self.create_test_server(wait_until='ACTIVE') resp, volume = self.volumes_client.create_volume(1) self.addCleanup(self.volumes_client.delete_volume, volume['id']) self.volumes_client.wait_for_volume_status(volume['id'], 'available') resp, body = self.client.attach_volume(server['id'], volume['id'], device=device) self.volumes_client.wait_for_volume_status(volume['id'], 'in-use') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) self.volumes_client.wait_for_volume_status(volume['id'], 'available') class DeleteServersAdminV3Test(base.BaseV3ComputeAdminTest): # NOTE: Server creations of each test class should be under 10 # for preventing "Quota exceeded for instances". @classmethod def setUpClass(cls): super(DeleteServersAdminV3Test, cls).setUpClass() cls.non_admin_client = cls.servers_client cls.admin_client = cls.servers_admin_client @test.attr(type='gate') def test_delete_server_while_in_error_state(self): # Delete a server while it's VM state is error resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.admin_client.reset_state(server['id'], state='error') self.assertEqual(202, resp.status) # Verify server's state resp, server = self.non_admin_client.get_server(server['id']) self.assertEqual(server['status'], 'ERROR') resp, _ = self.non_admin_client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.servers_client.wait_for_server_termination(server['id'], ignore_error=True) @test.attr(type='gate') def test_admin_delete_servers_of_others(self): # Administrator can delete servers of others resp, server = self.create_test_server(wait_until='ACTIVE') resp, _ = self.admin_client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.servers_client.wait_for_server_termination(server['id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_attach_volume.py0000664000175000017500000001044012332757070030267 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest.common.utils.linux import remote_client from tempest import config from tempest import test CONF = config.CONF class AttachVolumeV3Test(base.BaseV3ComputeTest): def __init__(self, *args, **kwargs): super(AttachVolumeV3Test, self).__init__(*args, **kwargs) self.server = None self.volume = None self.attached = False @classmethod def setUpClass(cls): cls.prepare_instance_network() super(AttachVolumeV3Test, cls).setUpClass() cls.device = CONF.compute.volume_device_name if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) def _detach(self, server_id, volume_id): if self.attached: self.servers_client.detach_volume(server_id, volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') def _delete_volume(self): if self.volume: self.volumes_client.delete_volume(self.volume['id']) self.volume = None def _create_and_attach(self): # Start a server and wait for it to become ready admin_pass = self.image_ssh_password resp, server = self.create_test_server(wait_until='ACTIVE', admin_password=admin_pass) self.server = server # Record addresses so that we can ssh later resp, server['addresses'] = \ self.servers_client.list_addresses(server['id']) # Create a volume and wait for it to become ready resp, volume = self.volumes_client.create_volume(1, display_name='test') self.volume = volume self.addCleanup(self._delete_volume) self.volumes_client.wait_for_volume_status(volume['id'], 'available') # Attach the volume to the server self.servers_client.attach_volume(server['id'], volume['id'], device='/dev/%s' % self.device) self.volumes_client.wait_for_volume_status(volume['id'], 'in-use') self.attached = True self.addCleanup(self._detach, server['id'], volume['id']) @testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test') @test.attr(type='gate') def test_attach_detach_volume(self): # Stop and Start a server with an attached volume, ensuring that # the volume remains attached. self._create_and_attach() server = self.server volume = self.volume self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.servers_client.start(server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') linux_client = remote_client.RemoteClient(server, self.image_ssh_user, server['admin_password']) partitions = linux_client.get_partitions() self.assertIn(self.device, partitions) self._detach(server['id'], volume['id']) self.attached = False self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.servers_client.start(server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') linux_client = remote_client.RemoteClient(server, self.image_ssh_user, server['admin_password']) partitions = linux_client.get_partitions() self.assertNotIn(self.device, partitions) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_metadata.py0000664000175000017500000001226312332757070030607 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class ServerMetadataV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(ServerMetadataV3Test, cls).setUpClass() cls.client = cls.servers_client cls.quotas = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE') cls.server_id = server['id'] def setUp(self): super(ServerMetadataV3Test, self).setUp() meta = {'key1': 'value1', 'key2': 'value2'} resp, _ = self.client.set_server_metadata(self.server_id, meta) self.assertEqual(resp.status, 200) @test.attr(type='gate') def test_list_server_metadata(self): # All metadata key/value pairs for a server should be returned resp, resp_metadata = self.client.list_server_metadata(self.server_id) # Verify the expected metadata items are in the list self.assertEqual(200, resp.status) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_set_server_metadata(self): # The server's metadata should be replaced with the provided values # Create a new set of metadata for the server req_metadata = {'meta2': 'data2', 'meta3': 'data3'} resp, metadata = self.client.set_server_metadata(self.server_id, req_metadata) self.assertEqual(200, resp.status) # Verify the expected values are correct, and that the # previous values have been removed resp, resp_metadata = self.client.list_server_metadata(self.server_id) self.assertEqual(resp_metadata, req_metadata) @test.attr(type='gate') def test_update_server_metadata(self): # The server's metadata values should be updated to the # provided values meta = {'key1': 'alt1', 'key3': 'value3'} resp, metadata = self.client.update_server_metadata(self.server_id, meta) self.assertEqual(201, resp.status) # Verify the values have been updated to the proper values resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_update_metadata_empty_body(self): # The original metadata should not be lost if empty metadata body is # passed meta = {} _, metadata = self.client.update_server_metadata(self.server_id, meta) resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_get_server_metadata_item(self): # The value for a specific metadata key should be returned resp, meta = self.client.get_server_metadata_item(self.server_id, 'key2') self.assertEqual('value2', meta['key2']) @test.attr(type='gate') def test_set_server_metadata_item(self): # The item's value should be updated to the provided value # Update the metadata value meta = {'nova': 'alt'} resp, body = self.client.set_server_metadata_item(self.server_id, 'nova', meta) self.assertEqual(200, resp.status) # Verify the meta item's value has been updated resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_delete_server_metadata_item(self): # The metadata value/key pair should be deleted from the server resp, meta = self.client.delete_server_metadata_item(self.server_id, 'key1') self.assertEqual(204, resp.status) # Verify the metadata item has been removed resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key2': 'value2'} self.assertEqual(expected, resp_metadata) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_instance_actions.py0000664000175000017500000000415212332757070030763 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class InstanceActionsV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(InstanceActionsV3Test, cls).setUpClass() cls.client = cls.servers_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.resp = resp cls.server_id = server['id'] @test.skip_because(bug="1206032") @test.attr(type='gate') def test_list_server_actions(self): # List actions of the provided server resp, body = self.client.reboot(self.server_id, 'HARD') self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, body = self.client.list_server_actions(self.server_id) self.assertEqual(200, resp.status) self.assertTrue(len(body) == 2, str(body)) self.assertTrue(any([i for i in body if i['action'] == 'create'])) self.assertTrue(any([i for i in body if i['action'] == 'reboot'])) @test.skip_because(bug="1206032") @test.attr(type='gate') @test.skip_because(bug="1281915") def test_get_server_action(self): # Get the action details of the provided server request_id = self.resp['x-compute-request-id'] resp, body = self.client.get_server_action(self.server_id, request_id) self.assertEqual(200, resp.status) self.assertEqual(self.server_id, body['server_uuid']) self.assertEqual('create', body['action']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_list_servers_negative.py0000664000175000017500000001617612332757070032056 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime from six import moves from tempest.api.compute import base from tempest import exceptions from tempest import test class ListServersNegativeV3Test(base.BaseV3ComputeTest): force_tenant_isolation = True @classmethod @test.safe_setup def setUpClass(cls): super(ListServersNegativeV3Test, cls).setUpClass() cls.client = cls.servers_client # The following servers are created for use # by the test methods in this class. These # servers are cleaned up automatically in the # tearDownClass method of the super-class. cls.existing_fixtures = [] cls.deleted_fixtures = [] cls.start_time = datetime.datetime.utcnow() for x in moves.xrange(2): resp, srv = cls.create_test_server() cls.existing_fixtures.append(srv) resp, srv = cls.create_test_server() cls.client.delete_server(srv['id']) # We ignore errors on termination because the server may # be put into ERROR status on a quick spawn, then delete, # as the compute node expects the instance local status # to be spawning, not deleted. See LP Bug#1061167 cls.client.wait_for_server_termination(srv['id'], ignore_error=True) cls.deleted_fixtures.append(srv) @test.attr(type=['negative', 'gate']) def test_list_servers_with_a_deleted_server(self): # Verify deleted servers do not show by default in list servers # List servers and verify server not returned resp, body = self.client.list_servers() servers = body['servers'] deleted_ids = [s['id'] for s in self.deleted_fixtures] actual = [srv for srv in servers if srv['id'] in deleted_ids] self.assertEqual('200', resp['status']) self.assertEqual([], actual) @test.attr(type=['negative', 'gate']) def test_list_servers_by_non_existing_image(self): # Listing servers for a non existing image returns empty list non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde' resp, body = self.client.list_servers(dict(image=non_existing_image)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type=['negative', 'gate']) def test_list_servers_by_non_existing_flavor(self): # Listing servers by non existing flavor returns empty list non_existing_flavor = 1234 resp, body = self.client.list_servers(dict(flavor=non_existing_flavor)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type=['negative', 'gate']) def test_list_servers_by_non_existing_server_name(self): # Listing servers for a non existent server name returns empty list non_existing_name = 'junk_server_1234' resp, body = self.client.list_servers(dict(name=non_existing_name)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type=['negative', 'gate']) def test_list_servers_status_non_existing(self): # Return an empty list when invalid status is specified non_existing_status = 'BALONEY' resp, body = self.client.list_servers(dict(status=non_existing_status)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type='gate') def test_list_servers_by_limits(self): # List servers by specifying limits resp, body = self.client.list_servers({'limit': 1}) self.assertEqual('200', resp['status']) self.assertEqual(1, len([x for x in body['servers'] if 'id' in x])) @test.attr(type=['negative', 'gate']) def test_list_servers_by_limits_greater_than_actual_count(self): # List servers by specifying a greater value for limit resp, body = self.client.list_servers({'limit': 100}) self.assertEqual('200', resp['status']) self.assertEqual(len(self.existing_fixtures), len(body['servers'])) @test.attr(type=['negative', 'gate']) def test_list_servers_by_limits_pass_string(self): # Return an error if a string value is passed for limit self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'limit': 'testing'}) @test.attr(type=['negative', 'gate']) def test_list_servers_by_limits_pass_negative_value(self): # Return an error if a negative value for limit is passed self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'limit': -1}) @test.attr(type='gate') def test_list_servers_by_changes_since(self): # Servers are listed by specifying changes-since date changes_since = {'changes_since': self.start_time.isoformat()} resp, body = self.client.list_servers(changes_since) self.assertEqual('200', resp['status']) # changes-since returns all instances, including deleted. num_expected = (len(self.existing_fixtures) + len(self.deleted_fixtures)) self.assertEqual(num_expected, len(body['servers']), "Number of servers %d is wrong in %s" % (num_expected, body['servers'])) @test.attr(type=['negative', 'gate']) def test_list_servers_by_changes_since_invalid_date(self): # Return an error when invalid date format is passed self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'changes_since': '2011/01/01'}) @test.attr(type=['negative', 'gate']) def test_list_servers_by_changes_since_future_date(self): # Return an empty list when a date in the future is passed changes_since = {'changes_since': '2051-01-01T12:34:00Z'} resp, body = self.client.list_servers(changes_since) self.assertEqual('200', resp['status']) self.assertEqual(0, len(body['servers'])) @test.attr(type=['negative', 'gate']) def test_list_servers_detail_server_is_deleted(self): # Server details are not listed for a deleted server deleted_ids = [s['id'] for s in self.deleted_fixtures] resp, body = self.client.list_servers_with_detail() servers = body['servers'] actual = [srv for srv in servers if srv['id'] in deleted_ids] self.assertEqual('200', resp['status']) self.assertEqual([], actual) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_multiple_create_negative.py0000664000175000017500000000515612332757070032504 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class MultipleCreateV3NegativeTest(base.BaseV3ComputeTest): _name = 'multiple-create-negative-test' def _generate_name(self): return data_utils.rand_name(self._name) def _create_multiple_servers(self, name=None, wait_until=None, **kwargs): """ This is the right way to create_multiple servers and manage to get the created servers into the servers list to be cleaned up after all. """ kwargs['name'] = kwargs.get('name', self._generate_name()) resp, body = self.create_test_server(**kwargs) return resp, body @test.attr(type=['negative', 'gate']) def test_min_count_less_than_one(self): invalid_min_count = 0 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=invalid_min_count) @test.attr(type=['negative', 'gate']) def test_min_count_non_integer(self): invalid_min_count = 2.5 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=invalid_min_count) @test.attr(type=['negative', 'gate']) def test_max_count_less_than_one(self): invalid_max_count = 0 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, max_count=invalid_max_count) @test.attr(type=['negative', 'gate']) def test_max_count_non_integer(self): invalid_max_count = 2.5 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, max_count=invalid_max_count) @test.attr(type=['negative', 'gate']) def test_max_count_less_than_min_count(self): min_count = 3 max_count = 2 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=min_count, max_count=max_count) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_instance_actions_negative.py0000664000175000017500000000336212332757070032647 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class InstanceActionsNegativeV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(InstanceActionsNegativeV3Test, cls).setUpClass() cls.client = cls.servers_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] @test.attr(type=['negative', 'gate']) def test_list_server_actions_invalid_server(self): # List actions of the invalid server id invalid_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.list_server_actions, invalid_server_id) @test.attr(type=['negative', 'gate']) def test_get_server_action_invalid_request(self): # Get the action details of the provided server with invalid request invalid_request_id = 'req-' + data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_server_action, self.server_id, invalid_request_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/servers/test_server_rescue.py0000664000175000017500000000313712332757070030315 0ustar chuckchuck00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class ServerRescueV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(ServerRescueV3Test, cls).setUpClass() # Server for positive tests resp, server = cls.create_test_server(wait_until='BUILD') cls.server_id = server['id'] cls.password = server['admin_password'] cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE') @test.attr(type='smoke') def test_rescue_unrescue_instance(self): resp, body = self.servers_client.rescue_server( self.server_id, admin_password=self.password) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') resp, body = self.servers_client.unrescue_server(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/test_live_block_migration.py0000664000175000017500000001251712332757070030134 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest import config from tempest.test import attr CONF = config.CONF class LiveBlockMigrationV3Test(base.BaseV3ComputeAdminTest): _host_key = 'os-extended-server-attributes:host' @classmethod def setUpClass(cls): super(LiveBlockMigrationV3Test, cls).setUpClass() cls.admin_hosts_client = cls.hosts_admin_client cls.admin_servers_client = cls.servers_admin_client cls.created_server_ids = [] def _get_compute_hostnames(self): _resp, body = self.admin_hosts_client.list_hosts() return [ host_record['host_name'] for host_record in body if host_record['service'] == 'compute' ] def _get_server_details(self, server_id): _resp, body = self.admin_servers_client.get_server(server_id) return body def _get_host_for_server(self, server_id): return self._get_server_details(server_id)[self._host_key] def _migrate_server_to(self, server_id, dest_host): _resp, body = self.admin_servers_client.live_migrate_server( server_id, dest_host, CONF.compute_feature_enabled. block_migration_for_live_migration) return body def _get_host_other_than(self, host): for target_host in self._get_compute_hostnames(): if host != target_host: return target_host def _get_server_status(self, server_id): return self._get_server_details(server_id)['status'] def _get_an_active_server(self): for server_id in self.created_server_ids: if 'ACTIVE' == self._get_server_status(server_id): return server_id else: _, server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] self.password = server['admin_password'] self.password = 'password' self.created_server_ids.append(server_id) return server_id def _volume_clean_up(self, server_id, volume_id): resp, body = self.volumes_client.get_volume(volume_id) if body['status'] == 'in-use': self.servers_client.detach_volume(server_id, volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') self.volumes_client.delete_volume(volume_id) @testtools.skipIf(not CONF.compute_feature_enabled.live_migration, 'Live migration not available') @attr(type='gate') def test_live_block_migration(self): # Live block migrate an instance to another host if len(self._get_compute_hostnames()) < 2: raise self.skipTest( "Less than 2 compute nodes, skipping migration test.") server_id = self._get_an_active_server() actual_host = self._get_host_for_server(server_id) target_host = self._get_host_other_than(actual_host) self._migrate_server_to(server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.assertEqual(target_host, self._get_host_for_server(server_id)) @testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not CONF.compute_feature_enabled. block_migration_for_live_migration, 'Block Live migration not available') @testtools.skipIf(not CONF.compute_feature_enabled. block_migrate_cinder_iscsi, 'Block Live migration not configured for iSCSI') @attr(type='gate') def test_iscsi_volume(self): # Live block migrate an instance to another host if len(self._get_compute_hostnames()) < 2: raise self.skipTest( "Less than 2 compute nodes, skipping migration test.") server_id = self._get_an_active_server() actual_host = self._get_host_for_server(server_id) target_host = self._get_host_other_than(actual_host) resp, volume = self.volumes_client.create_volume(1, display_name='test') self.volumes_client.wait_for_volume_status(volume['id'], 'available') self.addCleanup(self._volume_clean_up, server_id, volume['id']) # Attach the volume to the server self.servers_client.attach_volume(server_id, volume['id'], device='/dev/xvdb') self.volumes_client.wait_for_volume_status(volume['id'], 'in-use') self._migrate_server_to(server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.assertEqual(target_host, self._get_host_for_server(server_id)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/keypairs/0000775000175000017500000000000012332757136024165 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/keypairs/test_keypairs.py0000664000175000017500000001230512332757070027423 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class KeyPairsV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(KeyPairsV3Test, cls).setUpClass() cls.client = cls.keypairs_client def _delete_keypair(self, keypair_name): resp, _ = self.client.delete_keypair(keypair_name) self.assertEqual(204, resp.status) def _create_keypair(self, keypair_name, pub_key=None): resp, body = self.client.create_keypair(keypair_name, pub_key) self.addCleanup(self._delete_keypair, keypair_name) return resp, body @test.attr(type='gate') def test_keypairs_create_list_delete(self): # Keypairs created should be available in the response list # Create 3 keypairs key_list = list() for i in range(3): k_name = data_utils.rand_name('keypair-') resp, keypair = self._create_keypair(k_name) # Need to pop these keys so that our compare doesn't fail later, # as the keypair dicts from list API doesn't have them. keypair.pop('private_key') keypair.pop('user_id') self.assertEqual(201, resp.status) key_list.append(keypair) # Fetch all keypairs and verify the list # has all created keypairs resp, fetched_list = self.client.list_keypairs() self.assertEqual(200, resp.status) # We need to remove the extra 'keypair' element in the # returned dict. See comment in keypairs_client.list_keypairs() new_list = list() for keypair in fetched_list: new_list.append(keypair['keypair']) fetched_list = new_list # Now check if all the created keypairs are in the fetched list missing_kps = [kp for kp in key_list if kp not in fetched_list] self.assertFalse(missing_kps, "Failed to find keypairs %s in fetched list" % ', '.join(m_key['name'] for m_key in missing_kps)) @test.attr(type='gate') def test_keypair_create_delete(self): # Keypair should be created, verified and deleted k_name = data_utils.rand_name('keypair-') resp, keypair = self._create_keypair(k_name) self.assertEqual(201, resp.status) private_key = keypair['private_key'] key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name") self.assertTrue(private_key is not None, "Field private_key is empty or not found.") @test.attr(type='gate') def test_get_keypair_detail(self): # Keypair should be created, Got details by name and deleted k_name = data_utils.rand_name('keypair-') resp, keypair = self._create_keypair(k_name) resp, keypair_detail = self.client.get_keypair(k_name) self.assertEqual(200, resp.status) self.assertIn('name', keypair_detail) self.assertIn('public_key', keypair_detail) self.assertEqual(keypair_detail['name'], k_name, "The created keypair name is not equal " "to requested name") public_key = keypair_detail['public_key'] self.assertTrue(public_key is not None, "Field public_key is empty or not found.") @test.attr(type='gate') def test_keypair_create_with_pub_key(self): # Keypair should be created with a given public key k_name = data_utils.rand_name('keypair-') pub_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs" "Ne3/1ILNCqFyfYWDeTKLD6jEXC2OQHLmietMWW+/vd" "aZq7KZEwO0jhglaFjU1mpqq4Gz5RX156sCTNM9vRbw" "KAxfsdF9laBYVsex3m3Wmui3uYrKyumsoJn2g9GNnG1P" "I1mrVjZ61i0GY3khna+wzlTpCCmy5HNlrmbj3XLqBUpip" "TOXmsnr4sChzC53KCd8LXuwc1i/CZPvF+3XipvAgFSE53pCt" "LOeB1kYMOBaiUPLQTWXR3JpckqFIQwhIH0zoHlJvZE8hh90" "XcPojYN56tI0OlrGqojbediJYD0rUsJu4weZpbn8vilb3JuDY+jws" "snSA8wzBx3A/8y9Pp1B nova@ubuntu") resp, keypair = self._create_keypair(k_name, pub_key) self.assertEqual(201, resp.status) self.assertFalse('private_key' in keypair, "Field private_key is not empty!") key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name!") tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/keypairs/__init__.py0000664000175000017500000000000012332757070026261 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/keypairs/test_keypairs_negative.py0000664000175000017500000000774512332757070031321 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class KeyPairsNegativeV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(KeyPairsNegativeV3Test, cls).setUpClass() cls.client = cls.keypairs_client def _create_keypair(self, keypair_name, pub_key=None): self.client.create_keypair(keypair_name, pub_key) self.addCleanup(self.client.delete_keypair, keypair_name) @test.attr(type=['negative', 'gate']) def test_keypair_create_with_invalid_pub_key(self): # Keypair should not be created with a non RSA public key k_name = data_utils.rand_name('keypair-') pub_key = "ssh-rsa JUNK nova@ubuntu" self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name, pub_key) @test.attr(type=['negative', 'gate']) def test_keypair_delete_nonexistent_key(self): # Non-existent key deletion should throw a proper error k_name = data_utils.rand_name("keypair-non-existent-") self.assertRaises(exceptions.NotFound, self.client.delete_keypair, k_name) @test.attr(type=['negative', 'gate']) def test_create_keypair_with_empty_public_key(self): # Keypair should not be created with an empty public key k_name = data_utils.rand_name("keypair-") pub_key = ' ' self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name, pub_key) @test.attr(type=['negative', 'gate']) def test_create_keypair_when_public_key_bits_exceeds_maximum(self): # Keypair should not be created when public key bits are too long k_name = data_utils.rand_name("keypair-") pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu' self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name, pub_key) @test.attr(type=['negative', 'gate']) def test_create_keypair_with_duplicate_name(self): # Keypairs with duplicate names should not be created k_name = data_utils.rand_name('keypair-') resp, _ = self.client.create_keypair(k_name) self.addCleanup(self.client.delete_keypair, k_name) self.assertEqual(201, resp.status) # Now try the same keyname to create another key self.assertRaises(exceptions.Conflict, self._create_keypair, k_name) @test.attr(type=['negative', 'gate']) def test_create_keypair_with_empty_name_string(self): # Keypairs with name being an empty string should not be created self.assertRaises(exceptions.BadRequest, self._create_keypair, '') @test.attr(type=['negative', 'gate']) def test_create_keypair_with_long_keynames(self): # Keypairs with name longer than 255 chars should not be created k_name = 'keypair-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name) @test.attr(type=['negative', 'gate']) def test_create_keypair_invalid_name(self): # Keypairs with name being an invalid name should not be created k_name = 'key_/.\@:' self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/flavors/0000775000175000017500000000000012332757136024012 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/flavors/test_flavors.py0000664000175000017500000001250512332757070027077 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class FlavorsV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(FlavorsV3Test, cls).setUpClass() cls.client = cls.flavors_client @test.attr(type='smoke') def test_list_flavors(self): # List of all flavors should contain the expected flavor resp, flavors = self.client.list_flavors() resp, flavor = self.client.get_flavor_details(self.flavor_ref) flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'], 'name': flavor['name']} self.assertIn(flavor_min_detail, flavors) @test.attr(type='smoke') def test_list_flavors_with_detail(self): # Detailed list of all flavors should contain the expected flavor resp, flavors = self.client.list_flavors_with_detail() resp, flavor = self.client.get_flavor_details(self.flavor_ref) self.assertIn(flavor, flavors) @test.attr(type='smoke') def test_get_flavor(self): # The expected flavor details should be returned resp, flavor = self.client.get_flavor_details(self.flavor_ref) self.assertEqual(self.flavor_ref, flavor['id']) @test.attr(type='gate') def test_list_flavors_limit_results(self): # Only the expected number of flavors should be returned params = {'limit': 1} resp, flavors = self.client.list_flavors(params) self.assertEqual(1, len(flavors)) @test.attr(type='gate') def test_list_flavors_detailed_limit_results(self): # Only the expected number of flavors (detailed) should be returned params = {'limit': 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertEqual(1, len(flavors)) @test.attr(type='gate') def test_list_flavors_using_marker(self): # The list of flavors should start from the provided marker resp, flavors = self.client.list_flavors() flavor_id = flavors[0]['id'] params = {'marker': flavor_id} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]), 'The list of flavors did not start after the marker.') @test.attr(type='gate') def test_list_flavors_detailed_using_marker(self): # The list of flavors should start from the provided marker resp, flavors = self.client.list_flavors_with_detail() flavor_id = flavors[0]['id'] params = {'marker': flavor_id} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]), 'The list of flavors did not start after the marker.') @test.attr(type='gate') def test_list_flavors_detailed_filter_by_min_disk(self): # The detailed list of flavors should be filtered by disk space resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['disk']) flavor_id = flavors[0]['id'] params = {'min_disk': flavors[0]['disk'] + 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @test.attr(type='gate') def test_list_flavors_detailed_filter_by_min_ram(self): # The detailed list of flavors should be filtered by RAM resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['ram']) flavor_id = flavors[0]['id'] params = {'min_ram': flavors[0]['ram'] + 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @test.attr(type='gate') def test_list_flavors_filter_by_min_disk(self): # The list of flavors should be filtered by disk space resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['disk']) flavor_id = flavors[0]['id'] params = {'min_disk': flavors[0]['disk'] + 1} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @test.attr(type='gate') def test_list_flavors_filter_by_min_ram(self): # The list of flavors should be filtered by RAM resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['ram']) flavor_id = flavors[0]['id'] params = {'min_ram': flavors[0]['ram'] + 1} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/flavors/__init__.py0000664000175000017500000000000012332757070026106 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/flavors/test_flavors_negative.py0000664000175000017500000000252312332757070030760 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test load_tests = test.NegativeAutoTest.load_tests @test.SimpleNegativeAutoTest class FlavorsListNegativeV3Test(base.BaseV3ComputeTest, test.NegativeAutoTest): _service = 'computev3' _schema_file = 'compute/flavors/flavors_list_v3.json' @test.SimpleNegativeAutoTest class FlavorDetailsNegativeV3Test(base.BaseV3ComputeTest, test.NegativeAutoTest): _service = 'computev3' _schema_file = 'compute/flavors/flavor_details_v3.json' @classmethod def setUpClass(cls): super(FlavorDetailsNegativeV3Test, cls).setUpClass() cls.set_resource("flavor", cls.flavor_ref) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/images/0000775000175000017500000000000012332757136023603 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/images/test_images.py0000664000175000017500000000466412332757070026470 0ustar chuckchuck00000000000000# Copyright 2012 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. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class ImagesV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(ImagesV3Test, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client @test.attr(type='gate') def test_create_image_from_stopped_server(self): resp, server = self.create_test_server(wait_until='ACTIVE') self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.addCleanup(self.servers_client.delete_server, server['id']) snapshot_name = data_utils.rand_name('test-snap-') resp, image = self.create_image_from_server(server['id'], name=snapshot_name, wait_until='active') self.addCleanup(self.client.delete_image, image['id']) self.assertEqual(snapshot_name, image['name']) @test.attr(type='gate') def test_delete_queued_image(self): snapshot_name = data_utils.rand_name('test-snap-') resp, server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.servers_client.delete_server, server['id']) resp, image = self.create_image_from_server(server['id'], name=snapshot_name, wait_until='queued') resp, body = self.client.delete_image(image['id']) self.assertEqual('200', resp['status']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/images/test_images_oneserver_negative.py0000664000175000017500000001416012332757070032432 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ImagesOneServerNegativeV3Test(base.BaseV3ComputeTest): def tearDown(self): """Terminate test instances created after a test is executed.""" for image_id in self.image_ids: self.client.delete_image(image_id) self.image_ids.remove(image_id) self.server_check_teardown() super(ImagesOneServerNegativeV3Test, self).tearDown() def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ImagesOneServerNegativeV3Test, self).setUp() # Check if the server is in a clean state after test try: self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: LOG.exception('server %s timed out to become ACTIVE. rebuilding' % self.server_id) # Rebuild server if cannot reach the ACTIVE state # Usually it means the server had a serious accident self._reset_server() def _reset_server(self): self.__class__.server_id = self.rebuild_server(self.server_id) @classmethod def setUpClass(cls): super(ImagesOneServerNegativeV3Test, cls).setUpClass() cls.client = cls.images_client if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) try: resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] except Exception: cls.tearDownClass() raise cls.image_ids = [] @test.skip_because(bug="1006725") @test.attr(type=['negative', 'gate']) def test_create_image_specify_multibyte_character_image_name(self): # invalid multibyte sequence from: # http://stackoverflow.com/questions/1301402/ # example-invalid-utf8-string invalid_name = data_utils.rand_name(u'\xc3\x28') self.assertRaises(exceptions.BadRequest, self.servers_client.create_image, self.server_id, invalid_name) @test.attr(type=['negative', 'gate']) def test_create_image_specify_invalid_metadata(self): # Return an error when creating image with invalid metadata snapshot_name = data_utils.rand_name('test-snap-') meta = {'': ''} self.assertRaises(exceptions.BadRequest, self.servers_client.create_image, self.server_id, snapshot_name, meta) @test.attr(type=['negative', 'gate']) def test_create_image_specify_metadata_over_limits(self): # Return an error when creating image with meta data over 256 chars snapshot_name = data_utils.rand_name('test-snap-') meta = {'a' * 260: 'b' * 260} self.assertRaises(exceptions.BadRequest, self.servers_client.create_image, self.server_id, snapshot_name, meta) @test.attr(type=['negative', 'gate']) def test_create_second_image_when_first_image_is_being_saved(self): # Disallow creating another image when first image is being saved # Create first snapshot snapshot_name = data_utils.rand_name('test-snap-') resp, body = self.servers_client.create_image(self.server_id, snapshot_name) self.assertEqual(202, resp.status) image_id = data_utils.parse_image_id(resp['location']) self.image_ids.append(image_id) self.addCleanup(self._reset_server) # Create second snapshot alt_snapshot_name = data_utils.rand_name('test-snap-') self.assertRaises(exceptions.Conflict, self.servers_client.create_image, self.server_id, alt_snapshot_name) @test.attr(type=['negative', 'gate']) def test_create_image_specify_name_over_256_chars(self): # Return an error if snapshot name over 256 characters is passed snapshot_name = data_utils.rand_name('a' * 260) self.assertRaises(exceptions.BadRequest, self.servers_client.create_image, self.server_id, snapshot_name) @test.attr(type=['negative', 'gate']) def test_delete_image_that_is_not_yet_active(self): # Return an error while trying to delete an image what is creating snapshot_name = data_utils.rand_name('test-snap-') resp, body = self.servers_client.create_image(self.server_id, snapshot_name) self.assertEqual(202, resp.status) image_id = data_utils.parse_image_id(resp['location']) self.image_ids.append(image_id) self.addCleanup(self._reset_server) # Do not wait, attempt to delete the image, ensure it's successful resp, body = self.client.delete_image(image_id) self.assertEqual('200', resp['status']) self.image_ids.remove(image_id) self.assertRaises(exceptions.NotFound, self.client.get_image, image_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/images/test_images_oneserver.py0000664000175000017500000001067112332757070030553 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ImagesOneServerV3Test(base.BaseV3ComputeTest): def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ImagesOneServerV3Test, self).setUp() # Check if the server is in a clean state after test try: self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: LOG.exception('server %s timed out to become ACTIVE. rebuilding' % self.server_id) # Rebuild server if cannot reach the ACTIVE state # Usually it means the server had a serious accident self.__class__.server_id = self.rebuild_server(self.server_id) def tearDown(self): """Terminate test instances created after a test is executed.""" self.server_check_teardown() super(ImagesOneServerV3Test, self).tearDown() @classmethod def setUpClass(cls): super(ImagesOneServerV3Test, cls).setUpClass() cls.client = cls.images_client if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) try: resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] except Exception: cls.tearDownClass() raise def _get_default_flavor_disk_size(self, flavor_id): resp, flavor = self.flavors_client.get_flavor_details(flavor_id) return flavor['disk'] @test.attr(type='smoke') def test_create_delete_image(self): # Create a new image name = data_utils.rand_name('image') meta = {'image_type': 'test'} resp, body = self.servers_client.create_image(self.server_id, name, meta) self.assertEqual(202, resp.status) image_id = data_utils.parse_image_id(resp['location']) self.client.wait_for_image_status(image_id, 'active') # Verify the image was created correctly resp, image = self.client.get_image_meta(image_id) self.assertEqual(name, image['name']) self.assertEqual('test', image['properties']['image_type']) resp, original_image = self.client.get_image_meta(self.image_ref) # Verify minRAM is the same as the original image self.assertEqual(image['min_ram'], original_image['min_ram']) # Verify minDisk is the same as the original image or the flavor size flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref) self.assertIn(str(image['min_disk']), (str(original_image['min_disk']), str(flavor_disk_size))) # Verify the image was deleted correctly resp, body = self.client.delete_image(image_id) self.assertEqual('200', resp['status']) self.client.wait_for_resource_deletion(image_id) @test.attr(type=['gate']) def test_create_image_specify_multibyte_character_image_name(self): # prefix character is: # http://www.fileformat.info/info/unicode/char/1F4A9/index.htm utf8_name = data_utils.rand_name(u'\xF0\x9F\x92\xA9') resp, body = self.servers_client.create_image(self.server_id, utf8_name) image_id = data_utils.parse_image_id(resp['location']) self.addCleanup(self.client.delete_image, image_id) self.assertEqual('202', resp['status']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/images/__init__.py0000664000175000017500000000000012332757070025677 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/images/test_images_negative.py0000664000175000017500000000677012332757070030352 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ImagesNegativeV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(ImagesNegativeV3Test, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client def __create_image__(self, server_id, name, meta=None): resp, body = self.servers_client.create_image(server_id, name, meta) image_id = data_utils.parse_image_id(resp['location']) self.addCleanup(self.client.delete_image, image_id) self.client.wait_for_image_status(image_id, 'active') return resp, body @test.attr(type=['negative', 'gate']) def test_create_image_from_deleted_server(self): # An image should not be created if the server instance is removed resp, server = self.create_test_server(wait_until='ACTIVE') # Delete server before trying to create server self.servers_client.delete_server(server['id']) self.servers_client.wait_for_server_termination(server['id']) # Create a new image after server is deleted name = data_utils.rand_name('image') meta = {'image_type': 'test'} self.assertRaises(exceptions.NotFound, self.__create_image__, server['id'], name, meta) @test.attr(type=['negative', 'gate']) def test_create_image_from_nonexistent_server(self): # An image should not be created with invalid server id # Create a new image with invalid server id nonexistent_server_id = data_utils.rand_uuid() name = data_utils.rand_name('image') meta = {'image_type': 'test'} self.assertRaises(exceptions.NotFound, self.__create_image__, nonexistent_server_id, name, meta) @test.attr(type=['negative', 'gate']) def test_create_image_specify_uuid_35_characters_or_less(self): # Return an error if Image ID passed is 35 characters or less snapshot_name = data_utils.rand_name('test-snap-') test_uuid = ('a' * 35) self.assertRaises(exceptions.NotFound, self.servers_client.create_image, test_uuid, snapshot_name) @test.attr(type=['negative', 'gate']) def test_create_image_specify_uuid_37_characters_or_more(self): # Return an error if Image ID passed is 37 characters or more snapshot_name = data_utils.rand_name('test-snap-') test_uuid = ('a' * 37) self.assertRaises(exceptions.NotFound, self.servers_client.create_image, test_uuid, snapshot_name) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/__init__.py0000664000175000017500000000000012332757070024432 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/test_extensions.py0000664000175000017500000000374312332757070026152 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ExtensionsV3Test(base.BaseV3ComputeTest): @test.attr(type='gate') def test_list_extensions(self): # List of all extensions if len(CONF.compute_feature_enabled.api_v3_extensions) == 0: raise self.skipException('There are not any extensions configured') resp, extensions = self.extensions_client.list_extensions() self.assertEqual(200, resp.status) ext = CONF.compute_feature_enabled.api_v3_extensions[0] if ext == 'all': self.assertIn('Hosts', map(lambda x: x['name'], extensions)) elif ext: self.assertIn(ext, map(lambda x: x['name'], extensions)) else: raise self.skipException('There are not any extensions configured') # Log extensions list extension_list = map(lambda x: x['name'], extensions) LOG.debug("Nova extensions: %s" % ','.join(extension_list)) @test.attr(type='gate') def test_get_extension(self): # get the specified extensions resp, extension = self.extensions_client.get_extension('servers') self.assertEqual(200, resp.status) self.assertEqual('servers', extension['alias']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/test_live_block_migration_negative.py0000664000175000017500000000403212332757070032007 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class LiveBlockMigrationV3NegativeTest(base.BaseV3ComputeAdminTest): _host_key = 'os-extended-server-attributes:host' @classmethod def setUpClass(cls): super(LiveBlockMigrationV3NegativeTest, cls).setUpClass() if not CONF.compute_feature_enabled.live_migration: raise cls.skipException("Live migration is not enabled") cls.admin_hosts_client = cls.hosts_admin_client cls.admin_servers_client = cls.servers_admin_client def _migrate_server_to(self, server_id, dest_host): _resp, body = self.admin_servers_client.live_migrate_server( server_id, dest_host, CONF.compute_feature_enabled. block_migration_for_live_migration) return body @test.attr(type=['negative', 'gate']) def test_invalid_host_for_migration(self): # Migrating to an invalid host should not change the status target_host = data_utils.rand_name('host-') _, server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] self.assertRaises(exceptions.BadRequest, self._migrate_server_to, server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/test_quotas.py0000664000175000017500000000651512332757070025267 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class QuotasV3Test(base.BaseV3ComputeTest): @classmethod def setUpClass(cls): super(QuotasV3Test, cls).setUpClass() cls.client = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] resp, users = cls.admin_client.list_users_for_tenant(cls.tenant_id) cls.user_id = [user['id'] for user in users if user['name'] == cls.client.user][0] cls.default_quota_set = set(('metadata_items', 'ram', 'floating_ips', 'fixed_ips', 'key_pairs', 'instances', 'security_group_rules', 'cores', 'security_groups')) @test.attr(type='smoke') def test_get_quotas(self): # User can get the quota set for it's tenant expected_quota_set = self.default_quota_set | set(['id']) resp, quota_set = self.client.get_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.tenant_id) # get the quota set using user id resp, quota_set = self.client.get_quota_set(self.tenant_id, self.user_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.tenant_id) @test.attr(type='smoke') def test_get_default_quotas(self): # User can get the default quota set for it's tenant expected_quota_set = self.default_quota_set | set(['id']) resp, quota_set = self.client.get_default_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.tenant_id) @test.attr(type='smoke') def test_compare_tenant_quotas_with_default_quotas(self): # Tenants are created with the default quota values resp, defualt_quota_set = \ self.client.get_default_quota_set(self.tenant_id) self.assertEqual(200, resp.status) resp, tenant_quota_set = self.client.get_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(defualt_quota_set, tenant_quota_set) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/0000775000175000017500000000000012332757136023426 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py0000664000175000017500000001173112332757070032775 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # Copyright 2013 IBM Corp. # # 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 tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsExtraSpecsNegativeV3Test(base.BaseV3ComputeAdminTest): """ Negative Tests Flavor Extra Spec API extension. SET, UNSET, UPDATE Flavor Extra specs require admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsExtraSpecsNegativeV3Test, cls).setUpClass() cls.client = cls.flavors_admin_client flavor_name = data_utils.rand_name('test_flavor') ram = 512 vcpus = 1 disk = 10 ephemeral = 10 cls.new_flavor_id = data_utils.rand_int_id(start=1000) swap = 1024 rxtx = 1 # Create a flavor resp, cls.flavor = cls.client.create_flavor(flavor_name, ram, vcpus, disk, cls.new_flavor_id, ephemeral=ephemeral, swap=swap, rxtx=rxtx) @classmethod def tearDownClass(cls): resp, body = cls.client.delete_flavor(cls.flavor['id']) cls.client.wait_for_resource_deletion(cls.flavor['id']) super(FlavorsExtraSpecsNegativeV3Test, cls).tearDownClass() @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_set_keys(self): # Test to SET flavor extra spec as a user without admin privileges. specs = {"key1": "value1", "key2": "value2"} self.assertRaises(exceptions.Unauthorized, self.flavors_client.set_flavor_extra_spec, self.flavor['id'], specs) @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_update_specific_key(self): # non admin user is not allowed to update flavor extra spec specs = {"key1": "value1", "key2": "value2"} resp, body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) self.assertEqual(resp.status, 201) self.assertEqual(body['key1'], 'value1') self.assertRaises(exceptions.Unauthorized, self.flavors_client. update_flavor_extra_spec, self.flavor['id'], 'key1', key1='value1_new') @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_unset_keys(self): specs = {"key1": "value1", "key2": "value2"} set_resp, set_body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) self.assertRaises(exceptions.Unauthorized, self.flavors_client.unset_flavor_extra_spec, self.flavor['id'], 'key1') @test.attr(type=['negative', 'gate']) def test_flavor_unset_nonexistent_key(self): nonexistent_key = data_utils.rand_name('flavor_key') self.assertRaises(exceptions.NotFound, self.client.unset_flavor_extra_spec, self.flavor['id'], nonexistent_key) @test.attr(type=['negative', 'gate']) def test_flavor_get_nonexistent_key(self): self.assertRaises(exceptions.NotFound, self.flavors_client.get_flavor_extra_spec_with_key, self.flavor['id'], "nonexistent_key") @test.attr(type=['negative', 'gate']) def test_flavor_update_mismatch_key(self): # the key will be updated should be match the key in the body self.assertRaises(exceptions.BadRequest, self.client.update_flavor_extra_spec, self.flavor['id'], "key2", key1="value") @test.attr(type=['negative', 'gate']) def test_flavor_update_more_key(self): # there should be just one item in the request body self.assertRaises(exceptions.BadRequest, self.client.update_flavor_extra_spec, self.flavor['id'], "key1", key1="value", key2="value") tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_hosts_negative.py0000664000175000017500000001425712332757070030067 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # # 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 tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class HostsAdminNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests hosts API using admin privileges. """ @classmethod def setUpClass(cls): super(HostsAdminNegativeV3Test, cls).setUpClass() cls.client = cls.hosts_admin_client cls.non_admin_client = cls.hosts_client def _get_host_name(self): resp, hosts = self.client.list_hosts() self.assertEqual(200, resp.status) self.assertTrue(len(hosts) >= 1) hostname = hosts[0]['host_name'] return hostname @test.attr(type=['negative', 'gate']) def test_list_hosts_with_non_admin_user(self): self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_hosts) @test.attr(type=['negative', 'gate']) def test_show_host_detail_with_nonexistent_hostname(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.show_host_detail, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_show_host_detail_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.show_host_detail, hostname) @test.attr(type=['negative', 'gate']) def test_update_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.update_host, hostname, status='enable', maintenance_mode='enable') @test.attr(type=['negative', 'gate']) def test_update_host_with_extra_param(self): # only 'status' and 'maintenance_mode' are the valid params. hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname, status='enable', maintenance_mode='enable', param='XXX') @test.attr(type=['negative', 'gate']) def test_update_host_with_invalid_status(self): # 'status' can only be 'enable' or 'disable' hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname, status='invalid', maintenance_mode='enable') @test.attr(type=['negative', 'gate']) def test_update_host_with_invalid_maintenance_mode(self): # 'maintenance_mode' can only be 'enable' or 'disable' hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname, status='enable', maintenance_mode='invalid') @test.attr(type=['negative', 'gate']) def test_update_host_without_param(self): # 'status' or 'maintenance_mode' needed for host update hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname) @test.attr(type=['negative', 'gate']) def test_update_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.update_host, nonexitent_hostname, status='enable', maintenance_mode='enable') @test.attr(type=['negative', 'gate']) def test_startup_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.startup_host, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_startup_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.startup_host, hostname) @test.attr(type=['negative', 'gate']) def test_shutdown_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.shutdown_host, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_shutdown_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.shutdown_host, hostname) @test.attr(type=['negative', 'gate']) def test_reboot_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.reboot_host, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_reboot_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.reboot_host, hostname) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_flavors.py0000664000175000017500000003234612332757070026520 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests Flavors API Create and Delete that require admin privileges """ @classmethod def setUpClass(cls): super(FlavorsAdminV3Test, cls).setUpClass() cls.client = cls.flavors_admin_client cls.user_client = cls.flavors_client cls.flavor_name_prefix = 'test_flavor_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 cls.ephemeral = 10 cls.swap = 1024 cls.rxtx = 2 def flavor_clean_up(self, flavor_id): resp, body = self.client.delete_flavor(flavor_id) self.assertEqual(resp.status, 204) self.client.wait_for_resource_deletion(flavor_id) def _create_flavor(self, flavor_id): # Create a flavor and ensure it is listed # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(201, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(flavor['ram'], self.ram) self.assertEqual(flavor['swap'], self.swap) if test.is_extension_enabled("os-flavor-rxtx", "compute_v3"): self.assertEqual(flavor['os-flavor-rxtx:rxtx_factor'], self.rxtx) self.assertEqual(flavor['ephemeral'], self.ephemeral) self.assertEqual(flavor['flavor-access:is_public'], True) # Verify flavor is retrieved resp, flavor = self.client.get_flavor_details(flavor['id']) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) return flavor['id'] @test.attr(type='gate') def test_create_flavor_with_int_id(self): flavor_id = data_utils.rand_int_id(start=1000) new_flavor_id = self._create_flavor(flavor_id) self.assertEqual(new_flavor_id, str(flavor_id)) @test.attr(type='gate') def test_create_flavor_with_uuid_id(self): flavor_id = str(uuid.uuid4()) new_flavor_id = self._create_flavor(flavor_id) self.assertEqual(new_flavor_id, flavor_id) @test.attr(type='gate') def test_create_flavor_with_none_id(self): # If nova receives a request with None as flavor_id, # nova generates flavor_id of uuid. flavor_id = None new_flavor_id = self._create_flavor(flavor_id) self.assertEqual(new_flavor_id, str(uuid.UUID(new_flavor_id))) @test.attr(type='gate') def test_create_flavor_verify_entry_in_list_details(self): # Create a flavor and ensure it's details are listed # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.addCleanup(self.flavor_clean_up, flavor['id']) flag = False # Verify flavor is retrieved resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) @test.attr(type='gate') def test_create_list_flavor_without_extra_data(self): # Create a flavor and ensure it is listed # This operation requires the user to have 'admin' role def verify_flavor_response_extension(flavor): # check some extensions for the flavor create/show/detail response self.assertEqual(flavor['swap'], 0) if test.is_extension_enabled("os-flavor-rxtx", "compute_v3"): self.assertEqual(int(flavor['os-flavor-rxtx:rxtx_factor']), 1) self.assertEqual(int(flavor['ephemeral']), 0) self.assertEqual(flavor['flavor-access:is_public'], True) flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(201, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['ram'], self.ram) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(int(flavor['id']), new_flavor_id) verify_flavor_response_extension(flavor) # Verify flavor is retrieved resp, flavor = self.client.get_flavor_details(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) verify_flavor_response_extension(flavor) # Check if flavor is present in list resp, flavors = self.user_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: verify_flavor_response_extension(flavor) flag = True self.assertTrue(flag) @test.attr(type='gate') def test_list_non_public_flavor(self): # Create a flavor with os-flavor-access:is_public false should # be present in list_details. # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) # Verify flavor is retrieved flag = False resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) # Verify flavor is not retrieved with other user flag = False resp, flavors = self.user_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertFalse(flag) @test.attr(type='gate') def test_create_server_with_non_public_flavor(self): # Create a flavor with os-flavor-access:is_public false flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(201, resp.status) # Verify flavor is not used by other user self.assertRaises(exceptions.BadRequest, self.servers_client.create_server, 'test', self.image_ref, flavor['id']) @test.attr(type='gate') def test_list_public_flavor_with_other_user(self): # Create a Flavor with public access. # Try to List/Get flavor with another user flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="True") self.addCleanup(self.flavor_clean_up, flavor['id']) flag = False self.new_client = self.flavors_client # Verify flavor is retrieved with new user resp, flavors = self.new_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) @test.attr(type='gate') def test_is_public_string_variations(self): flavor_id_not_public = data_utils.rand_int_id(start=1000) flavor_name_not_public = data_utils.rand_name(self.flavor_name_prefix) flavor_id_public = data_utils.rand_int_id(start=1000) flavor_name_public = data_utils.rand_name(self.flavor_name_prefix) # Create a non public flavor resp, flavor = self.client.create_flavor(flavor_name_not_public, self.ram, self.vcpus, self.disk, flavor_id_not_public, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) # Create a public flavor resp, flavor = self.client.create_flavor(flavor_name_public, self.ram, self.vcpus, self.disk, flavor_id_public, is_public="True") self.addCleanup(self.flavor_clean_up, flavor['id']) def _flavor_lookup(flavors, flavor_name): for flavor in flavors: if flavor['name'] == flavor_name: return flavor return None def _test_string_variations(variations, flavor_name): for string in variations: params = {'is_public': string} r, flavors = self.client.list_flavors_with_detail(params) self.assertEqual(r.status, 200) flavor = _flavor_lookup(flavors, flavor_name) self.assertIsNotNone(flavor) _test_string_variations(['f', 'false', 'no', '0'], flavor_name_not_public) _test_string_variations(['t', 'true', 'yes', '1'], flavor_name_public) @test.attr(type='gate') def test_create_flavor_using_string_ram(self): flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) ram = " 1024 " resp, flavor = self.client.create_flavor(flavor_name, ram, self.vcpus, self.disk, new_flavor_id) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(201, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(flavor['ram'], int(ram)) self.assertEqual(int(flavor['id']), new_flavor_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_services_negative.py0000664000175000017500000000475512332757070030554 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import exceptions from tempest.test import attr class ServicesAdminNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests Services API. List and Enable/Disable require admin privileges. """ @classmethod def setUpClass(cls): super(ServicesAdminNegativeV3Test, cls).setUpClass() cls.client = cls.services_admin_client cls.non_admin_client = cls.services_client @attr(type=['negative', 'gate']) def test_list_services_with_non_admin_user(self): self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_services) @attr(type=['negative', 'gate']) def test_get_service_by_invalid_params(self): # return all services if send the request with invalid parameter resp, services = self.client.list_services() params = {'xxx': 'nova-compute'} resp, services_xxx = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(len(services), len(services_xxx)) @attr(type=['negative', 'gate']) def test_get_service_by_invalid_service_and_valid_host(self): resp, services = self.client.list_services() host_name = services[0]['host'] params = {'host': host_name, 'binary': 'xxx'} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(services)) @attr(type=['negative', 'gate']) def test_get_service_with_valid_service_and_invalid_host(self): resp, services = self.client.list_services() binary_name = services[0]['binary'] params = {'host': 'xxx', 'binary': binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(services)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_hosts.py0000664000175000017500000000631712332757070026203 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest import test class HostsAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests hosts API using admin privileges. """ @classmethod def setUpClass(cls): super(HostsAdminV3Test, cls).setUpClass() cls.client = cls.hosts_admin_client @test.attr(type='gate') def test_list_hosts(self): resp, hosts = self.client.list_hosts() self.assertEqual(200, resp.status) self.assertTrue(len(hosts) >= 2, str(hosts)) @test.attr(type='gate') def test_list_hosts_with_zone(self): self.useFixture(fixtures.LockFixture('availability_zone')) resp, hosts = self.client.list_hosts() host = hosts[0] zone_name = host['zone'] params = {'zone': zone_name} resp, hosts = self.client.list_hosts(params) self.assertEqual(200, resp.status) self.assertTrue(len(hosts) >= 1) self.assertIn(host, hosts) @test.attr(type='gate') def test_list_hosts_with_a_blank_zone(self): # If send the request with a blank zone, the request will be successful # and it will return all the hosts list params = {'zone': ''} resp, hosts = self.client.list_hosts(params) self.assertNotEqual(0, len(hosts)) self.assertEqual(200, resp.status) @test.attr(type='gate') def test_list_hosts_with_nonexistent_zone(self): # If send the request with a nonexistent zone, the request will be # successful and no hosts will be retured params = {'zone': 'xxx'} resp, hosts = self.client.list_hosts(params) self.assertEqual(0, len(hosts)) self.assertEqual(200, resp.status) @test.attr(type='gate') def test_show_host_detail(self): resp, hosts = self.client.list_hosts() self.assertEqual(200, resp.status) hosts = [host for host in hosts if host['service'] == 'compute'] self.assertTrue(len(hosts) >= 1) for host in hosts: hostname = host['host_name'] resp, resources = self.client.show_host_detail(hostname) self.assertEqual(200, resp.status) self.assertTrue(len(resources) >= 1) host_resource = resources[0]['resource'] self.assertIsNotNone(host_resource) self.assertIsNotNone(host_resource['cpu']) self.assertIsNotNone(host_resource['disk_gb']) self.assertIsNotNone(host_resource['memory_mb']) self.assertIsNotNone(host_resource['project']) self.assertEqual(hostname, host_resource['host']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_migrations.py0000664000175000017500000000366012332757070027215 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class MigrationsAdminV3Test(base.BaseV3ComputeAdminTest): @test.attr(type='gate') def test_list_migrations(self): # Admin can get the migrations list resp, _ = self.migrations_admin_client.list_migrations() self.assertEqual(200, resp.status) @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_list_migrations_in_flavor_resize_situation(self): # Admin can get the migrations list which contains the resized server resp, server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] resp, _ = self.servers_client.resize(server_id, self.flavor_ref_alt) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'VERIFY_RESIZE') self.servers_client.confirm_resize(server_id) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') resp, body = self.migrations_admin_client.list_migrations() self.assertEqual(200, resp.status) instance_uuids = [x['instance_uuid'] for x in body] self.assertIn(server_id, instance_uuids) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_agents.py0000664000175000017500000000731612332757070026324 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class AgentsAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests Agents API that require admin privileges """ @classmethod def setUpClass(cls): super(AgentsAdminV3Test, cls).setUpClass() cls.client = cls.agents_admin_client @test.attr(type='gate') def test_create_update_list_delete_agents(self): """ 1. Create 2 agents. 2. Update one of the agents. 3. List all agent builds. 4. List the agent builds by the filter. 5. Delete agents. """ params_kvm = expected_kvm = {'hypervisor': 'kvm', 'os': 'win', 'architecture': 'x86', 'version': '7.0', 'url': 'xxx://xxxx/xxx/xxx', 'md5hash': ("""add6bb58e139be103324d04d""" """82d8f545""")} resp, agent_kvm = self.client.create_agent(**params_kvm) self.assertEqual(201, resp.status) for expected_item, value in expected_kvm.items(): self.assertEqual(value, agent_kvm[expected_item]) params_xen = expected_xen = {'hypervisor': 'xen', 'os': 'linux', 'architecture': 'x86', 'version': '7.0', 'url': 'xxx://xxxx/xxx/xxx1', 'md5hash': """add6bb58e139be103324d04d8""" """2d8f546"""} resp, agent_xen = self.client.create_agent(**params_xen) self.assertEqual(201, resp.status) for expected_item, value in expected_xen.items(): self.assertEqual(value, agent_xen[expected_item]) params_kvm_new = expected_kvm_new = {'version': '8.0', 'url': 'xxx://xxxx/xxx/xxx2', 'md5hash': """add6bb58e139be103""" """324d04d82d8f547"""} resp, resp_agent_kvm = self.client.update_agent(agent_kvm['agent_id'], **params_kvm_new) self.assertEqual(200, resp.status) for expected_item, value in expected_kvm_new.items(): self.assertEqual(value, resp_agent_kvm[expected_item]) resp, agents = self.client.list_agents() self.assertEqual(200, resp.status) self.assertTrue(len(agents) > 1) params_filter = {'hypervisor': 'kvm'} resp, agent = self.client.list_agents(params_filter) self.assertEqual(200, resp.status) self.assertTrue(len(agent) > 0) self.assertEqual('kvm', agent[0]['hypervisor']) resp, _ = self.client.delete_agent(agent_kvm['agent_id']) self.assertEqual(204, resp.status) resp, _ = self.client.delete_agent(agent_xen['agent_id']) self.assertEqual(204, resp.status) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_servers_negative.py0000664000175000017500000001314612332757070030414 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # # 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 uuid import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.test import attr CONF = config.CONF class ServersAdminNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests Servers API using admin privileges """ @classmethod def setUpClass(cls): super(ServersAdminNegativeV3Test, cls).setUpClass() cls.client = cls.servers_admin_client cls.non_adm_client = cls.servers_client cls.flavors_client = cls.flavors_admin_client cls.identity_client = cls._get_identity_admin_client() tenant = cls.identity_client.get_tenant_by_name( cls.client.tenant_name) cls.tenant_id = tenant['id'] cls.s1_name = data_utils.rand_name('server') resp, server = cls.create_test_server(name=cls.s1_name, wait_until='ACTIVE') cls.s1_id = server['id'] def _get_unused_flavor_id(self): flavor_id = data_utils.rand_int_id(start=1000) while True: try: resp, body = self.flavors_client.get_flavor_details(flavor_id) except exceptions.NotFound: break flavor_id = data_utils.rand_int_id(start=1000) return flavor_id @attr(type=['negative', 'gate']) def test_resize_server_using_overlimit_ram(self): flavor_name = data_utils.rand_name("flavor-") flavor_id = self._get_unused_flavor_id() resp, quota_set = self.quotas_client.get_default_quota_set( self.tenant_id) ram = int(quota_set['ram']) + 1 vcpus = 8 disk = 10 resp, flavor_ref = self.flavors_client.create_flavor(flavor_name, ram, vcpus, disk, flavor_id) self.addCleanup(self.flavors_client.delete_flavor, flavor_id) self.assertRaises(exceptions.OverLimit, self.client.resize, self.servers[0]['id'], flavor_ref['id']) @attr(type=['negative', 'gate']) def test_resize_server_using_overlimit_vcpus(self): flavor_name = data_utils.rand_name("flavor-") flavor_id = self._get_unused_flavor_id() ram = 512 resp, quota_set = self.quotas_client.get_default_quota_set( self.tenant_id) vcpus = int(quota_set['cores']) + 1 disk = 10 resp, flavor_ref = self.flavors_client.create_flavor(flavor_name, ram, vcpus, disk, flavor_id) self.addCleanup(self.flavors_client.delete_flavor, flavor_id) self.assertRaises(exceptions.OverLimit, self.client.resize, self.servers[0]['id'], flavor_ref['id']) @attr(type=['negative', 'gate']) def test_reset_state_server_invalid_state(self): self.assertRaises(exceptions.BadRequest, self.client.reset_state, self.s1_id, state='invalid') @attr(type=['negative', 'gate']) def test_reset_state_server_invalid_type(self): self.assertRaises(exceptions.BadRequest, self.client.reset_state, self.s1_id, state=1) @attr(type=['negative', 'gate']) def test_reset_state_server_nonexistent_server(self): self.assertRaises(exceptions.NotFound, self.client.reset_state, '999') @attr(type=['negative', 'gate']) def test_get_server_diagnostics_by_non_admin(self): # Non-admin user can not view server diagnostics according to policy self.assertRaises(exceptions.Unauthorized, self.non_adm_client.get_server_diagnostics, self.s1_id) @attr(type=['negative', 'gate']) def test_migrate_non_existent_server(self): # migrate a non existent server self.assertRaises(exceptions.NotFound, self.client.migrate_server, str(uuid.uuid4())) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @attr(type=['negative', 'gate']) def test_migrate_server_invalid_state(self): # create server. resp, server = self.create_test_server(wait_until='ACTIVE') self.assertEqual(202, resp.status) server_id = server['id'] # suspend the server. resp, _ = self.client.suspend_server(server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(server_id, 'SUSPENDED') # migrate an suspended server should fail self.assertRaises(exceptions.Conflict, self.client.migrate_server, server_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_availability_zone_negative.py0000664000175000017500000000256412332757070032432 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import exceptions from tempest.test import attr class AZAdminNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests Availability Zone API List """ @classmethod def setUpClass(cls): super(AZAdminNegativeV3Test, cls).setUpClass() cls.client = cls.availability_zone_admin_client cls.non_adm_client = cls.availability_zone_client @attr(type=['negative', 'gate']) def test_get_availability_zone_list_detail_with_non_admin_user(self): # List of availability zones and available services with # non-administrator user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_availability_zone_list_detail) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_quotas_negative.py0000664000175000017500000000762212332757070030241 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2014 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import exceptions from tempest import test class QuotasAdminNegativeV3Test(base.BaseV3ComputeAdminTest): force_tenant_isolation = True @classmethod def setUpClass(cls): super(QuotasAdminNegativeV3Test, cls).setUpClass() cls.client = cls.quotas_client cls.adm_client = cls.quotas_admin_client # NOTE(afazekas): these test cases should always create and use a new # tenant most of them should be skipped if we can't do that cls.demo_tenant_id = cls.isolated_creds.get_primary_creds().tenant_id # TODO(afazekas): Add dedicated tenant to the skiped quota tests # it can be moved into the setUpClass as well @test.attr(type=['negative', 'gate']) def test_create_server_when_cpu_quota_is_full(self): # Disallow server creation when tenant's vcpu quota is full resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_vcpu_quota = quota_set['cores'] vcpu_quota = 0 # Set the quota to zero to conserve resources resp, quota_set = self.adm_client.update_quota_set(self.demo_tenant_id, force=True, cores=vcpu_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, cores=default_vcpu_quota) self.assertRaises(exceptions.OverLimit, self.create_test_server) @test.attr(type=['negative', 'gate']) def test_create_server_when_memory_quota_is_full(self): # Disallow server creation when tenant's memory quota is full resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_mem_quota = quota_set['ram'] mem_quota = 0 # Set the quota to zero to conserve resources self.adm_client.update_quota_set(self.demo_tenant_id, force=True, ram=mem_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, ram=default_mem_quota) self.assertRaises(exceptions.OverLimit, self.create_test_server) @test.attr(type=['negative', 'gate']) def test_update_quota_normal_user(self): self.assertRaises(exceptions.Unauthorized, self.client.update_quota_set, self.demo_tenant_id, ram=0) @test.attr(type=['negative', 'gate']) def test_create_server_when_instances_quota_is_full(self): # Once instances quota limit is reached, disallow server creation resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_instances_quota = quota_set['instances'] instances_quota = 0 # Set quota to zero to disallow server creation self.adm_client.update_quota_set(self.demo_tenant_id, force=True, instances=instances_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, instances=default_instances_quota) self.assertRaises(exceptions.OverLimit, self.create_test_server) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_hypervisor.py0000664000175000017500000000666112332757070027257 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.test import attr class HypervisorAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests Hypervisors API that require admin privileges """ @classmethod def setUpClass(cls): super(HypervisorAdminV3Test, cls).setUpClass() cls.client = cls.hypervisor_admin_client def _list_hypervisors(self): # List of hypervisors resp, hypers = self.client.get_hypervisor_list() self.assertEqual(200, resp.status) return hypers @attr(type='gate') def test_get_hypervisor_list(self): # List of hypervisor and available hypervisors hostname hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) @attr(type='gate') def test_get_hypervisor_list_details(self): # Display the details of the all hypervisor resp, hypers = self.client.get_hypervisor_list_details() self.assertEqual(200, resp.status) self.assertTrue(len(hypers) > 0) @attr(type='gate') def test_get_hypervisor_show_details(self): # Display the details of the specified hypervisor hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) resp, details = (self.client. get_hypervisor_show_details(hypers[0]['id'])) self.assertEqual(200, resp.status) self.assertTrue(len(details) > 0) self.assertEqual(details['hypervisor_hostname'], hypers[0]['hypervisor_hostname']) @attr(type='gate') def test_get_hypervisor_show_servers(self): # Show instances about the specific hypervisors hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) hypervisor_id = hypers[0]['id'] resp, hypervisors = self.client.get_hypervisor_servers(hypervisor_id) self.assertEqual(200, resp.status) self.assertTrue(len(hypervisors) > 0) @attr(type='gate') def test_get_hypervisor_stats(self): # Verify the stats of the all hypervisor resp, stats = self.client.get_hypervisor_stats() self.assertEqual(200, resp.status) self.assertTrue(len(stats) > 0) @attr(type='gate') def test_get_hypervisor_uptime(self): # Verify that GET shows the specified hypervisor uptime hypers = self._list_hypervisors() resp, uptime = self.client.get_hypervisor_uptime(hypers[0]['id']) self.assertEqual(200, resp.status) self.assertTrue(len(uptime) > 0) @attr(type='gate') def test_search_hypervisor(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) resp, hypers = self.client.search_hypervisor( hypers[0]['hypervisor_hostname']) self.assertEqual(200, resp.status) self.assertTrue(len(hypers) > 0) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_flavors_access_negative.py0000664000175000017500000001526612332757070031725 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsAccessNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests Flavor Access API extension. Add and remove Flavor Access require admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsAccessNegativeV3Test, cls).setUpClass() cls.client = cls.flavors_admin_client admin_client = cls._get_identity_admin_client() cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client. tenant_name) cls.tenant_id = cls.tenant['id'] cls.adm_tenant = admin_client.get_tenant_by_name( cls.flavors_admin_client.tenant_name) cls.adm_tenant_id = cls.adm_tenant['id'] cls.flavor_name_prefix = 'test_flavor_access_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 @test.attr(type=['negative', 'gate']) def test_flavor_access_list_with_public_flavor(self): # Test to list flavor access with exceptions by querying public flavor flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='True') self.addCleanup(self.client.delete_flavor, new_flavor['id']) self.assertEqual(resp.status, 201) self.assertRaises(exceptions.NotFound, self.client.list_flavor_access, new_flavor_id) @test.skip_because(bug='1265416') @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_add(self): # Test to add flavor access as a user without admin privileges. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) self.assertRaises(exceptions.Unauthorized, self.flavors_client.add_flavor_access, new_flavor['id'], self.tenant_id) @test.skip_because(bug='1265416') @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_remove(self): # Test to remove flavor access as a user without admin privileges. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # Add flavor access to a tenant. self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.addCleanup(self.client.remove_flavor_access, new_flavor['id'], self.tenant_id) self.assertRaises(exceptions.Unauthorized, self.flavors_client.remove_flavor_access, new_flavor['id'], self.tenant_id) @test.skip_because(bug='1265416') @test.attr(type=['negative', 'gate']) def test_add_flavor_access_duplicate(self): # Create a new flavor. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # Add flavor access to a tenant. self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.addCleanup(self.client.remove_flavor_access, new_flavor['id'], self.tenant_id) # An exception should be raised when adding flavor access to the same # tenant self.assertRaises(exceptions.Conflict, self.client.add_flavor_access, new_flavor['id'], self.tenant_id) @test.skip_because(bug='1265416') @test.attr(type=['negative', 'gate']) def test_remove_flavor_access_not_found(self): # Create a new flavor. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # An exception should be raised when flavor access is not found self.assertRaises(exceptions.NotFound, self.client.remove_flavor_access, new_flavor['id'], str(uuid.uuid4())) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_hypervisor_negative.py0000664000175000017500000001114512332757070031132 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.test import attr class HypervisorAdminNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests Hypervisors API that require admin privileges """ @classmethod def setUpClass(cls): super(HypervisorAdminNegativeV3Test, cls).setUpClass() cls.client = cls.hypervisor_admin_client cls.non_adm_client = cls.hypervisor_client def _list_hypervisors(self): # List of hypervisors resp, hypers = self.client.get_hypervisor_list() self.assertEqual(200, resp.status) return hypers @attr(type=['negative', 'gate']) def test_show_nonexistent_hypervisor(self): nonexistent_hypervisor_id = str(uuid.uuid4()) self.assertRaises( exceptions.NotFound, self.client.get_hypervisor_show_details, nonexistent_hypervisor_id) @attr(type=['negative', 'gate']) def test_show_hypervisor_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_show_details, hypers[0]['id']) @attr(type=['negative', 'gate']) def test_show_servers_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_servers, hypers[0]['id']) @attr(type=['negative', 'gate']) def test_show_servers_with_nonexistent_hypervisor(self): nonexistent_hypervisor_id = str(uuid.uuid4()) self.assertRaises( exceptions.NotFound, self.client.get_hypervisor_servers, nonexistent_hypervisor_id) @attr(type=['negative', 'gate']) def test_get_hypervisor_stats_with_non_admin_user(self): self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_stats) @attr(type=['negative', 'gate']) def test_get_nonexistent_hypervisor_uptime(self): nonexistent_hypervisor_id = str(uuid.uuid4()) self.assertRaises( exceptions.NotFound, self.client.get_hypervisor_uptime, nonexistent_hypervisor_id) @attr(type=['negative', 'gate']) def test_get_hypervisor_uptime_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_uptime, hypers[0]['id']) @attr(type=['negative', 'gate']) def test_get_hypervisor_list_with_non_admin_user(self): # List of hypervisor and available services with non admin user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_list) @attr(type=['negative', 'gate']) def test_get_hypervisor_list_details_with_non_admin_user(self): # List of hypervisor details and available services with non admin user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_list_details) @attr(type=['negative', 'gate']) def test_search_nonexistent_hypervisor(self): nonexistent_hypervisor_name = data_utils.rand_name('test_hypervisor') resp, hypers = self.client.search_hypervisor( nonexistent_hypervisor_name) self.assertEqual(200, resp.status) self.assertEqual(0, len(hypers)) @attr(type=['negative', 'gate']) def test_search_hypervisor_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.search_hypervisor, hypers[0]['hypervisor_hostname']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/__init__.py0000664000175000017500000000000012332757070025522 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_servers.py0000664000175000017500000001702212332757070026527 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class ServersAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests Servers API using admin privileges """ _host_key = 'os-extended-server-attributes:host' @classmethod @test.safe_setup def setUpClass(cls): super(ServersAdminV3Test, cls).setUpClass() cls.client = cls.servers_admin_client cls.non_admin_client = cls.servers_client cls.flavors_client = cls.flavors_admin_client cls.s1_name = data_utils.rand_name('server') resp, server = cls.create_test_server(name=cls.s1_name, wait_until='ACTIVE') cls.s1_id = server['id'] cls.s2_name = data_utils.rand_name('server') resp, server = cls.create_test_server(name=cls.s2_name, wait_until='ACTIVE') cls.s2_id = server['id'] @test.attr(type='gate') def test_list_servers_by_admin(self): # Listing servers by admin user returns empty list by default resp, body = self.client.list_servers_with_detail() servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.skip_because(bug='1265416') @test.attr(type='gate') def test_list_servers_by_admin_with_all_tenants(self): # Listing servers by admin user with all tenants parameter # Here should be listed all servers params = {'all_tenants': ''} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] servers_name = map(lambda x: x['name'], servers) self.assertIn(self.s1_name, servers_name) self.assertIn(self.s2_name, servers_name) @test.attr(type='gate') def test_list_servers_filter_by_existent_host(self): # Filter the list of servers by existent host name = data_utils.rand_name('server') flavor = self.flavor_ref image_id = self.image_ref resp, test_server = self.client.create_server( name, image_id, flavor) self.assertEqual('202', resp['status']) self.addCleanup(self.client.delete_server, test_server['id']) self.client.wait_for_server_status(test_server['id'], 'ACTIVE') resp, server = self.client.get_server(test_server['id']) self.assertEqual(server['status'], 'ACTIVE') hostname = server[self._host_key] params = {'host': hostname} resp, body = self.client.list_servers(params) self.assertEqual('200', resp['status']) servers = body['servers'] nonexistent_params = {'host': 'nonexistent_host'} resp, nonexistent_body = self.client.list_servers( nonexistent_params) self.assertEqual('200', resp['status']) nonexistent_servers = nonexistent_body['servers'] self.assertIn(test_server['id'], map(lambda x: x['id'], servers)) self.assertNotIn(test_server['id'], map(lambda x: x['id'], nonexistent_servers)) @test.attr(type='gate') def test_reset_state_server(self): # Reset server's state to 'error' resp, server = self.client.reset_state(self.s1_id) self.assertEqual(202, resp.status) # Verify server's state resp, server = self.client.get_server(self.s1_id) self.assertEqual(server['status'], 'ERROR') # Reset server's state to 'active' resp, server = self.client.reset_state(self.s1_id, state='active') self.assertEqual(202, resp.status) # Verify server's state resp, server = self.client.get_server(self.s1_id) self.assertEqual(server['status'], 'ACTIVE') @test.attr(type='gate') @test.skip_because(bug="1240043") def test_get_server_diagnostics_by_admin(self): # Retrieve server diagnostics by admin user resp, diagnostic = self.client.get_server_diagnostics(self.s1_id) self.assertEqual(200, resp.status) basic_attrs = ['rx_packets', 'rx_errors', 'rx_drop', 'tx_packets', 'tx_errors', 'tx_drop', 'read_req', 'write_req', 'cpu', 'memory'] for key in basic_attrs: self.assertIn(key, str(diagnostic.keys())) @test.attr(type='gate') def test_list_servers_filter_by_error_status(self): # Filter the list of servers by server error status params = {'status': 'error'} resp, server = self.client.reset_state(self.s1_id, state='error') resp, body = self.non_admin_client.list_servers(params) # Reset server's state to 'active' resp, server = self.client.reset_state(self.s1_id, state='active') # Verify server's state resp, server = self.client.get_server(self.s1_id) self.assertEqual(server['status'], 'ACTIVE') servers = body['servers'] # Verify error server in list result self.assertIn(self.s1_id, map(lambda x: x['id'], servers)) self.assertNotIn(self.s2_id, map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_rebuild_server_in_error_state(self): # The server in error state should be rebuilt using the provided # image and changed to ACTIVE state # resetting vm state require admin privilege resp, server = self.client.reset_state(self.s1_id, state='error') self.assertEqual(202, resp.status) resp, rebuilt_server = self.non_admin_client.rebuild( self.s1_id, self.image_ref_alt) self.addCleanup(self.non_admin_client.wait_for_server_status, self.s1_id, 'ACTIVE') self.addCleanup(self.non_admin_client.rebuild, self.s1_id, self.image_ref) # Verify the properties in the initial response are correct self.assertEqual(self.s1_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertEqual(self.image_ref_alt, rebuilt_image_id) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) self.non_admin_client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE', raise_on_error=False) # Verify the server properties after rebuilding resp, server = self.non_admin_client.get_server(rebuilt_server['id']) rebuilt_image_id = server['image']['id'] self.assertEqual(self.image_ref_alt, rebuilt_image_id) @test.attr(type='gate') def test_reset_network_inject_network_info(self): resp, server = self.create_test_server(wait_until='ACTIVE') # Reset Network of a Server resp, server_body = self.client.reset_network(server['id']) self.assertEqual(202, resp.status) # Inject the Network Info into Server resp, server = self.client.inject_network_info(server['id']) self.assertEqual(202, resp.status) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_aggregates_negative.py0000664000175000017500000002064612332757070031037 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class AggregatesAdminNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests Aggregates API that require admin privileges """ @classmethod def setUpClass(cls): super(AggregatesAdminNegativeV3Test, cls).setUpClass() cls.client = cls.aggregates_admin_client cls.user_client = cls.aggregates_client cls.aggregate_name_prefix = 'test_aggregate_' cls.az_name_prefix = 'test_az_' resp, hosts_all = cls.hosts_admin_client.list_hosts() hosts = map(lambda x: x['host_name'], filter(lambda y: y['service'] == 'compute', hosts_all)) cls.host = hosts[0] @test.attr(type=['negative', 'gate']) def test_aggregate_create_as_user(self): # Regular user is not allowed to create an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) self.assertRaises(exceptions.Unauthorized, self.user_client.create_aggregate, name=aggregate_name) @test.attr(type=['negative', 'gate']) def test_aggregate_create_aggregate_name_length_less_than_1(self): # the length of aggregate name should >= 1 and <=255 self.assertRaises(exceptions.BadRequest, self.client.create_aggregate, name='') @test.attr(type=['negative', 'gate']) def test_aggregate_create_aggregate_name_length_exceeds_255(self): # the length of aggregate name should >= 1 and <=255 aggregate_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.client.create_aggregate, name=aggregate_name) @test.attr(type=['negative', 'gate']) def test_aggregate_create_with_existent_aggregate_name(self): # creating an aggregate with existent aggregate name is forbidden aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Conflict, self.client.create_aggregate, name=aggregate_name) @test.attr(type=['negative', 'gate']) def test_aggregate_delete_as_user(self): # Regular user is not allowed to delete an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.delete_aggregate, aggregate['id']) @test.attr(type=['negative', 'gate']) def test_aggregate_list_as_user(self): # Regular user is not allowed to list aggregates. self.assertRaises(exceptions.Unauthorized, self.user_client.list_aggregates) @test.attr(type=['negative', 'gate']) def test_aggregate_get_details_as_user(self): # Regular user is not allowed to get aggregate details. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.get_aggregate, aggregate['id']) @test.attr(type=['negative', 'gate']) def test_aggregate_delete_with_invalid_id(self): # Delete an aggregate with invalid id should raise exceptions. self.assertRaises(exceptions.NotFound, self.client.delete_aggregate, -1) @test.attr(type=['negative', 'gate']) def test_aggregate_get_details_with_invalid_id(self): # Get aggregate details with invalid id should raise exceptions. self.assertRaises(exceptions.NotFound, self.client.get_aggregate, -1) @test.attr(type=['negative', 'gate']) def test_aggregate_add_non_exist_host(self): # Adding a non-exist host to an aggregate should raise exceptions. resp, hosts_all = self.hosts_admin_client.list_hosts() hosts = map(lambda x: x['host_name'], hosts_all) while True: non_exist_host = data_utils.rand_name('nonexist_host_') if non_exist_host not in hosts: break aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.NotFound, self.client.add_host, aggregate['id'], non_exist_host) @test.attr(type=['negative', 'gate']) def test_aggregate_add_host_as_user(self): # Regular user is not allowed to add a host to an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.add_host, aggregate['id'], self.host) @test.attr(type=['negative', 'gate']) def test_aggregate_add_existent_host(self): self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.add_host(aggregate['id'], self.host) self.assertEqual(202, resp.status) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) self.assertRaises(exceptions.Conflict, self.client.add_host, aggregate['id'], self.host) @test.attr(type=['negative', 'gate']) def test_aggregate_remove_host_as_user(self): # Regular user is not allowed to remove a host from an aggregate. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.add_host(aggregate['id'], self.host) self.assertEqual(202, resp.status) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) self.assertRaises(exceptions.Unauthorized, self.user_client.remove_host, aggregate['id'], self.host) @test.attr(type=['negative', 'gate']) def test_aggregate_remove_nonexistent_host(self): non_exist_host = data_utils.rand_name('nonexist_host_') aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.NotFound, self.client.remove_host, aggregate['id'], non_exist_host) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_aggregates.py0000664000175000017500000002347212332757070027155 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.common.utils import data_utils from tempest import test class AggregatesAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests Aggregates API that require admin privileges """ _host_key = 'os-extended-server-attributes:host' @classmethod def setUpClass(cls): super(AggregatesAdminV3Test, cls).setUpClass() cls.client = cls.aggregates_admin_client cls.aggregate_name_prefix = 'test_aggregate_' cls.az_name_prefix = 'test_az_' resp, hosts_all = cls.hosts_admin_client.list_hosts() hosts = map(lambda x: x['host_name'], filter(lambda y: y['service'] == 'compute', hosts_all)) cls.host = hosts[0] @test.attr(type='gate') def test_aggregate_create_delete(self): # Create and delete an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(201, resp.status) self.assertEqual(aggregate_name, aggregate['name']) self.assertIsNone(aggregate['availability_zone']) resp, _ = self.client.delete_aggregate(aggregate['id']) self.assertEqual(204, resp.status) self.client.wait_for_resource_deletion(aggregate['id']) @test.attr(type='gate') def test_aggregate_create_delete_with_az(self): # Create and delete an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) az_name = data_utils.rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate( name=aggregate_name, availability_zone=az_name) self.assertEqual(201, resp.status) self.assertEqual(aggregate_name, aggregate['name']) self.assertEqual(az_name, aggregate['availability_zone']) resp, _ = self.client.delete_aggregate(aggregate['id']) self.assertEqual(204, resp.status) self.client.wait_for_resource_deletion(aggregate['id']) @test.attr(type='gate') def test_aggregate_create_verify_entry_in_list(self): # Create an aggregate and ensure it is listed. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, aggregates = self.client.list_aggregates() self.assertEqual(200, resp.status) self.assertIn((aggregate['id'], aggregate['availability_zone']), map(lambda x: (x['id'], x['availability_zone']), aggregates)) @test.attr(type='gate') def test_aggregate_create_update_metadata_get_details(self): # Create an aggregate and ensure its details are returned. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.get_aggregate(aggregate['id']) self.assertEqual(200, resp.status) self.assertEqual(aggregate['name'], body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertEqual({}, body["metadata"]) # set the metadata of the aggregate meta = {"key": "value"} resp, body = self.client.set_metadata(aggregate['id'], meta) self.assertEqual(200, resp.status) self.assertEqual(meta, body["metadata"]) # verify the metadata has been set resp, body = self.client.get_aggregate(aggregate['id']) self.assertEqual(200, resp.status) self.assertEqual(meta, body["metadata"]) @test.attr(type='gate') def test_aggregate_create_update_with_az(self): # Update an aggregate and ensure properties are updated correctly self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) az_name = data_utils.rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate( name=aggregate_name, availability_zone=az_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertEqual(201, resp.status) self.assertEqual(aggregate_name, aggregate['name']) self.assertEqual(az_name, aggregate['availability_zone']) self.assertIsNotNone(aggregate['id']) aggregate_id = aggregate['id'] new_aggregate_name = aggregate_name + '_new' new_az_name = az_name + '_new' resp, resp_aggregate = self.client.update_aggregate(aggregate_id, new_aggregate_name, new_az_name) self.assertEqual(200, resp.status) self.assertEqual(new_aggregate_name, resp_aggregate['name']) self.assertEqual(new_az_name, resp_aggregate['availability_zone']) resp, aggregates = self.client.list_aggregates() self.assertEqual(200, resp.status) self.assertIn((aggregate_id, new_aggregate_name, new_az_name), map(lambda x: (x['id'], x['name'], x['availability_zone']), aggregates)) @test.attr(type='gate') def test_aggregate_add_remove_host(self): # Add an host to the given aggregate and remove. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.add_host(aggregate['id'], self.host) self.assertEqual(202, resp.status) self.assertEqual(aggregate_name, body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertIn(self.host, body['hosts']) resp, body = self.client.remove_host(aggregate['id'], self.host) self.assertEqual(202, resp.status) self.assertEqual(aggregate_name, body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertNotIn(self.host, body['hosts']) @test.attr(type='gate') def test_aggregate_add_host_list(self): # Add an host to the given aggregate and list. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) resp, aggregates = self.client.list_aggregates() aggs = filter(lambda x: x['id'] == aggregate['id'], aggregates) self.assertEqual(1, len(aggs)) agg = aggs[0] self.assertEqual(aggregate_name, agg['name']) self.assertIsNone(agg['availability_zone']) self.assertIn(self.host, agg['hosts']) @test.attr(type='gate') def test_aggregate_add_host_get_details(self): # Add an host to the given aggregate and get details. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) resp, body = self.client.get_aggregate(aggregate['id']) self.assertEqual(aggregate_name, body['name']) self.assertIsNone(body['availability_zone']) self.assertIn(self.host, body['hosts']) @test.attr(type='gate') def test_aggregate_add_host_create_server_with_az(self): # Add an host to the given aggregate and create a server. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) az_name = data_utils.rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate( name=aggregate_name, availability_zone=az_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) server_name = data_utils.rand_name('test_server_') admin_servers_client = self.servers_admin_client resp, server = self.create_test_server(name=server_name, availability_zone=az_name, wait_until='ACTIVE') resp, body = admin_servers_client.get_server(server['id']) self.assertEqual(self.host, body[self._host_key]) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_flavors_access.py0000664000175000017500000001067512332757070030042 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class FlavorsAccessV3Test(base.BaseV3ComputeAdminTest): """ Tests Flavor Access API extension. Add and remove Flavor Access require admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsAccessV3Test, cls).setUpClass() cls.client = cls.flavors_admin_client admin_client = cls._get_identity_admin_client() cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client. tenant_name) cls.tenant_id = cls.tenant['id'] cls.adm_tenant = admin_client.get_tenant_by_name( cls.flavors_admin_client.tenant_name) cls.adm_tenant_id = cls.adm_tenant['id'] cls.flavor_name_prefix = 'test_flavor_access_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 @test.skip_because(bug='1265416') @test.attr(type='gate') def test_flavor_access_list_with_private_flavor(self): # Test to list flavor access successfully by querying private flavor flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) self.assertEqual(resp.status, 201) resp, flavor_access = self.client.list_flavor_access(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(len(flavor_access), 1, str(flavor_access)) first_flavor = flavor_access[0] self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id'])) self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id']) @test.skip_because(bug='1265416') @test.attr(type='gate') def test_flavor_access_add_remove(self): # Test to add and remove flavor access to a given tenant. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # Add flavor access to a tenant. resp_body = { "tenant_id": str(self.tenant_id), "flavor_id": str(new_flavor['id']), } add_resp, add_body = \ self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.assertEqual(add_resp.status, 200) self.assertIn(resp_body, add_body) # The flavor is present in list. resp, flavors = self.flavors_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) self.assertIn(new_flavor['id'], map(lambda x: x['id'], flavors)) # Remove flavor access from a tenant. remove_resp, remove_body = \ self.client.remove_flavor_access(new_flavor['id'], self.tenant_id) self.assertEqual(remove_resp.status, 200) self.assertNotIn(resp_body, remove_body) # The flavor is not present in list. resp, flavors = self.flavors_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_availability_zone.py0000664000175000017500000000302412332757070030540 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.test import attr class AZAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests Availability Zone API List """ @classmethod def setUpClass(cls): super(AZAdminV3Test, cls).setUpClass() cls.client = cls.availability_zone_admin_client @attr(type='gate') def test_get_availability_zone_list(self): # List of availability zone resp, availability_zone = self.client.get_availability_zone_list() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) @attr(type='gate') def test_get_availability_zone_list_detail(self): # List of availability zones and available services resp, availability_zone = \ self.client.get_availability_zone_list_detail() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_quotas.py0000664000175000017500000001513512332757070026355 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class QuotasAdminV3Test(base.BaseV3ComputeAdminTest): force_tenant_isolation = True @classmethod def setUpClass(cls): super(QuotasAdminV3Test, cls).setUpClass() cls.client = cls.quotas_client cls.adm_client = cls.quotas_admin_client # NOTE(afazekas): these test cases should always create and use a new # tenant most of them should be skipped if we can't do that cls.demo_tenant_id = cls.isolated_creds.get_primary_creds().tenant_id cls.default_quota_set = set(('metadata_items', 'ram', 'floating_ips', 'fixed_ips', 'key_pairs', 'instances', 'security_group_rules', 'cores', 'security_groups')) @test.attr(type='smoke') def test_get_default_quotas(self): # Admin can get the default resource quota set for a tenant expected_quota_set = self.default_quota_set | set(['id']) resp, quota_set = self.adm_client.get_default_quota_set( self.demo_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.demo_tenant_id) @test.attr(type='smoke') def test_get_quota_set_detail(self): # Admin can get the detail of resource quota set for a tenant expected_quota_set = self.default_quota_set | set(['id']) expected_detail = ['reserved', 'limit', 'in_use'] resp, quota_set = self.adm_client.get_quota_set_detail( self.demo_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.demo_tenant_id) for quota in quota_set: if quota == 'id': continue self.assertEqual(sorted(expected_detail), sorted(quota_set[quota].keys())) @test.attr(type='gate') def test_update_all_quota_resources_for_tenant(self): # Admin can update all the resource quota limits for a tenant resp, default_quota_set = self.adm_client.get_default_quota_set( self.demo_tenant_id) new_quota_set = {'metadata_items': 256, 'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10, 'key_pairs': 200, 'instances': 20, 'security_group_rules': 20, 'cores': 2, 'security_groups': 20} # Update limits for all quota resources resp, quota_set = self.adm_client.update_quota_set( self.demo_tenant_id, force=True, **new_quota_set) default_quota_set.pop('id') self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, **default_quota_set) self.assertEqual(200, resp.status) quota_set.pop('id') self.assertEqual(new_quota_set, quota_set) # TODO(afazekas): merge these test cases @test.attr(type='gate') def test_get_updated_quotas(self): # Verify that GET shows the updated quota set of tenant tenant_name = data_utils.rand_name('cpu_quota_tenant_') tenant_desc = tenant_name + '-desc' identity_client = self.os_adm.identity_client _, tenant = identity_client.create_tenant(name=tenant_name, description=tenant_desc) tenant_id = tenant['id'] self.addCleanup(identity_client.delete_tenant, tenant_id) self.adm_client.update_quota_set(tenant_id, ram='5120') resp, quota_set = self.adm_client.get_quota_set(tenant_id) self.assertEqual(200, resp.status) self.assertEqual(5120, quota_set['ram']) # Verify that GET shows the updated quota set of user user_name = data_utils.rand_name('cpu_quota_user_') password = data_utils.rand_name('password-') email = user_name + '@testmail.tm' _, user = identity_client.create_user(name=user_name, password=password, tenant_id=tenant_id, email=email) user_id = user['id'] self.addCleanup(identity_client.delete_user, user_id) self.adm_client.update_quota_set(tenant_id, user_id=user_id, ram='2048') resp, quota_set = self.adm_client.get_quota_set(tenant_id, user_id=user_id) self.assertEqual(200, resp.status) self.assertEqual(2048, quota_set['ram']) @test.attr(type='gate') def test_delete_quota(self): # Admin can delete the resource quota set for a tenant tenant_name = data_utils.rand_name('cpu_quota_tenant_') tenant_desc = tenant_name + '-desc' identity_client = self.os_adm.identity_client _, tenant = identity_client.create_tenant(name=tenant_name, description=tenant_desc) tenant_id = tenant['id'] self.addCleanup(identity_client.delete_tenant, tenant_id) resp, quota_set_default = self.adm_client.get_quota_set(tenant_id) self.assertEqual(200, resp.status) ram_default = quota_set_default['ram'] self.adm_client.update_quota_set(tenant_id, ram='5120') self.assertEqual(200, resp.status) resp, _ = self.adm_client.delete_quota_set(tenant_id) self.assertEqual(204, resp.status) resp, quota_set_new = self.adm_client.get_quota_set(tenant_id) self.assertEqual(200, resp.status) self.assertEqual(ram_default, quota_set_new['ram']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_flavors_negative.py0000664000175000017500000003302212332757070030372 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsAdminNegativeV3Test(base.BaseV3ComputeAdminTest): """ Tests Flavors API Create and Delete that require admin privileges """ @classmethod def setUpClass(cls): super(FlavorsAdminNegativeV3Test, cls).setUpClass() cls.client = cls.flavors_admin_client cls.user_client = cls.flavors_client cls.flavor_name_prefix = 'test_flavor_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 cls.ephemeral = 10 cls.swap = 1024 cls.rxtx = 2 def flavor_clean_up(self, flavor_id): resp, body = self.client.delete_flavor(flavor_id) self.assertEqual(resp.status, 204) self.client.wait_for_resource_deletion(flavor_id) @test.attr(type=['negative', 'gate']) def test_get_flavor_details_for_deleted_flavor(self): # Delete a flavor and ensure it is not listed # Create a test flavor flavor_name = data_utils.rand_name(self.flavor_name_prefix) # no need to specify flavor_id, we can get the flavor_id from a # response of create_flavor() call. resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, '', ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) # Delete the flavor new_flavor_id = flavor['id'] resp_delete, body = self.client.delete_flavor(new_flavor_id) self.assertEqual(201, resp.status) self.assertEqual(204, resp_delete.status) # Deleted flavors can be seen via detailed GET resp, flavor = self.client.get_flavor_details(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) # Deleted flavors should not show up in a list however resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) flag = True for flavor in flavors: if flavor['name'] == flavor_name: flag = False self.assertTrue(flag) @test.attr(type=['negative', 'gate']) def test_invalid_is_public_string(self): # the 'is_public' parameter can be 'none/true/false' if it exists self.assertRaises(exceptions.BadRequest, self.client.list_flavors_with_detail, {'is_public': 'invalid'}) @test.attr(type=['negative', 'gate']) def test_create_flavor_as_user(self): # only admin user can create a flavor flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.Unauthorized, self.user_client.create_flavor, flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) @test.attr(type=['negative', 'gate']) def test_delete_flavor_as_user(self): # only admin user can delete a flavor self.assertRaises(exceptions.Unauthorized, self.user_client.delete_flavor, self.flavor_ref_alt) @test.attr(type=['negative', 'gate']) def test_create_flavor_using_invalid_ram(self): # the 'ram' attribute must be positive integer flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, flavor_name, -1, self.vcpus, self.disk, new_flavor_id) @test.attr(type=['negative', 'gate']) def test_create_flavor_using_invalid_vcpus(self): # the 'vcpu' attribute must be positive integer flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, flavor_name, self.ram, -1, self.disk, new_flavor_id) @test.attr(type=['negative', 'gate']) def test_create_flavor_with_name_length_less_than_1(self): # ensure name length >= 1 new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, '', self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_name_length_exceeds_255(self): # ensure name do not exceed 255 characters new_flavor_name = 'a' * 256 new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_name(self): # the regex of flavor_name is '^[\w\.\- ]*$' invalid_flavor_name = data_utils.rand_name('invalid-!@#$%-') new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, invalid_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_flavor_id(self): # the regex of flavor_id is '^[\w\.\- ]*$', and it cannot contain # leading and/or trailing whitespace new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) invalid_flavor_id = '!@#$%' self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, invalid_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_id_length_exceeds_255(self): # the length of flavor_id should not exceed 255 characters new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) invalid_flavor_id = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, invalid_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_root_gb(self): # root_gb attribute should be non-negative ( >= 0) integer new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, -1, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_ephemeral_gb(self): # ephemeral_gb attribute should be non-negative ( >= 0) integer new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=-1, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_swap(self): # swap attribute should be non-negative ( >= 0) integer new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=-1, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_rxtx_factor(self): # rxtx_factor attribute should be a positive float new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=-1.5, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_is_public(self): # is_public attribute should be boolean new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='Invalid') @test.attr(type=['negative', 'gate']) def test_create_flavor_already_exists(self): flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.assertEqual(201, resp.status) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertRaises(exceptions.Conflict, self.client.create_flavor, flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) @test.attr(type=['negative', 'gate']) def test_delete_nonexistent_flavor(self): nonexistent_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.delete_flavor, nonexistent_flavor_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_flavors_extra_specs.py0000664000175000017500000001155112332757070031113 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class FlavorsExtraSpecsV3Test(base.BaseV3ComputeAdminTest): """ Tests Flavor Extra Spec API extension. SET, UNSET, UPDATE Flavor Extra specs require admin privileges. GET Flavor Extra specs can be performed even by without admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsExtraSpecsV3Test, cls).setUpClass() cls.client = cls.flavors_admin_client flavor_name = data_utils.rand_name('test_flavor') ram = 512 vcpus = 1 disk = 10 ephemeral = 10 cls.new_flavor_id = data_utils.rand_int_id(start=1000) swap = 1024 rxtx = 1 # Create a flavor so as to set/get/unset extra specs resp, cls.flavor = cls.client.create_flavor(flavor_name, ram, vcpus, disk, cls.new_flavor_id, ephemeral=ephemeral, swap=swap, rxtx=rxtx) @classmethod def tearDownClass(cls): resp, body = cls.client.delete_flavor(cls.flavor['id']) cls.client.wait_for_resource_deletion(cls.flavor['id']) super(FlavorsExtraSpecsV3Test, cls).tearDownClass() @test.attr(type='gate') def test_flavor_set_get_update_show_unset_keys(self): # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra # spec as a user with admin privileges. # Assigning extra specs values that are to be set specs = {"key1": "value1", "key2": "value2"} # SET extra specs to the flavor created in setUp set_resp, set_body = \ self.client.set_flavor_extra_spec(self.flavor['id'], specs) self.assertEqual(set_resp.status, 201) self.assertEqual(set_body, specs) # GET extra specs and verify get_resp, get_body = \ self.client.get_flavor_extra_spec(self.flavor['id']) self.assertEqual(get_resp.status, 200) self.assertEqual(get_body, specs) # UPDATE the value of the extra specs key1 update_resp, update_body = \ self.client.update_flavor_extra_spec(self.flavor['id'], "key1", key1="value") self.assertEqual(update_resp.status, 200) self.assertEqual({"key1": "value"}, update_body) # GET extra specs and verify the value of the key2 # is the same as before get_resp, get_body = \ self.client.get_flavor_extra_spec(self.flavor['id']) self.assertEqual(get_resp.status, 200) self.assertEqual(get_body, {"key1": "value", "key2": "value2"}) # UNSET extra specs that were set in this test unset_resp, _ = \ self.client.unset_flavor_extra_spec(self.flavor['id'], "key1") self.assertEqual(unset_resp.status, 204) unset_resp, _ = \ self.client.unset_flavor_extra_spec(self.flavor['id'], "key2") self.assertEqual(unset_resp.status, 204) @test.attr(type='gate') def test_flavor_non_admin_get_all_keys(self): specs = {"key1": "value1", "key2": "value2"} set_resp, set_body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) resp, body = self.flavors_client.get_flavor_extra_spec( self.flavor['id']) self.assertEqual(resp.status, 200) for key in specs: self.assertEqual(body[key], specs[key]) @test.attr(type='gate') def test_flavor_non_admin_get_specific_key(self): specs = {"key1": "value1", "key2": "value2"} resp, body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) self.assertEqual(resp.status, 201) self.assertEqual(body['key1'], 'value1') self.assertIn('key2', body) resp, body = self.flavors_client.get_flavor_extra_spec_with_key( self.flavor['id'], 'key1') self.assertEqual(resp.status, 200) self.assertEqual(body['key1'], 'value1') self.assertNotIn('key2', body) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/admin/test_services.py0000664000175000017500000000560012332757070026660 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.test import attr class ServicesAdminV3Test(base.BaseV3ComputeAdminTest): """ Tests Services API. List and Enable/Disable require admin privileges. """ @classmethod def setUpClass(cls): super(ServicesAdminV3Test, cls).setUpClass() cls.client = cls.services_admin_client @attr(type='gate') def test_list_services(self): resp, services = self.client.list_services() self.assertEqual(200, resp.status) self.assertNotEqual(0, len(services)) @attr(type='gate') def test_get_service_by_service_binary_name(self): binary_name = 'nova-compute' params = {'binary': binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertNotEqual(0, len(services)) for service in services: self.assertEqual(binary_name, service['binary']) @attr(type='gate') def test_get_service_by_host_name(self): resp, services = self.client.list_services() self.assertEqual(200, resp.status) host_name = services[0]['host'] services_on_host = [service for service in services if service['host'] == host_name] params = {'host': host_name} resp, services = self.client.list_services(params) # we could have a periodic job checkin between the 2 service # lookups, so only compare binary lists. s1 = map(lambda x: x['binary'], services) s2 = map(lambda x: x['binary'], services_on_host) # sort the lists before comparing, to take out dependency # on order. self.assertEqual(sorted(s1), sorted(s2)) @attr(type='gate') def test_get_service_by_service_and_host_name(self): resp, services = self.client.list_services() host_name = services[0]['host'] binary_name = services[0]['binary'] params = {'host': host_name, 'binary': binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(1, len(services)) self.assertEqual(host_name, services[0]['host']) self.assertEqual(binary_name, services[0]['binary']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/certificates/0000775000175000017500000000000012332757136025003 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/certificates/__init__.py0000664000175000017500000000000012332757070027077 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v3/certificates/test_certificates.py0000664000175000017500000000246612332757070031066 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.test import attr class CertificatesV3Test(base.BaseV3ComputeTest): @attr(type='gate') def test_create_root_certificate(self): # create certificates resp, body = self.certificates_client.create_certificate() self.assertEqual(201, resp.status) self.assertIn('data', body) self.assertIn('private_key', body) @attr(type='gate') def test_get_root_certificate(self): # get the root certificate resp, body = self.certificates_client.get_certificate('root') self.assertEqual(200, resp.status) self.assertIn('data', body) self.assertIn('private_key', body) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/volumes/0000775000175000017500000000000012332757136023500 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/volumes/test_volumes_list.py0000664000175000017500000001637412332757070027646 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest.test import attr CONF = config.CONF class VolumesTestJSON(base.BaseV2ComputeTest): """ This test creates a number of 1G volumes. To run successfully, ensure that the backing file for the volume group that Nova uses has space for at least 3 1G volumes! If you are running a Devstack environment, ensure that the VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc """ @classmethod def setUpClass(cls): super(VolumesTestJSON, cls).setUpClass() cls.client = cls.volumes_extensions_client if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) # Create 3 Volumes cls.volume_list = [] cls.volume_id_list = [] for i in range(3): v_name = data_utils.rand_name('volume-%s' % cls._interface) metadata = {'Type': 'work'} try: resp, volume = cls.client.create_volume(size=1, display_name=v_name, metadata=metadata) cls.client.wait_for_volume_status(volume['id'], 'available') resp, volume = cls.client.get_volume(volume['id']) cls.volume_list.append(volume) cls.volume_id_list.append(volume['id']) except Exception: if cls.volume_list: # We could not create all the volumes, though we were able # to create *some* of the volumes. This is typically # because the backing file size of the volume group is # too small. So, here, we clean up whatever we did manage # to create and raise a SkipTest for volume in cls.volume_list: cls.delete_volume(volume['id']) msg = ("Failed to create ALL necessary volumes to run " "test. This typically means that the backing file " "size of the nova-volumes group is too small to " "create the 3 volumes needed by this test case") raise cls.skipException(msg) raise @classmethod def tearDownClass(cls): # Delete the created Volumes for volume in cls.volume_list: cls.delete_volume(volume['id']) super(VolumesTestJSON, cls).tearDownClass() @attr(type='gate') def test_volume_list(self): # Should return the list of Volumes # Fetch all Volumes resp, fetched_list = self.client.list_volumes() self.assertEqual(200, resp.status) # Now check if all the Volumes created in setup are in fetched list missing_volumes = [ v for v in self.volume_list if v not in fetched_list ] self.assertFalse(missing_volumes, "Failed to find volume %s in fetched list" % ', '.join(m_vol['displayName'] for m_vol in missing_volumes)) @attr(type='gate') def test_volume_list_with_details(self): # Should return the list of Volumes with details # Fetch all Volumes resp, fetched_list = self.client.list_volumes_with_detail() self.assertEqual(200, resp.status) # Now check if all the Volumes created in setup are in fetched list missing_volumes = [ v for v in self.volume_list if v not in fetched_list ] self.assertFalse(missing_volumes, "Failed to find volume %s in fetched list" % ', '.join(m_vol['displayName'] for m_vol in missing_volumes)) @attr(type='gate') def test_volume_list_param_limit(self): # Return the list of volumes based on limit set params = {'limit': 2} resp, fetched_vol_list = self.client.list_volumes(params=params) self.assertEqual(200, resp.status) self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volumes by limit set") @attr(type='gate') def test_volume_list_with_detail_param_limit(self): # Return the list of volumes with details based on limit set. params = {'limit': 2} resp, fetched_vol_list = \ self.client.list_volumes_with_detail(params=params) self.assertEqual(200, resp.status) self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volume details by limit set") @attr(type='gate') def test_volume_list_param_offset_and_limit(self): # Return the list of volumes based on offset and limit set. # get all volumes list response, all_vol_list = self.client.list_volumes() params = {'offset': 1, 'limit': 1} resp, fetched_vol_list = self.client.list_volumes(params=params) self.assertEqual(200, resp.status) # Validating length of the fetched volumes self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volumes by offset and limit") # Validating offset of fetched volume for index, volume in enumerate(fetched_vol_list): self.assertEqual(volume['id'], all_vol_list[index + params['offset']]['id'], "Failed to list volumes by offset and limit") @attr(type='gate') def test_volume_list_with_detail_param_offset_and_limit(self): # Return the list of volumes details based on offset and limit set. # get all volumes list response, all_vol_list = self.client.list_volumes_with_detail() params = {'offset': 1, 'limit': 1} resp, fetched_vol_list = \ self.client.list_volumes_with_detail(params=params) self.assertEqual(200, resp.status) # Validating length of the fetched volumes self.assertEqual(len(fetched_vol_list), params['limit'], "Failed to list volume details by offset and limit") # Validating offset of fetched volume for index, volume in enumerate(fetched_vol_list): self.assertEqual(volume['id'], all_vol_list[index + params['offset']]['id'], "Failed to list volume details by " "offset and limit") class VolumesTestXML(VolumesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/volumes/test_volumes_get.py0000664000175000017500000000607512332757070027447 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import test from testtools import matchers CONF = config.CONF class VolumesGetTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(VolumesGetTestJSON, cls).setUpClass() cls.client = cls.volumes_extensions_client if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @test.attr(type='smoke') def test_volume_create_get_delete(self): # CREATE, GET, DELETE Volume volume = None v_name = data_utils.rand_name('Volume-%s-') % self._interface metadata = {'Type': 'work'} # Create volume resp, volume = self.client.create_volume(size=1, display_name=v_name, metadata=metadata) self.addCleanup(self.delete_volume, volume['id']) self.assertEqual(200, resp.status) self.assertIn('id', volume) self.assertIn('displayName', volume) self.assertEqual(volume['displayName'], v_name, "The created volume name is not equal " "to the requested name") self.assertTrue(volume['id'] is not None, "Field volume id is empty or not found.") # Wait for Volume status to become ACTIVE self.client.wait_for_volume_status(volume['id'], 'available') # GET Volume resp, fetched_volume = self.client.get_volume(volume['id']) self.assertEqual(200, resp.status) # Verification of details of fetched Volume self.assertEqual(v_name, fetched_volume['displayName'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume is different ' 'from the created Volume') self.assertThat(fetched_volume['metadata'].items(), matchers.ContainsAll(metadata.items()), 'The fetched Volume metadata misses data ' 'from the created Volume') class VolumesGetTestXML(VolumesGetTestJSON): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/volumes/test_volumes_negative.py0000664000175000017500000001034612332757070030466 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.test import attr CONF = config.CONF class VolumesNegativeTest(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(VolumesNegativeTest, cls).setUpClass() cls.client = cls.volumes_extensions_client if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) @attr(type=['negative', 'gate']) def test_volume_get_nonexistent_volume_id(self): # Negative: Should not be able to get details of nonexistent volume # Creating a nonexistent volume id # Trying to GET a non existent volume self.assertRaises(exceptions.NotFound, self.client.get_volume, str(uuid.uuid4())) @attr(type=['negative', 'gate']) def test_volume_delete_nonexistent_volume_id(self): # Negative: Should not be able to delete nonexistent Volume # Creating nonexistent volume id # Trying to DELETE a non existent volume self.assertRaises(exceptions.NotFound, self.client.delete_volume, str(uuid.uuid4())) @attr(type=['negative', 'gate']) def test_create_volume_with_invalid_size(self): # Negative: Should not be able to create volume with invalid size # in request v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='#$%', display_name=v_name, metadata=metadata) @attr(type=['negative', 'gate']) def test_create_volume_with_out_passing_size(self): # Negative: Should not be able to create volume without passing size # in request v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='', display_name=v_name, metadata=metadata) @attr(type=['negative', 'gate']) def test_create_volume_with_size_zero(self): # Negative: Should not be able to create volume with size zero v_name = data_utils.rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='0', display_name=v_name, metadata=metadata) @attr(type=['negative', 'gate']) def test_get_invalid_volume_id(self): # Negative: Should not be able to get volume with invalid id self.assertRaises(exceptions.NotFound, self.client.get_volume, '#$%%&^&^') @attr(type=['negative', 'gate']) def test_get_volume_without_passing_volume_id(self): # Negative: Should not be able to get volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.get_volume, '') @attr(type=['negative', 'gate']) def test_delete_invalid_volume_id(self): # Negative: Should not be able to delete volume when invalid ID is # passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '!@#$%^&*()') @attr(type=['negative', 'gate']) def test_delete_volume_without_passing_volume_id(self): # Negative: Should not be able to delete volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '') class VolumesNegativeTestXML(VolumesNegativeTest): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/volumes/__init__.py0000664000175000017500000000000012332757070025574 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/volumes/test_attach_volume.py0000664000175000017500000001054112332757070027742 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest.common.utils.linux import remote_client from tempest import config from tempest import test CONF = config.CONF class AttachVolumeTestJSON(base.BaseV2ComputeTest): def __init__(self, *args, **kwargs): super(AttachVolumeTestJSON, self).__init__(*args, **kwargs) self.server = None self.volume = None self.attached = False @classmethod def setUpClass(cls): cls.prepare_instance_network() super(AttachVolumeTestJSON, cls).setUpClass() cls.device = CONF.compute.volume_device_name if not CONF.service_available.cinder: skip_msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(skip_msg) def _detach(self, server_id, volume_id): if self.attached: self.servers_client.detach_volume(server_id, volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') def _delete_volume(self): if self.volume: self.volumes_client.delete_volume(self.volume['id']) self.volume = None def _create_and_attach(self): # Start a server and wait for it to become ready admin_pass = self.image_ssh_password resp, server = self.create_test_server(wait_until='ACTIVE', adminPass=admin_pass) self.server = server # Record addresses so that we can ssh later resp, server['addresses'] = \ self.servers_client.list_addresses(server['id']) # Create a volume and wait for it to become ready resp, volume = self.volumes_client.create_volume(1, display_name='test') self.volume = volume self.addCleanup(self._delete_volume) self.volumes_client.wait_for_volume_status(volume['id'], 'available') # Attach the volume to the server self.servers_client.attach_volume(server['id'], volume['id'], device='/dev/%s' % self.device) self.volumes_client.wait_for_volume_status(volume['id'], 'in-use') self.attached = True self.addCleanup(self._detach, server['id'], volume['id']) @testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test') @test.attr(type='gate') def test_attach_detach_volume(self): # Stop and Start a server with an attached volume, ensuring that # the volume remains attached. self._create_and_attach() server = self.server volume = self.volume self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.servers_client.start(server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') linux_client = remote_client.RemoteClient(server, self.image_ssh_user, server['adminPass']) partitions = linux_client.get_partitions() self.assertIn(self.device, partitions) self._detach(server['id'], volume['id']) self.attached = False self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.servers_client.start(server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') linux_client = remote_client.RemoteClient(server, self.image_ssh_user, server['adminPass']) partitions = linux_client.get_partitions() self.assertNotIn(self.device, partitions) class AttachVolumeTestXML(AttachVolumeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/0000775000175000017500000000000012332757136023477 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_disk_config.py0000664000175000017500000001337212332757070027372 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class ServerDiskConfigTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): if not CONF.compute_feature_enabled.disk_config: msg = "DiskConfig extension not enabled." raise cls.skipException(msg) super(ServerDiskConfigTestJSON, cls).setUpClass() cls.client = cls.os.servers_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] def _update_server_with_disk_config(self, disk_config): resp, server = self.client.get_server(self.server_id) if disk_config != server['OS-DCF:diskConfig']: resp, server = self.client.update_server(self.server_id, disk_config=disk_config) self.assertEqual(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual(disk_config, server['OS-DCF:diskConfig']) @test.attr(type='gate') def test_rebuild_server_with_manual_disk_config(self): # A server should be rebuilt using the manual disk config option self._update_server_with_disk_config(disk_config='AUTO') resp, server = self.client.rebuild(self.server_id, self.image_ref_alt, disk_config='MANUAL') # Wait for the server to become active self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the specified attributes are set correctly resp, server = self.client.get_server(server['id']) self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) @test.attr(type='gate') def test_rebuild_server_with_auto_disk_config(self): # A server should be rebuilt using the auto disk config option self._update_server_with_disk_config(disk_config='MANUAL') resp, server = self.client.rebuild(self.server_id, self.image_ref_alt, disk_config='AUTO') # Wait for the server to become active self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the specified attributes are set correctly resp, server = self.client.get_server(server['id']) self.assertEqual('AUTO', server['OS-DCF:diskConfig']) def _get_alternative_flavor(self): resp, server = self.client.get_server(self.server_id) if server['flavor']['id'] == self.flavor_ref: return self.flavor_ref_alt else: return self.flavor_ref @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_resize_server_from_manual_to_auto(self): # A server should be resized from manual to auto disk config self._update_server_with_disk_config(disk_config='MANUAL') # Resize with auto option flavor_id = self._get_alternative_flavor() self.client.resize(self.server_id, flavor_id, disk_config='AUTO') self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.confirm_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, server = self.client.get_server(self.server_id) self.assertEqual('AUTO', server['OS-DCF:diskConfig']) @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_resize_server_from_auto_to_manual(self): # A server should be resized from auto to manual disk config self._update_server_with_disk_config(disk_config='AUTO') # Resize with manual option flavor_id = self._get_alternative_flavor() self.client.resize(self.server_id, flavor_id, disk_config='MANUAL') self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.confirm_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, server = self.client.get_server(self.server_id) self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) @test.attr(type='gate') def test_update_server_from_auto_to_manual(self): # A server should be updated from auto to manual disk config self._update_server_with_disk_config(disk_config='AUTO') # Update the disk_config attribute to manual resp, server = self.client.update_server(self.server_id, disk_config='MANUAL') self.assertEqual(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the disk_config attribute is set correctly resp, server = self.client.get_server(server['id']) self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) class ServerDiskConfigTestXML(ServerDiskConfigTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_servers_negative_new.py0000664000175000017500000000227312332757070031335 0ustar chuckchuck00000000000000# Copyright 2014 Red Hat, Inc & Deutsche Telekom AG # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test load_tests = test.NegativeAutoTest.load_tests @test.SimpleNegativeAutoTest class GetConsoleOutputNegativeTestJSON(base.BaseV2ComputeTest, test.NegativeAutoTest): _service = 'compute' _schema_file = 'compute/servers/get_console_output.json' @classmethod def setUpClass(cls): super(GetConsoleOutputNegativeTestJSON, cls).setUpClass() _resp, server = cls.create_test_server() cls.set_resource("server", server['id']) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_metadata_negative.py0000664000175000017500000001537012332757070032143 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ServerMetadataNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ServerMetadataNegativeTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.quotas = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE') cls.server_id = server['id'] @test.attr(type=['gate', 'negative']) def test_server_create_metadata_key_too_long(self): # Attempt to start a server with a meta-data key that is > 255 # characters # Tryset_server_metadata_item a few values for sz in [256, 257, 511, 1023]: key = "k" * sz meta = {key: 'data1'} self.assertRaises(exceptions.OverLimit, self.create_test_server, meta=meta) # no teardown - all creates should fail @test.attr(type=['negative', 'gate']) def test_create_server_metadata_blank_key(self): # Blank key should trigger an error. meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.create_test_server, meta=meta) @test.attr(type=['negative', 'gate']) def test_server_metadata_non_existent_server(self): # GET on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_server_metadata_item, non_existent_server_id, 'test2') @test.attr(type=['negative', 'gate']) def test_list_server_metadata_non_existent_server(self): # List metadata on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.list_server_metadata, non_existent_server_id) @test.attr(type=['negative', 'gate']) def test_wrong_key_passed_in_body(self): # Raise BadRequest if key in uri does not match # the key passed in body. meta = {'testkey': 'testvalue'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata_item, self.server_id, 'key', meta) @test.attr(type=['negative', 'gate']) def test_set_metadata_non_existent_server(self): # Set metadata on a non-existent server should not succeed non_existent_server_id = data_utils.rand_uuid() meta = {'meta1': 'data1'} self.assertRaises(exceptions.NotFound, self.client.set_server_metadata, non_existent_server_id, meta) @test.attr(type=['negative', 'gate']) def test_update_metadata_non_existent_server(self): # An update should not happen for a non-existent server non_existent_server_id = data_utils.rand_uuid() meta = {'key1': 'value1', 'key2': 'value2'} self.assertRaises(exceptions.NotFound, self.client.update_server_metadata, non_existent_server_id, meta) @test.attr(type=['negative', 'gate']) def test_update_metadata_with_blank_key(self): # Blank key should trigger an error meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.update_server_metadata, self.server_id, meta=meta) @test.attr(type=['negative', 'gate']) def test_delete_metadata_non_existent_server(self): # Should not be able to delete metadata item from a non-existent server non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.delete_server_metadata_item, non_existent_server_id, 'd') @test.attr(type=['negative', 'gate']) def test_metadata_items_limit(self): # Raise a 413 OverLimit exception while exceeding metadata items limit # for tenant. _, quota_set = self.quotas.get_quota_set(self.tenant_id) quota_metadata = quota_set['metadata_items'] req_metadata = {} for num in range(1, quota_metadata + 2): req_metadata['key' + str(num)] = 'val' + str(num) self.assertRaises(exceptions.OverLimit, self.client.set_server_metadata, self.server_id, req_metadata) # Raise a 413 OverLimit exception while exceeding metadata items limit # for tenant (update). self.assertRaises(exceptions.OverLimit, self.client.update_server_metadata, self.server_id, req_metadata) @test.attr(type=['negative', 'gate']) def test_set_server_metadata_blank_key(self): # Raise a bad request error for blank key. # set_server_metadata will replace all metadata with new value meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata, self.server_id, meta=meta) @test.attr(type=['negative', 'gate']) def test_set_server_metadata_missing_metadata(self): # Raise a bad request error for a missing metadata field # set_server_metadata will replace all metadata with new value meta = {'meta1': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata, self.server_id, meta=meta, no_metadata_field=True) class ServerMetadataNegativeTestXML(ServerMetadataNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_attach_interfaces.py0000664000175000017500000001424212332757070030557 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest import exceptions from tempest import test import time CONF = config.CONF class AttachInterfacesTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): if not CONF.service_available.neutron: raise cls.skipException("Neutron is required") # This test class requires network and subnet cls.set_network_resources(network=True, subnet=True) super(AttachInterfacesTestJSON, cls).setUpClass() cls.client = cls.os.interfaces_client def _check_interface(self, iface, port_id=None, network_id=None, fixed_ip=None): self.assertIn('port_state', iface) if port_id: self.assertEqual(iface['port_id'], port_id) if network_id: self.assertEqual(iface['net_id'], network_id) if fixed_ip: self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip) def _create_server_get_interfaces(self): resp, server = self.create_test_server(wait_until='ACTIVE') resp, ifs = self.client.list_interfaces(server['id']) self.assertEqual(200, resp.status) resp, body = self.client.wait_for_interface_status( server['id'], ifs[0]['port_id'], 'ACTIVE') ifs[0]['port_state'] = body['port_state'] return server, ifs def _test_create_interface(self, server): resp, iface = self.client.create_interface(server['id']) self.assertEqual(200, resp.status) resp, iface = self.client.wait_for_interface_status( server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface) return iface def _test_create_interface_by_network_id(self, server, ifs): network_id = ifs[0]['net_id'] resp, iface = self.client.create_interface(server['id'], network_id=network_id) self.assertEqual(200, resp.status) resp, iface = self.client.wait_for_interface_status( server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface, network_id=network_id) return iface def _test_show_interface(self, server, ifs): iface = ifs[0] resp, _iface = self.client.show_interface(server['id'], iface['port_id']) self.assertEqual(200, resp.status) self.assertEqual(iface, _iface) def _test_delete_interface(self, server, ifs): # NOTE(danms): delete not the first or last, but one in the middle iface = ifs[1] resp, _ = self.client.delete_interface(server['id'], iface['port_id']) self.assertEqual(202, resp.status) _ifs = self.client.list_interfaces(server['id'])[1] start = int(time.time()) while len(ifs) == len(_ifs): time.sleep(self.build_interval) _ifs = self.client.list_interfaces(server['id'])[1] timed_out = int(time.time()) - start >= self.build_timeout if len(ifs) == len(_ifs) and timed_out: message = ('Failed to delete interface within ' 'the required time: %s sec.' % self.build_timeout) raise exceptions.TimeoutException(message) self.assertNotIn(iface['port_id'], [i['port_id'] for i in _ifs]) return _ifs def _compare_iface_list(self, list1, list2): # NOTE(danms): port_state will likely have changed, so just # confirm the port_ids are the same at least list1 = [x['port_id'] for x in list1] list2 = [x['port_id'] for x in list2] self.assertEqual(sorted(list1), sorted(list2)) @test.attr(type='smoke') def test_create_list_show_delete_interfaces(self): server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertTrue(interface_count > 0) self._check_interface(ifs[0]) iface = self._test_create_interface(server) ifs.append(iface) iface = self._test_create_interface_by_network_id(server, ifs) ifs.append(iface) resp, _ifs = self.client.list_interfaces(server['id']) self._compare_iface_list(ifs, _ifs) self._test_show_interface(server, ifs) _ifs = self._test_delete_interface(server, ifs) self.assertEqual(len(ifs) - 1, len(_ifs)) @test.attr(type='smoke') def test_add_remove_fixed_ip(self): # Add and Remove the fixed IP to server. server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertTrue(interface_count > 0) self._check_interface(ifs[0]) network_id = ifs[0]['net_id'] resp, body = self.client.add_fixed_ip(server['id'], network_id) self.assertEqual(202, resp.status) # Remove the fixed IP from server. server_resp, server_detail = self.os.servers_client.get_server( server['id']) # Get the Fixed IP from server. fixed_ip = None for ip_set in server_detail['addresses']: for ip in server_detail['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': fixed_ip = ip['addr'] break if fixed_ip is not None: break resp, body = self.client.remove_fixed_ip(server['id'], fixed_ip) self.assertEqual(202, resp.status) class AttachInterfacesTestXML(AttachInterfacesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_virtual_interfaces.py0000664000175000017500000000407512332757070031004 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netaddr from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class VirtualInterfacesTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): # This test needs a network and a subnet cls.set_network_resources(network=True, subnet=True) super(VirtualInterfacesTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] @test.skip_because(bug="1183436", condition=CONF.service_available.neutron) @test.attr(type='gate') def test_list_virtual_interfaces(self): # Positive test:Should be able to GET the virtual interfaces list # for a given server_id resp, output = self.client.list_virtual_interfaces(self.server_id) self.assertEqual(200, resp.status) self.assertIsNotNone(output) virt_ifaces = output self.assertNotEqual(0, len(virt_ifaces['virtual_interfaces']), 'Expected virtual interfaces, got 0 interfaces.') for virt_iface in virt_ifaces['virtual_interfaces']: mac_address = virt_iface['mac_address'] self.assertTrue(netaddr.valid_mac(mac_address), "Invalid mac address detected.") class VirtualInterfacesTestXML(VirtualInterfacesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_create_server.py0000664000175000017500000002120412332757070027735 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import netaddr import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config from tempest import test CONF = config.CONF class ServersTestJSON(base.BaseV2ComputeTest): disk_config = 'AUTO' @classmethod def setUpClass(cls): cls.prepare_instance_network() super(ServersTestJSON, cls).setUpClass() cls.meta = {'hello': 'world'} cls.accessIPv4 = '1.1.1.1' cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2' cls.name = data_utils.rand_name('server') file_contents = 'This is a test file.' personality = [{'path': '/test.txt', 'contents': base64.b64encode(file_contents)}] cls.client = cls.servers_client cli_resp = cls.create_test_server(name=cls.name, meta=cls.meta, accessIPv4=cls.accessIPv4, accessIPv6=cls.accessIPv6, personality=personality, disk_config=cls.disk_config) cls.resp, cls.server_initial = cli_resp cls.password = cls.server_initial['adminPass'] cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE') resp, cls.server = cls.client.get_server(cls.server_initial['id']) @test.attr(type='smoke') def test_verify_server_details(self): # Verify the specified server attributes are set correctly self.assertEqual(self.accessIPv4, self.server['accessIPv4']) # NOTE(maurosr): See http://tools.ietf.org/html/rfc5952 (section 4) # Here we compare directly with the canonicalized format. self.assertEqual(self.server['accessIPv6'], str(netaddr.IPAddress(self.accessIPv6))) self.assertEqual(self.name, self.server['name']) self.assertEqual(self.image_ref, self.server['image']['id']) self.assertEqual(self.flavor_ref, self.server['flavor']['id']) self.assertEqual(self.meta, self.server['metadata']) @test.attr(type='smoke') def test_list_servers(self): # The created server should be in the list of all servers resp, body = self.client.list_servers() servers = body['servers'] found = any([i for i in servers if i['id'] == self.server['id']]) self.assertTrue(found) @test.attr(type='smoke') def test_list_servers_with_detail(self): # The created server should be in the detailed list of all servers resp, body = self.client.list_servers_with_detail() servers = body['servers'] found = any([i for i in servers if i['id'] == self.server['id']]) self.assertTrue(found) @testtools.skipUnless(CONF.compute.run_ssh, 'Instance validation tests are disabled.') @test.attr(type='gate') def test_verify_created_server_vcpus(self): # Verify that the number of vcpus reported by the instance matches # the amount stated by the flavor resp, flavor = self.flavors_client.get_flavor_details(self.flavor_ref) linux_client = remote_client.RemoteClient(self.server, self.ssh_user, self.password) self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus()) @testtools.skipUnless(CONF.compute.run_ssh, 'Instance validation tests are disabled.') @test.attr(type='gate') def test_host_name_is_same_as_server_name(self): # Verify the instance host name is the same as the server name linux_client = remote_client.RemoteClient(self.server, self.ssh_user, self.password) self.assertTrue(linux_client.hostname_equals_servername(self.name)) class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest): disk_config = 'AUTO' @classmethod def setUpClass(cls): cls.prepare_instance_network() super(ServersWithSpecificFlavorTestJSON, cls).setUpClass() cls.flavor_client = cls.os_adm.flavors_client cls.client = cls.servers_client @testtools.skipUnless(CONF.compute.run_ssh, 'Instance validation tests are disabled.') @test.attr(type='gate') def test_verify_created_server_ephemeral_disk(self): # Verify that the ephemeral disk is created when creating server def create_flavor_with_extra_specs(): flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor') flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000) ram = 64 vcpus = 1 disk = 0 # Create a flavor with extra specs resp, flavor = (self.flavor_client. create_flavor(flavor_with_eph_disk_name, ram, vcpus, disk, flavor_with_eph_disk_id, ephemeral=1)) self.addCleanup(flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) return flavor['id'] def create_flavor_without_extra_specs(): flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor') flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000) ram = 64 vcpus = 1 disk = 0 # Create a flavor without extra specs resp, flavor = (self.flavor_client. create_flavor(flavor_no_eph_disk_name, ram, vcpus, disk, flavor_no_eph_disk_id)) self.addCleanup(flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) return flavor['id'] def flavor_clean_up(flavor_id): resp, body = self.flavor_client.delete_flavor(flavor_id) self.assertEqual(resp.status, 202) self.flavor_client.wait_for_resource_deletion(flavor_id) flavor_with_eph_disk_id = create_flavor_with_extra_specs() flavor_no_eph_disk_id = create_flavor_without_extra_specs() admin_pass = self.image_ssh_password resp, server_no_eph_disk = (self.create_test_server( wait_until='ACTIVE', adminPass=admin_pass, flavor=flavor_no_eph_disk_id)) resp, server_with_eph_disk = (self.create_test_server( wait_until='ACTIVE', adminPass=admin_pass, flavor=flavor_with_eph_disk_id)) # Get partition number of server without extra specs. _, server_no_eph_disk = self.client.get_server( server_no_eph_disk['id']) linux_client = remote_client.RemoteClient(server_no_eph_disk, self.ssh_user, admin_pass) partition_num = len(linux_client.get_partitions().split('\n')) _, server_with_eph_disk = self.client.get_server( server_with_eph_disk['id']) linux_client = remote_client.RemoteClient(server_with_eph_disk, self.ssh_user, admin_pass) partition_num_emph = len(linux_client.get_partitions().split('\n')) self.assertEqual(partition_num + 1, partition_num_emph) class ServersTestManualDisk(ServersTestJSON): disk_config = 'MANUAL' @classmethod def setUpClass(cls): if not CONF.compute_feature_enabled.disk_config: msg = "DiskConfig extension not enabled." raise cls.skipException(msg) super(ServersTestManualDisk, cls).setUpClass() class ServersTestXML(ServersTestJSON): _interface = 'xml' class ServersWithSpecificFlavorTestXML(ServersWithSpecificFlavorTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_addresses_negative.py0000664000175000017500000000340412332757070032333 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import exceptions from tempest import test class ServerAddressesNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): cls.set_network_resources(network=True, subnet=True) super(ServerAddressesNegativeTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, cls.server = cls.create_test_server(wait_until='ACTIVE') @test.attr(type=['negative', 'gate']) def test_list_server_addresses_invalid_server_id(self): # List addresses request should fail if server id not in system self.assertRaises(exceptions.NotFound, self.client.list_addresses, '999') @test.attr(type=['negative', 'gate']) def test_list_server_addresses_by_network_neg(self): # List addresses by network should fail if network name not valid self.assertRaises(exceptions.NotFound, self.client.list_addresses_by_network, self.server['id'], 'invalid') class ServerAddressesNegativeTestXML(ServerAddressesNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_rescue_negative.py0000664000175000017500000001413312332757070031645 0ustar chuckchuck00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ServerRescueNegativeTestJSON(base.BaseV2ComputeTest): @classmethod @test.safe_setup def setUpClass(cls): cls.set_network_resources(network=True, subnet=True, router=True) super(ServerRescueNegativeTestJSON, cls).setUpClass() cls.device = 'vdf' # Create a volume and wait for it to become ready for attach resp, cls.volume = cls.volumes_extensions_client.create_volume( 1, display_name=data_utils.rand_name(cls.__name__ + '_volume')) cls.volumes_extensions_client.wait_for_volume_status( cls.volume['id'], 'available') # Server for negative tests resp, server = cls.create_test_server(wait_until='BUILD') resp, resc_server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] cls.password = server['adminPass'] cls.rescue_id = resc_server['id'] rescue_password = resc_server['adminPass'] cls.servers_client.rescue_server( cls.rescue_id, adminPass=rescue_password) cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE') cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE') @classmethod def tearDownClass(cls): cls.delete_volume(cls.volume['id']) super(ServerRescueNegativeTestJSON, cls).tearDownClass() def _detach(self, server_id, volume_id): self.servers_client.detach_volume(server_id, volume_id) self.volumes_extensions_client.wait_for_volume_status(volume_id, 'available') def _unrescue(self, server_id): resp, body = self.servers_client.unrescue_server(server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') def _unpause(self, server_id): resp, body = self.servers_client.unpause_server(server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type=['negative', 'gate']) def test_rescue_paused_instance(self): # Rescue a paused server resp, body = self.servers_client.pause_server(self.server_id) self.addCleanup(self._unpause, self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'PAUSED') self.assertRaises(exceptions.Conflict, self.servers_client.rescue_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_rescued_vm_reboot(self): self.assertRaises(exceptions.Conflict, self.servers_client.reboot, self.rescue_id, 'HARD') @test.attr(type=['negative', 'gate']) def test_rescue_non_existent_server(self): # Rescue a non-existing server non_existent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.servers_client.rescue_server, non_existent_server) @test.attr(type=['negative', 'gate']) def test_rescued_vm_rebuild(self): self.assertRaises(exceptions.Conflict, self.servers_client.rebuild, self.rescue_id, self.image_ref_alt) @test.attr(type=['negative', 'gate']) def test_rescued_vm_attach_volume(self): # Rescue the server self.servers_client.rescue_server(self.server_id, adminPass=self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') self.addCleanup(self._unrescue, self.server_id) # Attach the volume to the server self.assertRaises(exceptions.Conflict, self.servers_client.attach_volume, self.server_id, self.volume['id'], device='/dev/%s' % self.device) @test.attr(type=['negative', 'gate']) def test_rescued_vm_detach_volume(self): # Attach the volume to the server self.servers_client.attach_volume(self.server_id, self.volume['id'], device='/dev/%s' % self.device) self.volumes_extensions_client.wait_for_volume_status( self.volume['id'], 'in-use') # Rescue the server self.servers_client.rescue_server(self.server_id, adminPass=self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') # addCleanup is a LIFO queue self.addCleanup(self._detach, self.server_id, self.volume['id']) self.addCleanup(self._unrescue, self.server_id) # Detach the volume from the server expecting failure self.assertRaises(exceptions.Conflict, self.servers_client.detach_volume, self.server_id, self.volume['id']) class ServerRescueNegativeTestXML(ServerRescueNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_password.py0000664000175000017500000000256612332757070030346 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class ServerPasswordTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ServerPasswordTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, cls.server = cls.create_test_server(wait_until="ACTIVE") @test.attr(type='gate') def test_get_server_password(self): resp, body = self.client.get_password(self.server['id']) self.assertEqual(200, resp.status) @test.attr(type='gate') def test_delete_server_password(self): resp, body = self.client.delete_password(self.server['id']) self.assertEqual(204, resp.status) class ServerPasswordTestXML(ServerPasswordTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_actions.py0000664000175000017500000005140012332757070030133 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import testtools import urlparse from tempest.api.compute import base from tempest.common.utils import data_utils from tempest.common.utils.linux import remote_client from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ServerActionsTestJSON(base.BaseV2ComputeTest): run_ssh = CONF.compute.run_ssh def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ServerActionsTestJSON, self).setUp() # Check if the server is in a clean state after test try: self.client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: # Rebuild server if something happened to it during a test self.__class__.server_id = self.rebuild_server(self.server_id) def tearDown(self): _, server = self.client.get_server(self.server_id) self.assertEqual(self.image_ref, server['image']['id']) self.server_check_teardown() super(ServerActionsTestJSON, self).tearDown() @classmethod def setUpClass(cls): cls.prepare_instance_network() super(ServerActionsTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.server_id = cls.rebuild_server(None) @testtools.skipUnless(CONF.compute_feature_enabled.change_password, 'Change password not available.') @test.attr(type='gate') def test_change_server_password(self): # The server's password should be set to the provided password new_password = 'Newpass1234' resp, body = self.client.change_password(self.server_id, new_password) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Verify that the user can authenticate with the new password resp, server = self.client.get_server(self.server_id) linux_client = remote_client.RemoteClient(server, self.ssh_user, new_password) linux_client.validate_authentication() @test.attr(type='smoke') def test_reboot_server_hard(self): # The server should be power cycled if self.run_ssh: # Get the time the server was last rebooted, resp, server = self.client.get_server(self.server_id) linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) boot_time = linux_client.get_boot_time() resp, body = self.client.reboot(self.server_id, 'HARD') self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Log in and verify the boot time has changed linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) new_boot_time = linux_client.get_boot_time() self.assertTrue(new_boot_time > boot_time, '%s > %s' % (new_boot_time, boot_time)) @test.skip_because(bug="1014647") @test.attr(type='smoke') def test_reboot_server_soft(self): # The server should be signaled to reboot gracefully if self.run_ssh: # Get the time the server was last rebooted, resp, server = self.client.get_server(self.server_id) linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) boot_time = linux_client.get_boot_time() resp, body = self.client.reboot(self.server_id, 'SOFT') self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Log in and verify the boot time has changed linux_client = remote_client.RemoteClient(server, self.ssh_user, self.password) new_boot_time = linux_client.get_boot_time() self.assertTrue(new_boot_time > boot_time, '%s > %s' % (new_boot_time, boot_time)) @test.attr(type='smoke') def test_rebuild_server(self): # The server should be rebuilt using the provided image and data meta = {'rebuild': 'server'} new_name = data_utils.rand_name('server') file_contents = 'Test server rebuild.' personality = [{'path': 'rebuild.txt', 'contents': base64.b64encode(file_contents)}] password = 'rebuildPassw0rd' resp, rebuilt_server = self.client.rebuild(self.server_id, self.image_ref_alt, name=new_name, metadata=meta, personality=personality, adminPass=password) # Verify the properties in the initial response are correct self.assertEqual(self.server_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) # Verify the server properties after the rebuild completes self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE') resp, server = self.client.get_server(rebuilt_server['id']) rebuilt_image_id = server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(new_name, server['name']) if self.run_ssh: # Verify that the user can authenticate with the provided password linux_client = remote_client.RemoteClient(server, self.ssh_user, password) linux_client.validate_authentication() if self.image_ref_alt != self.image_ref: self.client.rebuild(self.server_id, self.image_ref) @test.attr(type='gate') def test_rebuild_server_in_stop_state(self): # The server in stop state should be rebuilt using the provided # image and remain in SHUTOFF state resp, server = self.client.get_server(self.server_id) old_image = server['image']['id'] new_image = self.image_ref_alt \ if old_image == self.image_ref else self.image_ref resp, server = self.client.stop(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SHUTOFF') resp, rebuilt_server = self.client.rebuild(self.server_id, new_image) # Verify the properties in the initial response are correct self.assertEqual(self.server_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertEqual(new_image, rebuilt_image_id) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) # Verify the server properties after the rebuild completes self.client.wait_for_server_status(rebuilt_server['id'], 'SHUTOFF') resp, server = self.client.get_server(rebuilt_server['id']) rebuilt_image_id = server['image']['id'] self.assertEqual(new_image, rebuilt_image_id) # Restore to the original image (The tearDown will test it again) if self.image_ref_alt != self.image_ref: self.client.rebuild(self.server_id, old_image) self.client.wait_for_server_status(self.server_id, 'SHUTOFF') self.client.start(self.server_id) def _detect_server_image_flavor(self, server_id): # Detects the current server image flavor ref. resp, server = self.client.get_server(server_id) current_flavor = server['flavor']['id'] new_flavor_ref = self.flavor_ref_alt \ if current_flavor == self.flavor_ref else self.flavor_ref return current_flavor, new_flavor_ref @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='smoke') def test_resize_server_confirm(self): # The server's RAM and disk space should be modified to that of # the provided flavor previous_flavor_ref, new_flavor_ref = \ self._detect_server_image_flavor(self.server_id) resp, server = self.client.resize(self.server_id, new_flavor_ref) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.confirm_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, server = self.client.get_server(self.server_id) self.assertEqual(new_flavor_ref, server['flavor']['id']) @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_resize_server_revert(self): # The server's RAM and disk space should return to its original # values after a resize is reverted previous_flavor_ref, new_flavor_ref = \ self._detect_server_image_flavor(self.server_id) resp, server = self.client.resize(self.server_id, new_flavor_ref) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.revert_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, server = self.client.get_server(self.server_id) self.assertEqual(previous_flavor_ref, server['flavor']['id']) @test.attr(type='gate') def test_create_backup(self): # Positive test:create backup successfully and rotate backups correctly # create the first and the second backup backup1 = data_utils.rand_name('backup-1') resp, _ = self.servers_client.create_backup(self.server_id, 'daily', 2, backup1) oldest_backup_exist = True # the oldest one should be deleted automatically in this test def _clean_oldest_backup(oldest_backup): if oldest_backup_exist: self.os.image_client.delete_image(oldest_backup) image1_id = data_utils.parse_image_id(resp['location']) self.addCleanup(_clean_oldest_backup, image1_id) self.assertEqual(202, resp.status) self.os.image_client.wait_for_image_status(image1_id, 'active') backup2 = data_utils.rand_name('backup-2') self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') resp, _ = self.servers_client.create_backup(self.server_id, 'daily', 2, backup2) image2_id = data_utils.parse_image_id(resp['location']) self.addCleanup(self.os.image_client.delete_image, image2_id) self.assertEqual(202, resp.status) self.os.image_client.wait_for_image_status(image2_id, 'active') # verify they have been created properties = { 'image_type': 'backup', 'backup_type': "daily", 'instance_uuid': self.server_id, } resp, image_list = self.os.image_client.image_list_detail( properties, status='active', sort_key='created_at', sort_dir='asc') self.assertEqual(200, resp.status) self.assertEqual(2, len(image_list)) self.assertEqual((backup1, backup2), (image_list[0]['name'], image_list[1]['name'])) # create the third one, due to the rotation is 2, # the first one will be deleted backup3 = data_utils.rand_name('backup-3') self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') resp, _ = self.servers_client.create_backup(self.server_id, 'daily', 2, backup3) image3_id = data_utils.parse_image_id(resp['location']) self.addCleanup(self.os.image_client.delete_image, image3_id) self.assertEqual(202, resp.status) # the first back up should be deleted self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') self.os.image_client.wait_for_resource_deletion(image1_id) oldest_backup_exist = False resp, image_list = self.os.image_client.image_list_detail( properties, status='active', sort_key='created_at', sort_dir='asc') self.assertEqual(200, resp.status) self.assertEqual(2, len(image_list), 'Unexpected number of images for ' 'v2:test_create_backup; was the oldest backup not ' 'yet deleted? Image list: %s' % [image['name'] for image in image_list]) self.assertEqual((backup2, backup3), (image_list[0]['name'], image_list[1]['name'])) def _get_output(self): resp, output = self.servers_client.get_console_output( self.server_id, 10) self.assertEqual(200, resp.status) self.assertTrue(output, "Console output was empty.") lines = len(output.split('\n')) self.assertEqual(lines, 10) @test.attr(type='gate') def test_get_console_output(self): # Positive test:Should be able to GET the console output # for a given server_id and number of lines # This reboot is necessary for outputting some console log after # creating a instance backup. If a instance backup, the console # log file is truncated and we cannot get any console log through # "console-log" API. # The detail is https://bugs.launchpad.net/nova/+bug/1251920 resp, body = self.servers_client.reboot(self.server_id, 'HARD') self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') self.wait_for(self._get_output) @test.attr(type='gate') def test_get_console_output_server_id_in_shutoff_status(self): # Positive test:Should be able to GET the console output # for a given server_id in SHUTOFF status # NOTE: SHUTOFF is irregular status. To avoid test instability, # one server is created only for this test without using # the server that was created in setupClass. resp, server = self.create_test_server(wait_until='ACTIVE') temp_server_id = server['id'] resp, server = self.servers_client.stop(temp_server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(temp_server_id, 'SHUTOFF') self.wait_for(self._get_output) @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type='gate') def test_pause_unpause_server(self): resp, server = self.client.pause_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'PAUSED') resp, server = self.client.unpause_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type='gate') def test_suspend_resume_server(self): resp, server = self.client.suspend_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SUSPENDED') resp, server = self.client.resume_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') @test.attr(type='gate') def test_shelve_unshelve_server(self): resp, server = self.client.shelve_server(self.server_id) self.assertEqual(202, resp.status) offload_time = CONF.compute.shelved_offload_time if offload_time >= 0: self.client.wait_for_server_status(self.server_id, 'SHELVED_OFFLOADED', extra_timeout=offload_time) else: self.client.wait_for_server_status(self.server_id, 'SHELVED') resp, server = self.client.shelve_offload_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SHELVED_OFFLOADED') resp, server = self.client.get_server(self.server_id) image_name = server['name'] + '-shelved' params = {'name': image_name} resp, images = self.images_client.list_images(params) self.assertEqual(1, len(images)) self.assertEqual(image_name, images[0]['name']) resp, server = self.client.unshelve_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') @test.attr(type='gate') def test_stop_start_server(self): resp, server = self.servers_client.stop(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF') resp, server = self.servers_client.start(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') @test.attr(type='gate') def test_lock_unlock_server(self): # Lock the server,try server stop(exceptions throw),unlock it and retry resp, server = self.servers_client.lock_server(self.server_id) self.assertEqual(202, resp.status) resp, server = self.servers_client.get_server(self.server_id) self.assertEqual(200, resp.status) self.assertEqual(server['status'], 'ACTIVE') # Locked server is not allowed to be stopped by non-admin user self.assertRaises(exceptions.Conflict, self.servers_client.stop, self.server_id) resp, server = self.servers_client.unlock_server(self.server_id) self.assertEqual(202, resp.status) resp, server = self.servers_client.stop(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF') resp, server = self.servers_client.start(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') def _validate_url(self, url): valid_scheme = ['http', 'https'] parsed_url = urlparse.urlparse(url) self.assertNotEqual('None', parsed_url.port) self.assertNotEqual('None', parsed_url.hostname) self.assertIn(parsed_url.scheme, valid_scheme) @testtools.skipUnless(CONF.compute_feature_enabled.vnc_console, 'VNC Console feature is disabled.') @test.attr(type='gate') def test_get_vnc_console(self): # Get the VNC console of type 'novnc' and 'xvpvnc' console_types = ['novnc', 'xvpvnc'] for console_type in console_types: resp, body = self.servers_client.get_vnc_console(self.server_id, console_type) self.assertEqual( 200, resp.status, "Failed to get Console Type: %s" % (console_types)) self.assertEqual(console_type, body['type']) self.assertNotEqual('', body['url']) self._validate_url(body['url']) class ServerActionsTestXML(ServerActionsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_multiple_create.py0000664000175000017500000000454312332757070030271 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class MultipleCreateTestJSON(base.BaseV2ComputeTest): _name = 'multiple-create-test' def _generate_name(self): return data_utils.rand_name(self._name) def _create_multiple_servers(self, name=None, wait_until=None, **kwargs): """ This is the right way to create_multiple servers and manage to get the created servers into the servers list to be cleaned up after all. """ kwargs['name'] = kwargs.get('name', self._generate_name()) resp, body = self.create_test_server(**kwargs) return resp, body @test.attr(type='gate') def test_multiple_create(self): resp, body = self._create_multiple_servers(wait_until='ACTIVE', min_count=1, max_count=2) # NOTE(maurosr): do status response check and also make sure that # reservation_id is not in the response body when the request send # contains return_reservation_id=False self.assertEqual('202', resp['status']) self.assertNotIn('reservation_id', body) @test.attr(type='gate') def test_multiple_create_with_reservation_return(self): resp, body = self._create_multiple_servers(wait_until='ACTIVE', min_count=1, max_count=2, return_reservation_id=True) self.assertEqual(resp['status'], '202') self.assertIn('reservation_id', body) class MultipleCreateTestXML(MultipleCreateTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_servers_negative.py0000664000175000017500000004507312332757070030471 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import sys import testtools from tempest.api.compute import base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ServersNegativeTestJSON(base.BaseV2ComputeTest): def setUp(self): super(ServersNegativeTestJSON, self).setUp() try: self.client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: self.__class__.server_id = self.rebuild_server(self.server_id) def tearDown(self): self.server_check_teardown() super(ServersNegativeTestJSON, self).tearDown() @classmethod def setUpClass(cls): super(ServersNegativeTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.alt_os = clients.AltManager() cls.alt_client = cls.alt_os.servers_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] @test.attr(type=['negative', 'gate']) def test_server_name_blank(self): # Create a server with name parameter empty self.assertRaises(exceptions.BadRequest, self.create_test_server, name='') @test.attr(type=['negative', 'gate']) def test_personality_file_contents_not_encoded(self): # Use an unencoded file when creating a server with personality file_contents = 'This is a test file.' person = [{'path': '/etc/testfile.txt', 'contents': file_contents}] self.assertRaises(exceptions.BadRequest, self.create_test_server, personality=person) @test.attr(type=['negative', 'gate']) def test_create_with_invalid_image(self): # Create a server with an unknown image self.assertRaises(exceptions.BadRequest, self.create_test_server, image_id=-1) @test.attr(type=['negative', 'gate']) def test_create_with_invalid_flavor(self): # Create a server with an unknown flavor self.assertRaises(exceptions.BadRequest, self.create_test_server, flavor=-1,) @test.attr(type=['negative', 'gate']) def test_invalid_access_ip_v4_address(self): # An access IPv4 address must match a valid address pattern IPv4 = '1.1.1.1.1.1' self.assertRaises(exceptions.BadRequest, self.create_test_server, accessIPv4=IPv4) @test.attr(type=['negative', 'gate']) def test_invalid_ip_v6_address(self): # An access IPv6 address must match a valid address pattern IPv6 = 'notvalid' self.assertRaises(exceptions.BadRequest, self.create_test_server, accessIPv6=IPv6) @test.attr(type=['negative', 'gate']) def test_resize_nonexistent_server(self): # Resize a non-existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.resize, nonexistent_server, self.flavor_ref) @test.attr(type=['negative', 'gate']) def test_resize_server_with_non_existent_flavor(self): # Resize a server with non-existent flavor nonexistent_flavor = data_utils.rand_uuid() self.assertRaises(exceptions.BadRequest, self.client.resize, self.server_id, flavor_ref=nonexistent_flavor) @test.attr(type=['negative', 'gate']) def test_resize_server_with_null_flavor(self): # Resize a server with null flavor self.assertRaises(exceptions.BadRequest, self.client.resize, self.server_id, flavor_ref="") @test.attr(type=['negative', 'gate']) def test_reboot_non_existent_server(self): # Reboot a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.reboot, nonexistent_server, 'SOFT') @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type=['negative', 'gate']) def test_pause_paused_server(self): # Pause a paused server. self.client.pause_server(self.server_id) self.client.wait_for_server_status(self.server_id, 'PAUSED') self.assertRaises(exceptions.Conflict, self.client.pause_server, self.server_id) self.client.unpause_server(self.server_id) @test.attr(type=['negative', 'gate']) def test_rebuild_reboot_deleted_server(self): # Rebuild and Reboot a deleted server _, server = self.create_test_server() self.client.delete_server(server['id']) self.client.wait_for_server_termination(server['id']) self.assertRaises(exceptions.NotFound, self.client.rebuild, server['id'], self.image_ref_alt) self.assertRaises(exceptions.NotFound, self.client.reboot, server['id'], 'SOFT') @test.attr(type=['negative', 'gate']) def test_rebuild_non_existent_server(self): # Rebuild a non existent server nonexistent_server = data_utils.rand_uuid() meta = {'rebuild': 'server'} new_name = data_utils.rand_name('server') file_contents = 'Test server rebuild.' personality = [{'path': '/etc/rebuild.txt', 'contents': base64.b64encode(file_contents)}] self.assertRaises(exceptions.NotFound, self.client.rebuild, nonexistent_server, self.image_ref_alt, name=new_name, meta=meta, personality=personality, adminPass='rebuild') @test.attr(type=['negative', 'gate']) def test_create_numeric_server_name(self): # Create a server with a numeric name if self.__class__._interface == "xml": raise self.skipException("Not testable in XML") server_name = 12345 self.assertRaises(exceptions.BadRequest, self.create_test_server, name=server_name) @test.attr(type=['negative', 'gate']) def test_create_server_name_length_exceeds_256(self): # Create a server with name length exceeding 256 characters server_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.create_test_server, name=server_name) @test.attr(type=['negative', 'gate']) def test_create_with_invalid_network_uuid(self): # Pass invalid network uuid while creating a server networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}] self.assertRaises(exceptions.BadRequest, self.create_test_server, networks=networks) @test.attr(type=['negative', 'gate']) def test_create_with_non_existent_keypair(self): # Pass a non-existent keypair while creating a server key_name = data_utils.rand_name('key') self.assertRaises(exceptions.BadRequest, self.create_test_server, key_name=key_name) @test.attr(type=['negative', 'gate']) def test_create_server_metadata_exceeds_length_limit(self): # Pass really long metadata while creating a server metadata = {'a': 'b' * 260} self.assertRaises(exceptions.OverLimit, self.create_test_server, meta=metadata) @test.attr(type=['negative', 'gate']) def test_update_name_of_non_existent_server(self): # Update name of a non-existent server server_name = data_utils.rand_name('server') new_name = data_utils.rand_name('server') + '_updated' self.assertRaises(exceptions.NotFound, self.client.update_server, server_name, name=new_name) @test.attr(type=['negative', 'gate']) def test_update_server_set_empty_name(self): # Update name of the server to an empty string server_name = data_utils.rand_name('server') new_name = '' self.assertRaises(exceptions.BadRequest, self.client.update_server, server_name, name=new_name) @test.attr(type=['negative', 'gate']) def test_update_server_of_another_tenant(self): # Update name of a server that belongs to another tenant new_name = self.server_id + '_new' self.assertRaises(exceptions.NotFound, self.alt_client.update_server, self.server_id, name=new_name) @test.attr(type=['negative', 'gate']) def test_update_server_name_length_exceeds_256(self): # Update name of server exceed the name length limit new_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.client.update_server, self.server_id, name=new_name) @test.attr(type=['negative', 'gate']) def test_delete_non_existent_server(self): # Delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.delete_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_delete_a_server_of_another_tenant(self): # Delete a server that belongs to another tenant self.assertRaises(exceptions.NotFound, self.alt_client.delete_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_delete_server_pass_negative_id(self): # Pass an invalid string parameter to delete server self.assertRaises(exceptions.NotFound, self.client.delete_server, -1) @test.attr(type=['negative', 'gate']) def test_delete_server_pass_id_exceeding_length_limit(self): # Pass a server ID that exceeds length limit to delete server self.assertRaises(exceptions.NotFound, self.client.delete_server, sys.maxint + 1) @test.attr(type=['negative', 'gate']) def test_create_with_nonexistent_security_group(self): # Create a server with a nonexistent security group security_groups = [{'name': 'does_not_exist'}] self.assertRaises(exceptions.BadRequest, self.create_test_server, security_groups=security_groups) @test.attr(type=['negative', 'gate']) def test_get_non_existent_server(self): # Get a non existent server details nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_stop_non_existent_server(self): # Stop a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.servers_client.stop, nonexistent_server) @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type=['negative', 'gate']) def test_pause_non_existent_server(self): # pause a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.pause_server, nonexistent_server) @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type=['negative', 'gate']) def test_unpause_non_existent_server(self): # unpause a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.unpause_server, nonexistent_server) @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type=['negative', 'gate']) def test_unpause_server_invalid_state(self): # unpause an active server. self.assertRaises(exceptions.Conflict, self.client.unpause_server, self.server_id) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_suspend_non_existent_server(self): # suspend a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.suspend_server, nonexistent_server) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_suspend_server_invalid_state(self): # suspend a suspended server. resp, _ = self.client.suspend_server(self.server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'SUSPENDED') self.assertRaises(exceptions.Conflict, self.client.suspend_server, self.server_id) self.client.resume_server(self.server_id) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_resume_non_existent_server(self): # resume a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.resume_server, nonexistent_server) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_resume_server_invalid_state(self): # resume an active server. self.assertRaises(exceptions.Conflict, self.client.resume_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_get_console_output_of_non_existent_server(self): # get the console output for a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_console_output, nonexistent_server, 10) @test.attr(type=['negative', 'gate']) def test_force_delete_nonexistent_server_id(self): # force-delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.force_delete_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_force_delete_server_invalid_state(self): # we can only force-delete a server in 'soft-delete' state self.assertRaises(exceptions.Conflict, self.client.force_delete_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_restore_nonexistent_server_id(self): # restore-delete a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.restore_soft_deleted_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_restore_server_invalid_state(self): # we can only restore-delete a server in 'soft-delete' state self.assertRaises(exceptions.Conflict, self.client.restore_soft_deleted_server, self.server_id) @test.attr(type=['negative', 'gate']) def test_shelve_non_existent_server(self): # shelve a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.shelve_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_shelve_shelved_server(self): # shelve a shelved server. resp, server = self.client.shelve_server(self.server_id) self.assertEqual(202, resp.status) offload_time = CONF.compute.shelved_offload_time if offload_time >= 0: self.client.wait_for_server_status(self.server_id, 'SHELVED_OFFLOADED', extra_timeout=offload_time) else: self.client.wait_for_server_status(self.server_id, 'SHELVED') resp, server = self.client.get_server(self.server_id) image_name = server['name'] + '-shelved' params = {'name': image_name} resp, images = self.images_client.list_images(params) self.assertEqual(1, len(images)) self.assertEqual(image_name, images[0]['name']) self.assertRaises(exceptions.Conflict, self.client.shelve_server, self.server_id) self.client.unshelve_server(self.server_id) @test.attr(type=['negative', 'gate']) def test_unshelve_non_existent_server(self): # unshelve a non existent server nonexistent_server = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.unshelve_server, nonexistent_server) @test.attr(type=['negative', 'gate']) def test_unshelve_server_invalid_state(self): # unshelve an active server. self.assertRaises(exceptions.Conflict, self.client.unshelve_server, self.server_id) class ServersNegativeTestXML(ServersNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/__init__.py0000664000175000017500000000000012332757070025573 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_servers.py0000664000175000017500000001146112332757070026601 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class ServersTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ServersTestJSON, cls).setUpClass() cls.client = cls.servers_client def tearDown(self): self.clear_servers() super(ServersTestJSON, self).tearDown() @test.attr(type='gate') def test_create_server_with_admin_password(self): # If an admin password is provided on server creation, the server's # root password should be set to that password. resp, server = self.create_test_server(adminPass='testpassword') # Verify the password is set correctly in the response self.assertEqual('testpassword', server['adminPass']) @test.attr(type='gate') def test_create_with_existing_server_name(self): # Creating a server with a name that already exists is allowed # TODO(sdague): clear out try, we do cleanup one layer up server_name = data_utils.rand_name('server') resp, server = self.create_test_server(name=server_name, wait_until='ACTIVE') id1 = server['id'] resp, server = self.create_test_server(name=server_name, wait_until='ACTIVE') id2 = server['id'] self.assertNotEqual(id1, id2, "Did not create a new server") resp, server = self.client.get_server(id1) name1 = server['name'] resp, server = self.client.get_server(id2) name2 = server['name'] self.assertEqual(name1, name2) @test.attr(type='gate') def test_create_specify_keypair(self): # Specify a keypair while creating a server key_name = data_utils.rand_name('key') resp, keypair = self.keypairs_client.create_keypair(key_name) resp, body = self.keypairs_client.list_keypairs() resp, server = self.create_test_server(key_name=key_name) self.assertEqual('202', resp['status']) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual(key_name, server['key_name']) @test.attr(type='gate') def test_update_server_name(self): # The server name should be changed to the the provided value resp, server = self.create_test_server(wait_until='ACTIVE') # Update the server with a new name resp, server = self.client.update_server(server['id'], name='newname') self.assertEqual(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the name of the server has changed resp, server = self.client.get_server(server['id']) self.assertEqual('newname', server['name']) @test.attr(type='gate') def test_update_access_server_address(self): # The server's access addresses should reflect the provided values resp, server = self.create_test_server(wait_until='ACTIVE') # Update the IPv4 and IPv6 access addresses resp, body = self.client.update_server(server['id'], accessIPv4='1.1.1.1', accessIPv6='::babe:202:202') self.assertEqual(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the access addresses have been updated resp, server = self.client.get_server(server['id']) self.assertEqual('1.1.1.1', server['accessIPv4']) self.assertEqual('::babe:202:202', server['accessIPv6']) @test.attr(type='gate') def test_create_server_with_ipv6_addr_only(self): # Create a server without an IPv4 address(only IPv6 address). resp, server = self.create_test_server(accessIPv6='2001:2001::3') self.assertEqual('202', resp['status']) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual('2001:2001::3', server['accessIPv6']) class ServersTestXML(ServersTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_addresses.py0000664000175000017500000000546212332757070030457 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class ServerAddressesTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): # This test module might use a network and a subnet cls.set_network_resources(network=True, subnet=True) super(ServerAddressesTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, cls.server = cls.create_test_server(wait_until='ACTIVE') @test.skip_because(bug="1210483", condition=CONF.service_available.neutron) @test.attr(type='smoke') def test_list_server_addresses(self): # All public and private addresses for # a server should be returned resp, addresses = self.client.list_addresses(self.server['id']) self.assertEqual('200', resp['status']) # We do not know the exact network configuration, but an instance # should at least have a single public or private address self.assertTrue(len(addresses) >= 1) for network_name, network_addresses in addresses.iteritems(): self.assertTrue(len(network_addresses) >= 1) for address in network_addresses: self.assertTrue(address['addr']) self.assertTrue(address['version']) @test.attr(type='smoke') def test_list_server_addresses_by_network(self): # Providing a network type should filter # the addresses return by that type resp, addresses = self.client.list_addresses(self.server['id']) # Once again we don't know the environment's exact network config, # but the response for each individual network should be the same # as the partial result of the full address list id = self.server['id'] for addr_type in addresses: resp, addr = self.client.list_addresses_by_network(id, addr_type) self.assertEqual('200', resp['status']) addr = addr[addr_type] for address in addresses[addr_type]: self.assertTrue(any([a for a in addr if a == address])) class ServerAddressesTestXML(ServerAddressesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_list_server_filters.py0000664000175000017500000002736412332757070031212 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.api import utils from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ListServerFiltersTestJSON(base.BaseV2ComputeTest): @classmethod @test.safe_setup def setUpClass(cls): cls.set_network_resources(network=True, subnet=True, dhcp=True) super(ListServerFiltersTestJSON, cls).setUpClass() cls.client = cls.servers_client # Check to see if the alternate image ref actually exists... images_client = cls.images_client resp, images = images_client.list_images() if cls.image_ref != cls.image_ref_alt and \ any([image for image in images if image['id'] == cls.image_ref_alt]): cls.multiple_images = True else: cls.image_ref_alt = cls.image_ref # Do some sanity checks here. If one of the images does # not exist, fail early since the tests won't work... try: cls.images_client.get_image(cls.image_ref) except exceptions.NotFound: raise RuntimeError("Image %s (image_ref) was not found!" % cls.image_ref) try: cls.images_client.get_image(cls.image_ref_alt) except exceptions.NotFound: raise RuntimeError("Image %s (image_ref_alt) was not found!" % cls.image_ref_alt) cls.s1_name = data_utils.rand_name(cls.__name__ + '-instance') resp, cls.s1 = cls.create_test_server(name=cls.s1_name, wait_until='ACTIVE') cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance') resp, cls.s2 = cls.create_test_server(name=cls.s2_name, image_id=cls.image_ref_alt, wait_until='ACTIVE') cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance') resp, cls.s3 = cls.create_test_server(name=cls.s3_name, flavor=cls.flavor_ref_alt, wait_until='ACTIVE') if (CONF.service_available.neutron and CONF.compute.allow_tenant_isolation): network = cls.isolated_creds.get_primary_network() cls.fixed_network_name = network['name'] else: cls.fixed_network_name = CONF.compute.fixed_network_name @utils.skip_unless_attr('multiple_images', 'Only one image found') @test.attr(type='gate') def test_list_servers_filter_by_image(self): # Filter the list of servers by image params = {'image': self.image_ref} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_flavor(self): # Filter the list of servers by flavor params = {'flavor': self.flavor_ref_alt} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_server_name(self): # Filter the list of servers by server name params = {'name': self.s1_name} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_server_status(self): # Filter the list of servers by server status params = {'status': 'active'} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_shutoff_status(self): # Filter the list of servers by server shutoff status params = {'status': 'shutoff'} self.client.stop(self.s1['id']) self.client.wait_for_server_status(self.s1['id'], 'SHUTOFF') resp, body = self.client.list_servers(params) self.client.start(self.s1['id']) self.client.wait_for_server_status(self.s1['id'], 'ACTIVE') servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_filter_by_limit(self): # Verify only the expected number of servers are returned params = {'limit': 1} resp, servers = self.client.list_servers(params) # when _interface='xml', one element for servers_links in servers self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x])) @test.attr(type='gate') def test_list_servers_filter_by_zero_limit(self): # Verify only the expected number of servers are returned params = {'limit': 0} resp, servers = self.client.list_servers(params) self.assertEqual(0, len(servers['servers'])) @test.attr(type='gate') def test_list_servers_filter_by_exceed_limit(self): # Verify only the expected number of servers are returned params = {'limit': 100000} resp, servers = self.client.list_servers(params) resp, all_servers = self.client.list_servers() self.assertEqual(len([x for x in all_servers['servers'] if 'id' in x]), len([x for x in servers['servers'] if 'id' in x])) @utils.skip_unless_attr('multiple_images', 'Only one image found') @test.attr(type='gate') def test_list_servers_detailed_filter_by_image(self): # Filter the detailed list of servers by image params = {'image': self.image_ref} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_detailed_filter_by_flavor(self): # Filter the detailed list of servers by flavor params = {'flavor': self.flavor_ref_alt} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_detailed_filter_by_server_name(self): # Filter the detailed list of servers by server name params = {'name': self.s1_name} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_detailed_filter_by_server_status(self): # Filter the detailed list of servers by server status params = {'status': 'active'} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers]) @test.attr(type='gate') def test_list_servers_filtered_by_name_wildcard(self): # List all servers that contains '-instance' in name params = {'name': '-instance'} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) # Let's take random part of name and try to search it part_name = self.s1_name[6:-1] params = {'name': part_name} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_filtered_by_ip(self): # Filter servers by ip # Here should be listed 1 server resp, self.s1 = self.client.get_server(self.s1['id']) ip = self.s1['addresses'][self.fixed_network_name][0]['addr'] params = {'ip': ip} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @test.skip_because(bug="1182883", condition=CONF.service_available.neutron) @test.attr(type='gate') def test_list_servers_filtered_by_ip_regex(self): # Filter servers by regex ip # List all servers filtered by part of ip address. # Here should be listed all servers resp, self.s1 = self.client.get_server(self.s1['id']) ip = self.s1['addresses'][self.fixed_network_name][0]['addr'][0:-3] params = {'ip': ip} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) @test.attr(type='gate') def test_list_servers_detailed_limit_results(self): # Verify only the expected number of detailed results are returned params = {'limit': 1} resp, servers = self.client.list_servers_with_detail(params) self.assertEqual(1, len(servers['servers'])) class ListServerFiltersTestXML(ListServerFiltersTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_delete_server.py0000664000175000017500000001611112332757070027735 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class DeleteServersTestJSON(base.BaseV2ComputeTest): # NOTE: Server creations of each test class should be under 10 # for preventing "Quota exceeded for instances" @classmethod def setUpClass(cls): super(DeleteServersTestJSON, cls).setUpClass() cls.client = cls.servers_client @test.attr(type='gate') def test_delete_server_while_in_building_state(self): # Delete a server while it's VM state is Building resp, server = self.create_test_server(wait_until='BUILD') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_active_server(self): # Delete a server while it's VM state is Active resp, server = self.create_test_server(wait_until='ACTIVE') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_server_while_in_shutoff_state(self): # Delete a server while it's VM state is Shutoff resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.stop(server['id']) self.client.wait_for_server_status(server['id'], 'SHUTOFF') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @testtools.skipUnless(CONF.compute_feature_enabled.pause, 'Pause is not available.') @test.attr(type='gate') def test_delete_server_while_in_pause_state(self): # Delete a server while it's VM state is Pause resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.pause_server(server['id']) self.client.wait_for_server_status(server['id'], 'PAUSED') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_server_while_in_shelved_state(self): # Delete a server while it's VM state is Shelved resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.shelve_server(server['id']) self.assertEqual(202, resp.status) offload_time = CONF.compute.shelved_offload_time if offload_time >= 0: self.client.wait_for_server_status(server['id'], 'SHELVED_OFFLOADED', extra_timeout=offload_time) else: self.client.wait_for_server_status(server['id'], 'SHELVED') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @testtools.skipIf(not CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_delete_server_while_in_verify_resize_state(self): # Delete a server while it's VM state is VERIFY_RESIZE resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.client.resize(server['id'], self.flavor_ref_alt) self.assertEqual(202, resp.status) self.client.wait_for_server_status(server['id'], 'VERIFY_RESIZE') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) @test.attr(type='gate') def test_delete_server_while_in_attached_volume(self): # Delete a server while a volume is attached to it volumes_client = self.volumes_extensions_client device = '/dev/%s' % CONF.compute.volume_device_name resp, server = self.create_test_server(wait_until='ACTIVE') resp, volume = volumes_client.create_volume(1) self.addCleanup(volumes_client.delete_volume, volume['id']) volumes_client.wait_for_volume_status(volume['id'], 'available') resp, body = self.client.attach_volume(server['id'], volume['id'], device=device) volumes_client.wait_for_volume_status(volume['id'], 'in-use') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id']) volumes_client.wait_for_volume_status(volume['id'], 'available') class DeleteServersAdminTestJSON(base.BaseV2ComputeAdminTest): # NOTE: Server creations of each test class should be under 10 # for preventing "Quota exceeded for instances". @classmethod def setUpClass(cls): super(DeleteServersAdminTestJSON, cls).setUpClass() cls.non_admin_client = cls.servers_client cls.admin_client = cls.os_adm.servers_client @test.attr(type='gate') def test_delete_server_while_in_error_state(self): # Delete a server while it's VM state is error resp, server = self.create_test_server(wait_until='ACTIVE') resp, body = self.admin_client.reset_state(server['id'], state='error') self.assertEqual(202, resp.status) # Verify server's state resp, server = self.non_admin_client.get_server(server['id']) self.assertEqual(server['status'], 'ERROR') resp, _ = self.non_admin_client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.servers_client.wait_for_server_termination(server['id'], ignore_error=True) @test.attr(type='gate') def test_admin_delete_servers_of_others(self): # Administrator can delete servers of others resp, server = self.create_test_server(wait_until='ACTIVE') resp, _ = self.admin_client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.servers_client.wait_for_server_termination(server['id']) class DeleteServersTestXML(DeleteServersTestJSON): _interface = 'xml' class DeleteServersAdminTestXML(DeleteServersAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_availability_zone.py0000664000175000017500000000254712332757070030622 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class AZV3Test(base.BaseComputeTest): """ Tests Availability Zone API List """ _api_version = 3 @classmethod def setUpClass(cls): super(AZV3Test, cls).setUpClass() cls.client = cls.availability_zone_client @test.attr(type='gate') def test_get_availability_zone_list_with_non_admin_user(self): # List of availability zone with non-administrator user resp, availability_zone = self.client.get_availability_zone_list() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) class AZV2TestJSON(AZV3Test): _api_version = 2 class AZV2TestXML(AZV2TestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_virtual_interfaces_negative.py0000664000175000017500000000303212332757070032656 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest import exceptions from tempest import test class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): # For this test no network resources are needed cls.set_network_resources() super(VirtualInterfacesNegativeTestJSON, cls).setUpClass() cls.client = cls.servers_client @test.attr(type=['negative', 'gate']) def test_list_virtual_interfaces_invalid_server_id(self): # Negative test: Should not be able to GET virtual interfaces # for an invalid server_id invalid_server_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.list_virtual_interfaces, invalid_server_id) class VirtualInterfacesNegativeTestXML(VirtualInterfacesNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_metadata.py0000664000175000017500000001221312332757070030252 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class ServerMetadataTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ServerMetadataTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.quotas = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE') cls.server_id = server['id'] def setUp(self): super(ServerMetadataTestJSON, self).setUp() meta = {'key1': 'value1', 'key2': 'value2'} resp, _ = self.client.set_server_metadata(self.server_id, meta) self.assertEqual(resp.status, 200) @test.attr(type='gate') def test_list_server_metadata(self): # All metadata key/value pairs for a server should be returned resp, resp_metadata = self.client.list_server_metadata(self.server_id) # Verify the expected metadata items are in the list self.assertEqual(200, resp.status) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_set_server_metadata(self): # The server's metadata should be replaced with the provided values # Create a new set of metadata for the server req_metadata = {'meta2': 'data2', 'meta3': 'data3'} resp, metadata = self.client.set_server_metadata(self.server_id, req_metadata) self.assertEqual(200, resp.status) # Verify the expected values are correct, and that the # previous values have been removed resp, resp_metadata = self.client.list_server_metadata(self.server_id) self.assertEqual(resp_metadata, req_metadata) @test.attr(type='gate') def test_update_server_metadata(self): # The server's metadata values should be updated to the # provided values meta = {'key1': 'alt1', 'key3': 'value3'} resp, metadata = self.client.update_server_metadata(self.server_id, meta) self.assertEqual(200, resp.status) # Verify the values have been updated to the proper values resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_update_metadata_empty_body(self): # The original metadata should not be lost if empty metadata body is # passed meta = {} _, metadata = self.client.update_server_metadata(self.server_id, meta) resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_get_server_metadata_item(self): # The value for a specific metadata key should be returned resp, meta = self.client.get_server_metadata_item(self.server_id, 'key2') self.assertEqual('value2', meta['key2']) @test.attr(type='gate') def test_set_server_metadata_item(self): # The item's value should be updated to the provided value # Update the metadata value meta = {'nova': 'alt'} resp, body = self.client.set_server_metadata_item(self.server_id, 'nova', meta) self.assertEqual(200, resp.status) # Verify the meta item's value has been updated resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_delete_server_metadata_item(self): # The metadata value/key pair should be deleted from the server resp, meta = self.client.delete_server_metadata_item(self.server_id, 'key1') self.assertEqual(204, resp.status) # Verify the metadata item has been removed resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key2': 'value2'} self.assertEqual(expected, resp_metadata) class ServerMetadataTestXML(ServerMetadataTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_instance_actions.py0000664000175000017500000000410412332757070030430 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class InstanceActionsTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(InstanceActionsTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.request_id = resp['x-compute-request-id'] cls.server_id = server['id'] @test.attr(type='gate') def test_list_instance_actions(self): # List actions of the provided server resp, body = self.client.reboot(self.server_id, 'HARD') self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, body = self.client.list_instance_actions(self.server_id) self.assertEqual(200, resp.status) self.assertTrue(len(body) == 2, str(body)) self.assertTrue(any([i for i in body if i['action'] == 'create'])) self.assertTrue(any([i for i in body if i['action'] == 'reboot'])) @test.attr(type='gate') def test_get_instance_action(self): # Get the action details of the provided server resp, body = self.client.get_instance_action(self.server_id, self.request_id) self.assertEqual(200, resp.status) self.assertEqual(self.server_id, body['instance_uuid']) self.assertEqual('create', body['action']) class InstanceActionsTestXML(InstanceActionsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_list_servers_negative.py0000664000175000017500000001644412332757070031524 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime from six import moves from tempest.api.compute import base from tempest import exceptions from tempest import test class ListServersNegativeTestJSON(base.BaseV2ComputeTest): force_tenant_isolation = True @classmethod @test.safe_setup def setUpClass(cls): super(ListServersNegativeTestJSON, cls).setUpClass() cls.client = cls.servers_client # The following servers are created for use # by the test methods in this class. These # servers are cleaned up automatically in the # tearDownClass method of the super-class. cls.existing_fixtures = [] cls.deleted_fixtures = [] cls.start_time = datetime.datetime.utcnow() for x in moves.xrange(2): resp, srv = cls.create_test_server() cls.existing_fixtures.append(srv) resp, srv = cls.create_test_server() cls.client.delete_server(srv['id']) # We ignore errors on termination because the server may # be put into ERROR status on a quick spawn, then delete, # as the compute node expects the instance local status # to be spawning, not deleted. See LP Bug#1061167 cls.client.wait_for_server_termination(srv['id'], ignore_error=True) cls.deleted_fixtures.append(srv) @test.attr(type=['negative', 'gate']) def test_list_servers_with_a_deleted_server(self): # Verify deleted servers do not show by default in list servers # List servers and verify server not returned resp, body = self.client.list_servers() servers = body['servers'] deleted_ids = [s['id'] for s in self.deleted_fixtures] actual = [srv for srv in servers if srv['id'] in deleted_ids] self.assertEqual('200', resp['status']) self.assertEqual([], actual) @test.attr(type=['negative', 'gate']) def test_list_servers_by_non_existing_image(self): # Listing servers for a non existing image returns empty list non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde' resp, body = self.client.list_servers(dict(image=non_existing_image)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type=['negative', 'gate']) def test_list_servers_by_non_existing_flavor(self): # Listing servers by non existing flavor returns empty list non_existing_flavor = 1234 resp, body = self.client.list_servers(dict(flavor=non_existing_flavor)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type=['negative', 'gate']) def test_list_servers_by_non_existing_server_name(self): # Listing servers for a non existent server name returns empty list non_existing_name = 'junk_server_1234' resp, body = self.client.list_servers(dict(name=non_existing_name)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type=['negative', 'gate']) def test_list_servers_status_non_existing(self): # Return an empty list when invalid status is specified non_existing_status = 'BALONEY' resp, body = self.client.list_servers(dict(status=non_existing_status)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type='gate') def test_list_servers_by_limits(self): # List servers by specifying limits resp, body = self.client.list_servers({'limit': 1}) self.assertEqual('200', resp['status']) # when _interface='xml', one element for servers_links in servers self.assertEqual(1, len([x for x in body['servers'] if 'id' in x])) @test.attr(type=['negative', 'gate']) def test_list_servers_by_limits_greater_than_actual_count(self): # List servers by specifying a greater value for limit resp, body = self.client.list_servers({'limit': 100}) self.assertEqual('200', resp['status']) self.assertEqual(len(self.existing_fixtures), len(body['servers'])) @test.attr(type=['negative', 'gate']) def test_list_servers_by_limits_pass_string(self): # Return an error if a string value is passed for limit self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'limit': 'testing'}) @test.attr(type=['negative', 'gate']) def test_list_servers_by_limits_pass_negative_value(self): # Return an error if a negative value for limit is passed self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'limit': -1}) @test.attr(type='gate') def test_list_servers_by_changes_since(self): # Servers are listed by specifying changes-since date changes_since = {'changes-since': self.start_time.isoformat()} resp, body = self.client.list_servers(changes_since) self.assertEqual('200', resp['status']) # changes-since returns all instances, including deleted. num_expected = (len(self.existing_fixtures) + len(self.deleted_fixtures)) self.assertEqual(num_expected, len(body['servers']), "Number of servers %d is wrong in %s" % (num_expected, body['servers'])) @test.attr(type=['negative', 'gate']) def test_list_servers_by_changes_since_invalid_date(self): # Return an error when invalid date format is passed self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'changes-since': '2011/01/01'}) @test.attr(type=['negative', 'gate']) def test_list_servers_by_changes_since_future_date(self): # Return an empty list when a date in the future is passed changes_since = {'changes-since': '2051-01-01T12:34:00Z'} resp, body = self.client.list_servers(changes_since) self.assertEqual('200', resp['status']) self.assertEqual(0, len(body['servers'])) @test.attr(type=['negative', 'gate']) def test_list_servers_detail_server_is_deleted(self): # Server details are not listed for a deleted server deleted_ids = [s['id'] for s in self.deleted_fixtures] resp, body = self.client.list_servers_with_detail() servers = body['servers'] actual = [srv for srv in servers if srv['id'] in deleted_ids] self.assertEqual('200', resp['status']) self.assertEqual([], actual) class ListServersNegativeTestXML(ListServersNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_multiple_create_negative.py0000664000175000017500000000530512332757070032150 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class MultipleCreateNegativeTestJSON(base.BaseV2ComputeTest): _name = 'multiple-create-test' def _generate_name(self): return data_utils.rand_name(self._name) def _create_multiple_servers(self, name=None, wait_until=None, **kwargs): """ This is the right way to create_multiple servers and manage to get the created servers into the servers list to be cleaned up after all. """ kwargs['name'] = kwargs.get('name', self._generate_name()) resp, body = self.create_test_server(**kwargs) return resp, body @test.attr(type=['negative', 'gate']) def test_min_count_less_than_one(self): invalid_min_count = 0 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=invalid_min_count) @test.attr(type=['negative', 'gate']) def test_min_count_non_integer(self): invalid_min_count = 2.5 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=invalid_min_count) @test.attr(type=['negative', 'gate']) def test_max_count_less_than_one(self): invalid_max_count = 0 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, max_count=invalid_max_count) @test.attr(type=['negative', 'gate']) def test_max_count_non_integer(self): invalid_max_count = 2.5 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, max_count=invalid_max_count) @test.attr(type=['negative', 'gate']) def test_max_count_less_than_min_count(self): min_count = 3 max_count = 2 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=min_count, max_count=max_count) class MultipleCreateNegativeTestXML(MultipleCreateNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_instance_actions_negative.py0000664000175000017500000000350212332757070032313 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class InstanceActionsNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(InstanceActionsNegativeTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] @test.attr(type=['negative', 'gate']) def test_list_instance_actions_non_existent_server(self): # List actions of the non-existent server id non_existent_server_id = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.list_instance_actions, non_existent_server_id) @test.attr(type=['negative', 'gate']) def test_get_instance_action_invalid_request(self): # Get the action details of the provided server with invalid request self.assertRaises(exceptions.NotFound, self.client.get_instance_action, self.server_id, '999') class InstanceActionsNegativeTestXML(InstanceActionsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_rescue.py0000664000175000017500000001221212332757070027757 0ustar chuckchuck00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class ServerRescueTestJSON(base.BaseV2ComputeTest): @classmethod @test.safe_setup def setUpClass(cls): cls.set_network_resources(network=True, subnet=True, router=True) super(ServerRescueTestJSON, cls).setUpClass() # Floating IP creation resp, body = cls.floating_ips_client.create_floating_ip() cls.floating_ip_id = str(body['id']).strip() cls.floating_ip = str(body['ip']).strip() # Security group creation cls.sg_name = data_utils.rand_name('sg') cls.sg_desc = data_utils.rand_name('sg-desc') resp, cls.sg = \ cls.security_groups_client.create_security_group(cls.sg_name, cls.sg_desc) cls.sg_id = cls.sg['id'] # Create a volume and wait for it to become ready for attach resp, cls.volume = cls.volumes_extensions_client.create_volume( 1, display_name=data_utils.rand_name(cls.__name__ + '_volume')) cls.volumes_extensions_client.wait_for_volume_status( cls.volume['id'], 'available') # Server for positive tests resp, server = cls.create_test_server(wait_until='BUILD') resp, resc_server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] cls.password = server['adminPass'] cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE') def setUp(self): super(ServerRescueTestJSON, self).setUp() @classmethod def tearDownClass(cls): # Deleting the floating IP which is created in this method cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id) cls.delete_volume(cls.volume['id']) resp, cls.sg = cls.security_groups_client.delete_security_group( cls.sg_id) super(ServerRescueTestJSON, cls).tearDownClass() def tearDown(self): super(ServerRescueTestJSON, self).tearDown() def _unrescue(self, server_id): resp, body = self.servers_client.unrescue_server(server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') @test.attr(type='smoke') def test_rescue_unrescue_instance(self): resp, body = self.servers_client.rescue_server( self.server_id, adminPass=self.password) self.assertEqual(200, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') resp, body = self.servers_client.unrescue_server(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') @test.attr(type='gate') def test_rescued_vm_associate_dissociate_floating_ip(self): # Rescue the server self.servers_client.rescue_server( self.server_id, adminPass=self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') self.addCleanup(self._unrescue, self.server_id) # Association of floating IP to a rescued vm client = self.floating_ips_client resp, body = client.associate_floating_ip_to_server(self.floating_ip, self.server_id) self.assertEqual(202, resp.status) # Disassociation of floating IP that was associated in this method resp, body = \ client.disassociate_floating_ip_from_server(self.floating_ip, self.server_id) self.assertEqual(202, resp.status) @test.attr(type='gate') def test_rescued_vm_add_remove_security_group(self): # Rescue the server self.servers_client.rescue_server( self.server_id, adminPass=self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') self.addCleanup(self._unrescue, self.server_id) # Add Security group resp, body = self.servers_client.add_security_group(self.server_id, self.sg_name) self.assertEqual(202, resp.status) # Delete Security group resp, body = self.servers_client.remove_security_group(self.server_id, self.sg_name) self.assertEqual(202, resp.status) class ServerRescueTestXML(ServerRescueTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/servers/test_server_personality.py0000664000175000017500000000477612332757070031062 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 from tempest.api.compute import base from tempest import exceptions from tempest import test class ServerPersonalityTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ServerPersonalityTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.user_client = cls.limits_client @test.attr(type='gate') def test_personality_files_exceed_limit(self): # Server creation should fail if greater than the maximum allowed # number of files are injected into the server. file_contents = 'This is a test file.' personality = [] max_file_limit = \ self.user_client.get_specific_absolute_limit("maxPersonality") for i in range(0, int(max_file_limit) + 1): path = 'etc/test' + str(i) + '.txt' personality.append({'path': path, 'contents': base64.b64encode(file_contents)}) self.assertRaises(exceptions.OverLimit, self.create_test_server, personality=personality) @test.attr(type='gate') def test_can_create_server_with_max_number_personality_files(self): # Server should be created successfully if maximum allowed number of # files is injected into the server during creation. file_contents = 'This is a test file.' max_file_limit = \ self.user_client.get_specific_absolute_limit("maxPersonality") person = [] for i in range(0, int(max_file_limit)): path = 'etc/test' + str(i) + '.txt' person.append({ 'path': path, 'contents': base64.b64encode(file_contents), }) resp, server = self.create_test_server(personality=person) self.assertEqual('202', resp['status']) class ServerPersonalityTestXML(ServerPersonalityTestJSON): _interface = "xml" tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/test_live_block_migration.py0000664000175000017500000001276312332757070027607 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest): _host_key = 'OS-EXT-SRV-ATTR:host' @classmethod def setUpClass(cls): super(LiveBlockMigrationTestJSON, cls).setUpClass() cls.admin_hosts_client = cls.os_adm.hosts_client cls.admin_servers_client = cls.os_adm.servers_client cls.created_server_ids = [] def _get_compute_hostnames(self): _resp, body = self.admin_hosts_client.list_hosts() return [ host_record['host_name'] for host_record in body if host_record['service'] == 'compute' ] def _get_server_details(self, server_id): _resp, body = self.admin_servers_client.get_server(server_id) return body def _get_host_for_server(self, server_id): return self._get_server_details(server_id)[self._host_key] def _migrate_server_to(self, server_id, dest_host): _resp, body = self.admin_servers_client.live_migrate_server( server_id, dest_host, CONF.compute_feature_enabled.block_migration_for_live_migration) return body def _get_host_other_than(self, host): for target_host in self._get_compute_hostnames(): if host != target_host: return target_host def _get_server_status(self, server_id): return self._get_server_details(server_id)['status'] def _get_an_active_server(self): for server_id in self.created_server_ids: if 'ACTIVE' == self._get_server_status(server_id): return server_id else: _, server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] self.password = server['adminPass'] self.password = 'password' self.created_server_ids.append(server_id) return server_id def _volume_clean_up(self, server_id, volume_id): resp, body = self.volumes_client.get_volume(volume_id) if body['status'] == 'in-use': self.servers_client.detach_volume(server_id, volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') self.volumes_client.delete_volume(volume_id) @testtools.skipIf(not CONF.compute_feature_enabled.live_migration, 'Live migration not available') @test.attr(type='gate') def test_live_block_migration(self): # Live block migrate an instance to another host if len(self._get_compute_hostnames()) < 2: raise self.skipTest( "Less than 2 compute nodes, skipping migration test.") server_id = self._get_an_active_server() actual_host = self._get_host_for_server(server_id) target_host = self._get_host_other_than(actual_host) self._migrate_server_to(server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.assertEqual(target_host, self._get_host_for_server(server_id)) @testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not CONF.compute_feature_enabled. block_migration_for_live_migration, 'Block Live migration not available') @testtools.skipIf(not CONF.compute_feature_enabled. block_migrate_cinder_iscsi, 'Block Live migration not configured for iSCSI') @test.attr(type='gate') def test_iscsi_volume(self): # Live block migrate an instance to another host if len(self._get_compute_hostnames()) < 2: raise self.skipTest( "Less than 2 compute nodes, skipping migration test.") server_id = self._get_an_active_server() actual_host = self._get_host_for_server(server_id) target_host = self._get_host_other_than(actual_host) resp, volume = self.volumes_client.create_volume(1, display_name='test') self.volumes_client.wait_for_volume_status(volume['id'], 'available') self.addCleanup(self._volume_clean_up, server_id, volume['id']) # Attach the volume to the server self.servers_client.attach_volume(server_id, volume['id'], device='/dev/xvdb') self.volumes_client.wait_for_volume_status(volume['id'], 'in-use') self._migrate_server_to(server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.assertEqual(target_host, self._get_host_for_server(server_id)) class LiveBlockMigrationTestXML(LiveBlockMigrationTestJSON): _host_key = ( '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host') _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/keypairs/0000775000175000017500000000000012332757136023635 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/keypairs/test_keypairs.py0000664000175000017500000001241312332757070027073 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class KeyPairsTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(KeyPairsTestJSON, cls).setUpClass() cls.client = cls.keypairs_client def _delete_keypair(self, keypair_name): resp, _ = self.client.delete_keypair(keypair_name) self.assertEqual(202, resp.status) def _create_keypair(self, keypair_name, pub_key=None): resp, body = self.client.create_keypair(keypair_name, pub_key) self.addCleanup(self._delete_keypair, keypair_name) return resp, body @test.attr(type='gate') def test_keypairs_create_list_delete(self): # Keypairs created should be available in the response list # Create 3 keypairs key_list = list() for i in range(3): k_name = data_utils.rand_name('keypair-') resp, keypair = self._create_keypair(k_name) # Need to pop these keys so that our compare doesn't fail later, # as the keypair dicts from list API doesn't have them. keypair.pop('private_key') keypair.pop('user_id') self.assertEqual(200, resp.status) key_list.append(keypair) # Fetch all keypairs and verify the list # has all created keypairs resp, fetched_list = self.client.list_keypairs() self.assertEqual(200, resp.status) # We need to remove the extra 'keypair' element in the # returned dict. See comment in keypairs_client.list_keypairs() new_list = list() for keypair in fetched_list: new_list.append(keypair['keypair']) fetched_list = new_list # Now check if all the created keypairs are in the fetched list missing_kps = [kp for kp in key_list if kp not in fetched_list] self.assertFalse(missing_kps, "Failed to find keypairs %s in fetched list" % ', '.join(m_key['name'] for m_key in missing_kps)) @test.attr(type='gate') def test_keypair_create_delete(self): # Keypair should be created, verified and deleted k_name = data_utils.rand_name('keypair-') resp, keypair = self._create_keypair(k_name) self.assertEqual(200, resp.status) private_key = keypair['private_key'] key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name") self.assertTrue(private_key is not None, "Field private_key is empty or not found.") @test.attr(type='gate') def test_get_keypair_detail(self): # Keypair should be created, Got details by name and deleted k_name = data_utils.rand_name('keypair-') resp, keypair = self._create_keypair(k_name) resp, keypair_detail = self.client.get_keypair(k_name) self.assertEqual(200, resp.status) self.assertIn('name', keypair_detail) self.assertIn('public_key', keypair_detail) self.assertEqual(keypair_detail['name'], k_name, "The created keypair name is not equal " "to requested name") public_key = keypair_detail['public_key'] self.assertTrue(public_key is not None, "Field public_key is empty or not found.") @test.attr(type='gate') def test_keypair_create_with_pub_key(self): # Keypair should be created with a given public key k_name = data_utils.rand_name('keypair-') pub_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs" "Ne3/1ILNCqFyfYWDeTKLD6jEXC2OQHLmietMWW+/vd" "aZq7KZEwO0jhglaFjU1mpqq4Gz5RX156sCTNM9vRbw" "KAxfsdF9laBYVsex3m3Wmui3uYrKyumsoJn2g9GNnG1P" "I1mrVjZ61i0GY3khna+wzlTpCCmy5HNlrmbj3XLqBUpip" "TOXmsnr4sChzC53KCd8LXuwc1i/CZPvF+3XipvAgFSE53pCt" "LOeB1kYMOBaiUPLQTWXR3JpckqFIQwhIH0zoHlJvZE8hh90" "XcPojYN56tI0OlrGqojbediJYD0rUsJu4weZpbn8vilb3JuDY+jws" "snSA8wzBx3A/8y9Pp1B nova@ubuntu") resp, keypair = self._create_keypair(k_name, pub_key) self.assertEqual(200, resp.status) self.assertFalse('private_key' in keypair, "Field private_key is not empty!") key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name!") class KeyPairsTestXML(KeyPairsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/keypairs/__init__.py0000664000175000017500000000000012332757070025731 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/keypairs/test_keypairs_negative.py0000664000175000017500000001013712332757070030756 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class KeyPairsNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(KeyPairsNegativeTestJSON, cls).setUpClass() cls.client = cls.keypairs_client def _create_keypair(self, keypair_name, pub_key=None): self.client.create_keypair(keypair_name, pub_key) self.addCleanup(self.client.delete_keypair, keypair_name) @test.attr(type=['negative', 'gate']) def test_keypair_create_with_invalid_pub_key(self): # Keypair should not be created with a non RSA public key k_name = data_utils.rand_name('keypair-') pub_key = "ssh-rsa JUNK nova@ubuntu" self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name, pub_key) @test.attr(type=['negative', 'gate']) def test_keypair_delete_nonexistent_key(self): # Non-existent key deletion should throw a proper error k_name = data_utils.rand_name("keypair-non-existent-") self.assertRaises(exceptions.NotFound, self.client.delete_keypair, k_name) @test.attr(type=['negative', 'gate']) def test_create_keypair_with_empty_public_key(self): # Keypair should not be created with an empty public key k_name = data_utils.rand_name("keypair-") pub_key = ' ' self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name, pub_key) @test.attr(type=['negative', 'gate']) def test_create_keypair_when_public_key_bits_exceeds_maximum(self): # Keypair should not be created when public key bits are too long k_name = data_utils.rand_name("keypair-") pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu' self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name, pub_key) @test.attr(type=['negative', 'gate']) def test_create_keypair_with_duplicate_name(self): # Keypairs with duplicate names should not be created k_name = data_utils.rand_name('keypair-') resp, _ = self.client.create_keypair(k_name) self.assertEqual(200, resp.status) # Now try the same keyname to create another key self.assertRaises(exceptions.Conflict, self._create_keypair, k_name) resp, _ = self.client.delete_keypair(k_name) self.assertEqual(202, resp.status) @test.attr(type=['negative', 'gate']) def test_create_keypair_with_empty_name_string(self): # Keypairs with name being an empty string should not be created self.assertRaises(exceptions.BadRequest, self._create_keypair, '') @test.attr(type=['negative', 'gate']) def test_create_keypair_with_long_keynames(self): # Keypairs with name longer than 255 chars should not be created k_name = 'keypair-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name) @test.attr(type=['negative', 'gate']) def test_create_keypair_invalid_name(self): # Keypairs with name being an invalid name should not be created k_name = 'key_/.\@:' self.assertRaises(exceptions.BadRequest, self._create_keypair, k_name) class KeyPairsNegativeTestXML(KeyPairsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/flavors/0000775000175000017500000000000012332757136023462 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/flavors/test_flavors.py0000664000175000017500000001260512332757070026550 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class FlavorsTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(FlavorsTestJSON, cls).setUpClass() cls.client = cls.flavors_client @test.attr(type='smoke') def test_list_flavors(self): # List of all flavors should contain the expected flavor resp, flavors = self.client.list_flavors() resp, flavor = self.client.get_flavor_details(self.flavor_ref) flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'], 'name': flavor['name']} self.assertIn(flavor_min_detail, flavors) @test.attr(type='smoke') def test_list_flavors_with_detail(self): # Detailed list of all flavors should contain the expected flavor resp, flavors = self.client.list_flavors_with_detail() resp, flavor = self.client.get_flavor_details(self.flavor_ref) self.assertIn(flavor, flavors) @test.attr(type='smoke') def test_get_flavor(self): # The expected flavor details should be returned resp, flavor = self.client.get_flavor_details(self.flavor_ref) self.assertEqual(self.flavor_ref, flavor['id']) @test.attr(type='gate') def test_list_flavors_limit_results(self): # Only the expected number of flavors should be returned params = {'limit': 1} resp, flavors = self.client.list_flavors(params) self.assertEqual(1, len(flavors)) @test.attr(type='gate') def test_list_flavors_detailed_limit_results(self): # Only the expected number of flavors (detailed) should be returned params = {'limit': 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertEqual(1, len(flavors)) @test.attr(type='gate') def test_list_flavors_using_marker(self): # The list of flavors should start from the provided marker resp, flavors = self.client.list_flavors() flavor_id = flavors[0]['id'] params = {'marker': flavor_id} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]), 'The list of flavors did not start after the marker.') @test.attr(type='gate') def test_list_flavors_detailed_using_marker(self): # The list of flavors should start from the provided marker resp, flavors = self.client.list_flavors_with_detail() flavor_id = flavors[0]['id'] params = {'marker': flavor_id} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]), 'The list of flavors did not start after the marker.') @test.attr(type='gate') def test_list_flavors_detailed_filter_by_min_disk(self): # The detailed list of flavors should be filtered by disk space resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['disk']) flavor_id = flavors[0]['id'] params = {'minDisk': flavors[0]['disk'] + 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @test.attr(type='gate') def test_list_flavors_detailed_filter_by_min_ram(self): # The detailed list of flavors should be filtered by RAM resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['ram']) flavor_id = flavors[0]['id'] params = {'minRam': flavors[0]['ram'] + 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @test.attr(type='gate') def test_list_flavors_filter_by_min_disk(self): # The list of flavors should be filtered by disk space resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['disk']) flavor_id = flavors[0]['id'] params = {'minDisk': flavors[0]['disk'] + 1} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @test.attr(type='gate') def test_list_flavors_filter_by_min_ram(self): # The list of flavors should be filtered by RAM resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['ram']) flavor_id = flavors[0]['id'] params = {'minRam': flavors[0]['ram'] + 1} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) class FlavorsTestXML(FlavorsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/flavors/test_flavors_negative_xml.py0000664000175000017500000000337312332757070031314 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest import exceptions from tempest import test class FlavorsNegativeTestXML(base.BaseV2ComputeTest): _interface = 'xml' @classmethod def setUpClass(cls): super(FlavorsNegativeTestXML, cls).setUpClass() cls.client = cls.flavors_client @test.attr(type=['negative', 'gate']) def test_invalid_minRam_filter(self): self.assertRaises(exceptions.BadRequest, self.client.list_flavors_with_detail, {'minRam': 'invalid'}) @test.attr(type=['negative', 'gate']) def test_invalid_minDisk_filter(self): self.assertRaises(exceptions.BadRequest, self.client.list_flavors_with_detail, {'minDisk': 'invalid'}) @test.attr(type=['negative', 'gate']) def test_non_existent_flavor_id(self): # flavor details are not returned for non-existent flavors nonexistent_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.get_flavor_details, nonexistent_flavor_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/flavors/__init__.py0000664000175000017500000000000012332757070025556 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/flavors/test_flavors_negative.py0000664000175000017500000000255212332757070030432 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test load_tests = test.NegativeAutoTest.load_tests @test.SimpleNegativeAutoTest class FlavorsListWithDetailsNegativeTestJSON(base.BaseV2ComputeTest, test.NegativeAutoTest): _service = 'compute' _schema_file = 'compute/flavors/flavors_list.json' @test.SimpleNegativeAutoTest class FlavorDetailsNegativeTestJSON(base.BaseV2ComputeTest, test.NegativeAutoTest): _service = 'compute' _schema_file = 'compute/flavors/flavor_details.json' @classmethod def setUpClass(cls): super(FlavorDetailsNegativeTestJSON, cls).setUpClass() cls.set_resource("flavor", cls.flavor_ref) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/0000775000175000017500000000000012332757136023253 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_images.py0000664000175000017500000000342512332757070026132 0ustar chuckchuck00000000000000# Copyright 2012 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. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class ImagesTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ImagesTestJSON, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client cls.servers_client = cls.servers_client @test.attr(type='gate') def test_delete_saving_image(self): snapshot_name = data_utils.rand_name('test-snap-') resp, server = self.create_test_server(wait_until='ACTIVE') self.addCleanup(self.servers_client.delete_server, server['id']) resp, image = self.create_image_from_server(server['id'], name=snapshot_name, wait_until='SAVING') resp, body = self.client.delete_image(image['id']) self.assertEqual('204', resp['status']) class ImagesTestXML(ImagesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_list_image_filters.py0000664000175000017500000002256312332757070030536 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ListImageFiltersTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ListImageFiltersTestJSON, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client try: resp, cls.server1 = cls.create_test_server() resp, cls.server2 = cls.create_test_server(wait_until='ACTIVE') # NOTE(sdague) this is faster than doing the sync wait_util on both cls.servers_client.wait_for_server_status(cls.server1['id'], 'ACTIVE') # Create images to be used in the filter tests resp, cls.image1 = cls.create_image_from_server( cls.server1['id'], wait_until='ACTIVE') cls.image1_id = cls.image1['id'] # Servers have a hidden property for when they are being imaged # Performing back-to-back create image calls on a single # server will sometimes cause failures resp, cls.image3 = cls.create_image_from_server( cls.server2['id'], wait_until='ACTIVE') cls.image3_id = cls.image3['id'] # Wait for the server to be active after the image upload resp, cls.image2 = cls.create_image_from_server( cls.server1['id'], wait_until='ACTIVE') cls.image2_id = cls.image2['id'] except Exception: LOG.exception('setUpClass failed') cls.tearDownClass() raise @test.attr(type='gate') def test_list_images_filter_by_status(self): # The list of images should contain only images with the # provided status params = {'status': 'ACTIVE'} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @test.attr(type='gate') def test_list_images_filter_by_name(self): # List of all images should contain the expected images filtered # by name params = {'name': self.image1['name']} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertFalse(any([i for i in images if i['id'] == self.image3_id])) @test.attr(type='gate') def test_list_images_filter_by_server_id(self): # The images should contain images filtered by server id params = {'server': self.server1['id']} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id]), "Failed to find image %s in images. Got images %s" % (self.image1_id, images)) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertFalse(any([i for i in images if i['id'] == self.image3_id])) @test.attr(type='gate') def test_list_images_filter_by_server_ref(self): # The list of servers should be filtered by server ref server_links = self.server2['links'] # Try all server link types for link in server_links: params = {'server': link['href']} resp, images = self.client.list_images(params) self.assertFalse(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @test.attr(type='gate') def test_list_images_filter_by_type(self): # The list of servers should be filtered by image type params = {'type': 'snapshot'} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) self.assertFalse(any([i for i in images if i['id'] == self.image_ref])) @test.attr(type='gate') def test_list_images_limit_results(self): # Verify only the expected number of results are returned params = {'limit': '1'} resp, images = self.client.list_images(params) # when _interface='xml', one element for images_links in images # ref: Question #224349 self.assertEqual(1, len([x for x in images if 'id' in x])) @test.attr(type='gate') def test_list_images_filter_by_changes_since(self): # Verify only updated images are returned in the detailed list # Becoming ACTIVE will modify the updated time # Filter by the image's created time params = {'changes-since': self.image3['created']} resp, images = self.client.list_images(params) found = any([i for i in images if i['id'] == self.image3_id]) self.assertTrue(found) @test.attr(type='gate') def test_list_images_with_detail_filter_by_status(self): # Detailed list of all images should only contain images # with the provided status params = {'status': 'ACTIVE'} resp, images = self.client.list_images_with_detail(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @test.attr(type='gate') def test_list_images_with_detail_filter_by_name(self): # Detailed list of all images should contain the expected # images filtered by name params = {'name': self.image1['name']} resp, images = self.client.list_images_with_detail(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertFalse(any([i for i in images if i['id'] == self.image3_id])) @test.attr(type='gate') def test_list_images_with_detail_limit_results(self): # Verify only the expected number of results (with full details) # are returned params = {'limit': '1'} resp, images = self.client.list_images_with_detail(params) self.assertEqual(1, len(images)) @test.attr(type='gate') def test_list_images_with_detail_filter_by_server_ref(self): # Detailed list of servers should be filtered by server ref server_links = self.server2['links'] # Try all server link types for link in server_links: params = {'server': link['href']} resp, images = self.client.list_images_with_detail(params) self.assertFalse(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @test.attr(type='gate') def test_list_images_with_detail_filter_by_type(self): # The detailed list of servers should be filtered by image type params = {'type': 'snapshot'} resp, images = self.client.list_images_with_detail(params) resp, image4 = self.client.get_image(self.image_ref) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) self.assertFalse(any([i for i in images if i['id'] == self.image_ref])) @test.attr(type='gate') def test_list_images_with_detail_filter_by_changes_since(self): # Verify an update image is returned # Becoming ACTIVE will modify the updated time # Filter by the image's created time params = {'changes-since': self.image1['created']} resp, images = self.client.list_images_with_detail(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) class ListImageFiltersTestXML(ListImageFiltersTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_images_oneserver_negative.py0000664000175000017500000001411312332757070032100 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ImagesOneServerNegativeTestJSON(base.BaseV2ComputeTest): def tearDown(self): """Terminate test instances created after a test is executed.""" for image_id in self.image_ids: self.client.delete_image(image_id) self.image_ids.remove(image_id) self.server_check_teardown() super(ImagesOneServerNegativeTestJSON, self).tearDown() def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ImagesOneServerNegativeTestJSON, self).setUp() # Check if the server is in a clean state after test try: self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: LOG.exception('server %s timed out to become ACTIVE. rebuilding' % self.server_id) # Rebuild server if cannot reach the ACTIVE state # Usually it means the server had a serious accident self._reset_server() def _reset_server(self): self.__class__.server_id = self.rebuild_server(self.server_id) @classmethod def setUpClass(cls): super(ImagesOneServerNegativeTestJSON, cls).setUpClass() cls.client = cls.images_client if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) try: resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] except Exception: cls.tearDownClass() raise cls.image_ids = [] @test.skip_because(bug="1006725") @test.attr(type=['negative', 'gate']) def test_create_image_specify_multibyte_character_image_name(self): if self.__class__._interface == "xml": raise self.skipException("Not testable in XML") # invalid multibyte sequence from: # http://stackoverflow.com/questions/1301402/ # example-invalid-utf8-string invalid_name = data_utils.rand_name(u'\xc3\x28') self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server_id, invalid_name) @test.attr(type=['negative', 'gate']) def test_create_image_specify_invalid_metadata(self): # Return an error when creating image with invalid metadata snapshot_name = data_utils.rand_name('test-snap-') meta = {'': ''} self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server_id, snapshot_name, meta) @test.attr(type=['negative', 'gate']) def test_create_image_specify_metadata_over_limits(self): # Return an error when creating image with meta data over 256 chars snapshot_name = data_utils.rand_name('test-snap-') meta = {'a' * 260: 'b' * 260} self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server_id, snapshot_name, meta) @test.attr(type=['negative', 'gate']) def test_create_second_image_when_first_image_is_being_saved(self): # Disallow creating another image when first image is being saved # Create first snapshot snapshot_name = data_utils.rand_name('test-snap-') resp, body = self.client.create_image(self.server_id, snapshot_name) self.assertEqual(202, resp.status) image_id = data_utils.parse_image_id(resp['location']) self.image_ids.append(image_id) self.addCleanup(self._reset_server) # Create second snapshot alt_snapshot_name = data_utils.rand_name('test-snap-') self.assertRaises(exceptions.Conflict, self.client.create_image, self.server_id, alt_snapshot_name) @test.attr(type=['negative', 'gate']) def test_create_image_specify_name_over_256_chars(self): # Return an error if snapshot name over 256 characters is passed snapshot_name = data_utils.rand_name('a' * 260) self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server_id, snapshot_name) @test.attr(type=['negative', 'gate']) def test_delete_image_that_is_not_yet_active(self): # Return an error while trying to delete an image what is creating snapshot_name = data_utils.rand_name('test-snap-') resp, body = self.client.create_image(self.server_id, snapshot_name) self.assertEqual(202, resp.status) image_id = data_utils.parse_image_id(resp['location']) self.image_ids.append(image_id) self.addCleanup(self._reset_server) # Do not wait, attempt to delete the image, ensure it's successful resp, body = self.client.delete_image(image_id) self.assertEqual('204', resp['status']) self.image_ids.remove(image_id) self.assertRaises(exceptions.NotFound, self.client.get_image, image_id) class ImagesOneServerNegativeTestXML(ImagesOneServerNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_list_image_filters_negative.py0000664000175000017500000000306012332757070032407 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ListImageFiltersNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ListImageFiltersNegativeTestJSON, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client @test.attr(type=['negative', 'gate']) def test_get_nonexistent_image(self): # Check raises a NotFound nonexistent_image = data_utils.rand_uuid() self.assertRaises(exceptions.NotFound, self.client.get_image, nonexistent_image) class ListImageFiltersNegativeTestXML(ListImageFiltersNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_list_images.py0000664000175000017500000000367712332757070027176 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class ListImagesTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ListImagesTestJSON, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client @test.attr(type='smoke') def test_get_image(self): # Returns the correct details for a single image resp, image = self.client.get_image(self.image_ref) self.assertEqual(self.image_ref, image['id']) @test.attr(type='smoke') def test_list_images(self): # The list of all images should contain the image resp, images = self.client.list_images() found = any([i for i in images if i['id'] == self.image_ref]) self.assertTrue(found) @test.attr(type='smoke') def test_list_images_with_detail(self): # Detailed list of all images should contain the expected images resp, images = self.client.list_images_with_detail() found = any([i for i in images if i['id'] == self.image_ref]) self.assertTrue(found) class ListImagesTestXML(ListImagesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_images_oneserver.py0000664000175000017500000001116112332757070030216 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ImagesOneServerTestJSON(base.BaseV2ComputeTest): def tearDown(self): """Terminate test instances created after a test is executed.""" self.server_check_teardown() super(ImagesOneServerTestJSON, self).tearDown() def setUp(self): # NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ImagesOneServerTestJSON, self).setUp() # Check if the server is in a clean state after test try: self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: LOG.exception('server %s timed out to become ACTIVE. rebuilding' % self.server_id) # Rebuild server if cannot reach the ACTIVE state # Usually it means the server had a serious accident self.__class__.server_id = self.rebuild_server(self.server_id) @classmethod def setUpClass(cls): super(ImagesOneServerTestJSON, cls).setUpClass() cls.client = cls.images_client if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) try: resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] except Exception: cls.tearDownClass() raise def _get_default_flavor_disk_size(self, flavor_id): resp, flavor = self.flavors_client.get_flavor_details(flavor_id) return flavor['disk'] @test.attr(type='smoke') def test_create_delete_image(self): # Create a new image name = data_utils.rand_name('image') meta = {'image_type': 'test'} resp, body = self.client.create_image(self.server_id, name, meta) self.assertEqual(202, resp.status) image_id = data_utils.parse_image_id(resp['location']) self.client.wait_for_image_status(image_id, 'ACTIVE') # Verify the image was created correctly resp, image = self.client.get_image(image_id) self.assertEqual(name, image['name']) self.assertEqual('test', image['metadata']['image_type']) resp, original_image = self.client.get_image(self.image_ref) # Verify minRAM is the same as the original image self.assertEqual(image['minRam'], original_image['minRam']) # Verify minDisk is the same as the original image or the flavor size flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref) self.assertIn(str(image['minDisk']), (str(original_image['minDisk']), str(flavor_disk_size))) # Verify the image was deleted correctly resp, body = self.client.delete_image(image_id) self.assertEqual('204', resp['status']) self.client.wait_for_resource_deletion(image_id) @test.attr(type=['gate']) def test_create_image_specify_multibyte_character_image_name(self): if self.__class__._interface == "xml": # NOTE(sdague): not entirely accurage, but we'd need a ton of work # in our XML client to make this good raise self.skipException("Not testable in XML") # prefix character is: # http://www.fileformat.info/info/unicode/char/1F4A9/index.htm utf8_name = data_utils.rand_name(u'\xF0\x9F\x92\xA9') resp, body = self.client.create_image(self.server_id, utf8_name) image_id = data_utils.parse_image_id(resp['location']) self.addCleanup(self.client.delete_image, image_id) self.assertEqual('202', resp['status']) class ImagesOneServerTestXML(ImagesOneServerTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_image_metadata_negative.py0000664000175000017500000000623312332757070031471 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class ImagesMetadataTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ImagesMetadataTestJSON, cls).setUpClass() cls.client = cls.images_client @test.attr(type=['negative', 'gate']) def test_list_nonexistent_image_metadata(self): # Negative test: List on nonexistent image # metadata should not happen self.assertRaises(exceptions.NotFound, self.client.list_image_metadata, data_utils.rand_uuid()) @test.attr(type=['negative', 'gate']) def test_update_nonexistent_image_metadata(self): # Negative test:An update should not happen for a non-existent image meta = {'key1': 'alt1', 'key2': 'alt2'} self.assertRaises(exceptions.NotFound, self.client.update_image_metadata, data_utils.rand_uuid(), meta) @test.attr(type=['negative', 'gate']) def test_get_nonexistent_image_metadata_item(self): # Negative test: Get on non-existent image should not happen self.assertRaises(exceptions.NotFound, self.client.get_image_metadata_item, data_utils.rand_uuid(), 'key2') @test.attr(type=['negative', 'gate']) def test_set_nonexistent_image_metadata(self): # Negative test: Metadata should not be set to a non-existent image meta = {'key1': 'alt1', 'key2': 'alt2'} self.assertRaises(exceptions.NotFound, self.client.set_image_metadata, data_utils.rand_uuid(), meta) @test.attr(type=['negative', 'gate']) def test_set_nonexistent_image_metadata_item(self): # Negative test: Metadata item should not be set to a # nonexistent image meta = {'key1': 'alt'} self.assertRaises(exceptions.NotFound, self.client.set_image_metadata_item, data_utils.rand_uuid(), 'key1', meta) @test.attr(type=['negative', 'gate']) def test_delete_nonexistent_image_metadata_item(self): # Negative test: Shouldn't be able to delete metadata # item from non-existent image self.assertRaises(exceptions.NotFound, self.client.delete_image_metadata_item, data_utils.rand_uuid(), 'key1') class ImagesMetadataTestXML(ImagesMetadataTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/__init__.py0000664000175000017500000000000012332757070025347 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_images_negative.py0000664000175000017500000001344212332757070030014 0ustar chuckchuck00000000000000# Copyright 2012 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. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ImagesNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(ImagesNegativeTestJSON, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client cls.servers_client = cls.servers_client @test.attr(type=['negative', 'gate']) def test_create_image_from_deleted_server(self): # An image should not be created if the server instance is removed resp, server = self.create_test_server(wait_until='ACTIVE') # Delete server before trying to create server self.servers_client.delete_server(server['id']) self.servers_client.wait_for_server_termination(server['id']) # Create a new image after server is deleted name = data_utils.rand_name('image') meta = {'image_type': 'test'} self.assertRaises(exceptions.NotFound, self.create_image_from_server, server['id'], name=name, meta=meta) @test.attr(type=['negative', 'gate']) def test_create_image_from_invalid_server(self): # An image should not be created with invalid server id # Create a new image with invalid server id name = data_utils.rand_name('image') meta = {'image_type': 'test'} resp = {} resp['status'] = None self.assertRaises(exceptions.NotFound, self.create_image_from_server, '!@#$%^&*()', name=name, meta=meta) @test.attr(type=['negative', 'gate']) def test_create_image_from_stopped_server(self): resp, server = self.create_test_server(wait_until='ACTIVE') self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.addCleanup(self.servers_client.delete_server, server['id']) snapshot_name = data_utils.rand_name('test-snap-') resp, image = self.create_image_from_server(server['id'], name=snapshot_name, wait_until='ACTIVE', wait_for_server=False) self.addCleanup(self.client.delete_image, image['id']) self.assertEqual(snapshot_name, image['name']) @test.attr(type=['negative', 'gate']) def test_create_image_specify_uuid_35_characters_or_less(self): # Return an error if Image ID passed is 35 characters or less snapshot_name = data_utils.rand_name('test-snap-') test_uuid = ('a' * 35) self.assertRaises(exceptions.NotFound, self.client.create_image, test_uuid, snapshot_name) @test.attr(type=['negative', 'gate']) def test_create_image_specify_uuid_37_characters_or_more(self): # Return an error if Image ID passed is 37 characters or more snapshot_name = data_utils.rand_name('test-snap-') test_uuid = ('a' * 37) self.assertRaises(exceptions.NotFound, self.client.create_image, test_uuid, snapshot_name) @test.attr(type=['negative', 'gate']) def test_delete_image_with_invalid_image_id(self): # An image should not be deleted with invalid image id self.assertRaises(exceptions.NotFound, self.client.delete_image, '!@$%^&*()') @test.attr(type=['negative', 'gate']) def test_delete_non_existent_image(self): # Return an error while trying to delete a non-existent image non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa' self.assertRaises(exceptions.NotFound, self.client.delete_image, non_existent_image_id) @test.attr(type=['negative', 'gate']) def test_delete_image_blank_id(self): # Return an error while trying to delete an image with blank Id self.assertRaises(exceptions.NotFound, self.client.delete_image, '') @test.attr(type=['negative', 'gate']) def test_delete_image_non_hex_string_id(self): # Return an error while trying to delete an image with non hex id image_id = '11a22b9-120q-5555-cc11-00ab112223gj' self.assertRaises(exceptions.NotFound, self.client.delete_image, image_id) @test.attr(type=['negative', 'gate']) def test_delete_image_negative_image_id(self): # Return an error while trying to delete an image with negative id self.assertRaises(exceptions.NotFound, self.client.delete_image, -1) @test.attr(type=['negative', 'gate']) def test_delete_image_id_is_over_35_character_limit(self): # Return an error while trying to delete image with id over limit self.assertRaises(exceptions.NotFound, self.client.delete_image, '11a22b9-12a9-5555-cc11-00ab112223fa-3fac') class ImagesNegativeTestXML(ImagesNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/images/test_image_metadata.py0000664000175000017500000001115712332757070027610 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class ImagesMetadataTestJSON(base.BaseV2ComputeTest): @classmethod @test.safe_setup def setUpClass(cls): super(ImagesMetadataTestJSON, cls).setUpClass() if not CONF.service_available.glance: skip_msg = ("%s skipped as glance is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.client = cls.images_client cls.image_id = None resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] # Snapshot the server once to save time name = data_utils.rand_name('image') resp, _ = cls.client.create_image(cls.server_id, name, {}) cls.image_id = resp['location'].rsplit('/', 1)[1] cls.client.wait_for_image_status(cls.image_id, 'ACTIVE') @classmethod def tearDownClass(cls): if cls.image_id: cls.client.delete_image(cls.image_id) super(ImagesMetadataTestJSON, cls).tearDownClass() def setUp(self): super(ImagesMetadataTestJSON, self).setUp() meta = {'key1': 'value1', 'key2': 'value2'} resp, _ = self.client.set_image_metadata(self.image_id, meta) self.assertEqual(resp.status, 200) @test.attr(type='gate') def test_list_image_metadata(self): # All metadata key/value pairs for an image should be returned resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_set_image_metadata(self): # The metadata for the image should match the new values req_metadata = {'meta2': 'value2', 'meta3': 'value3'} resp, body = self.client.set_image_metadata(self.image_id, req_metadata) resp, resp_metadata = self.client.list_image_metadata(self.image_id) self.assertEqual(req_metadata, resp_metadata) @test.attr(type='gate') def test_update_image_metadata(self): # The metadata for the image should match the updated values req_metadata = {'key1': 'alt1', 'key3': 'value3'} resp, metadata = self.client.update_image_metadata(self.image_id, req_metadata) resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_get_image_metadata_item(self): # The value for a specific metadata key should be returned resp, meta = self.client.get_image_metadata_item(self.image_id, 'key2') self.assertEqual('value2', meta['key2']) @test.attr(type='gate') def test_set_image_metadata_item(self): # The value provided for the given meta item should be set for # the image meta = {'key1': 'alt'} resp, body = self.client.set_image_metadata_item(self.image_id, 'key1', meta) resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key1': 'alt', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @test.attr(type='gate') def test_delete_image_metadata_item(self): # The metadata value/key pair should be deleted from the image resp, body = self.client.delete_image_metadata_item(self.image_id, 'key1') resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key2': 'value2'} self.assertEqual(expected, resp_metadata) class ImagesMetadataTestXML(ImagesMetadataTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/__init__.py0000664000175000017500000000000012332757070024102 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/test_extensions.py0000664000175000017500000000416112332757070025615 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class ExtensionsTestJSON(base.BaseV2ComputeTest): @test.attr(type='gate') def test_list_extensions(self): # List of all extensions if len(CONF.compute_feature_enabled.api_extensions) == 0: raise self.skipException('There are not any extensions configured') resp, extensions = self.extensions_client.list_extensions() self.assertEqual(200, resp.status) ext = CONF.compute_feature_enabled.api_extensions[0] if ext == 'all': self.assertIn('Hosts', map(lambda x: x['name'], extensions)) elif ext: self.assertIn(ext, map(lambda x: x['name'], extensions)) else: raise self.skipException('There are not any extensions configured') # Log extensions list extension_list = map(lambda x: x['name'], extensions) LOG.debug("Nova extensions: %s" % ','.join(extension_list)) @test.requires_ext(extension='os-consoles', service='compute') @test.attr(type='gate') def test_get_extension(self): # get the specified extensions resp, extension = self.extensions_client.get_extension('os-consoles') self.assertEqual(200, resp.status) self.assertEqual('os-consoles', extension['alias']) class ExtensionsTestXML(ExtensionsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/limits/0000775000175000017500000000000012332757136023307 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/limits/__init__.py0000664000175000017500000000000012332757070025403 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/limits/test_absolute_limits.py0000664000175000017500000000406712332757070030123 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(AbsoluteLimitsTestJSON, cls).setUpClass() cls.client = cls.limits_client @test.attr(type='gate') def test_absLimits_get(self): # To check if all limits are present in the response resp, absolute_limits = self.client.get_absolute_limits() expected_elements = ['maxImageMeta', 'maxPersonality', 'maxPersonalitySize', 'maxServerMeta', 'maxTotalCores', 'maxTotalFloatingIps', 'maxSecurityGroups', 'maxSecurityGroupRules', 'maxTotalInstances', 'maxTotalKeypairs', 'maxTotalRAMSize', 'totalCoresUsed', 'totalFloatingIpsUsed', 'totalSecurityGroupsUsed', 'totalInstancesUsed', 'totalRAMUsed'] # check whether all expected elements exist missing_elements =\ [ele for ele in expected_elements if ele not in absolute_limits] self.assertEqual(0, len(missing_elements), "Failed to find element %s in absolute limits list" % ', '.join(ele for ele in missing_elements)) class AbsoluteLimitsTestXML(AbsoluteLimitsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/limits/test_absolute_limits_negative.py0000664000175000017500000000346412332757070032005 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import exceptions from tempest import test class AbsoluteLimitsNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(AbsoluteLimitsNegativeTestJSON, cls).setUpClass() cls.client = cls.limits_client cls.server_client = cls.servers_client @test.attr(type=['negative', 'gate']) def test_max_image_meta_exceed_limit(self): # We should not create vm with image meta over maxImageMeta limit # Get max limit value max_meta = self.client.get_specific_absolute_limit('maxImageMeta') # Create server should fail, since we are passing > metadata Limit! max_meta_data = int(max_meta) + 1 meta_data = {} for xx in range(max_meta_data): meta_data[str(xx)] = str(xx) self.assertRaises(exceptions.OverLimit, self.server_client.create_server, name='test', meta=meta_data, flavor_ref=self.flavor_ref, image_ref=self.image_ref) class AbsoluteLimitsNegativeTestXML(AbsoluteLimitsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/test_live_block_migration_negative.py0000664000175000017500000000433212332757070031462 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class LiveBlockMigrationNegativeTestJSON(base.BaseV2ComputeAdminTest): _host_key = 'OS-EXT-SRV-ATTR:host' @classmethod def setUpClass(cls): super(LiveBlockMigrationNegativeTestJSON, cls).setUpClass() if not CONF.compute_feature_enabled.live_migration: raise cls.skipException("Live migration is not enabled") cls.admin_hosts_client = cls.os_adm.hosts_client cls.admin_servers_client = cls.os_adm.servers_client def _migrate_server_to(self, server_id, dest_host): _resp, body = self.admin_servers_client.live_migrate_server( server_id, dest_host, CONF.compute_feature_enabled. block_migration_for_live_migration) return body @test.attr(type=['negative', 'gate']) def test_invalid_host_for_migration(self): # Migrating to an invalid host should not change the status target_host = data_utils.rand_name('host-') _, server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] self.assertRaises(exceptions.BadRequest, self._migrate_server_to, server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') class LiveBlockMigrationNegativeTestXML(LiveBlockMigrationNegativeTestJSON): _host_key = ( '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host') _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/test_quotas.py0000664000175000017500000000704612332757070024737 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class QuotasTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(QuotasTestJSON, cls).setUpClass() cls.client = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] resp, users = cls.admin_client.list_users_for_tenant(cls.tenant_id) cls.user_id = [user['id'] for user in users if user['name'] == cls.client.user][0] cls.default_quota_set = set(('injected_file_content_bytes', 'metadata_items', 'injected_files', 'ram', 'floating_ips', 'fixed_ips', 'key_pairs', 'injected_file_path_bytes', 'instances', 'security_group_rules', 'cores', 'security_groups')) @test.attr(type='smoke') def test_get_quotas(self): # User can get the quota set for it's tenant expected_quota_set = self.default_quota_set | set(['id']) resp, quota_set = self.client.get_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.tenant_id) # get the quota set using user id resp, quota_set = self.client.get_quota_set(self.tenant_id, self.user_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.tenant_id) @test.attr(type='smoke') def test_get_default_quotas(self): # User can get the default quota set for it's tenant expected_quota_set = self.default_quota_set | set(['id']) resp, quota_set = self.client.get_default_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.tenant_id) @test.attr(type='smoke') def test_compare_tenant_quotas_with_default_quotas(self): # Tenants are created with the default quota values resp, defualt_quota_set = \ self.client.get_default_quota_set(self.tenant_id) self.assertEqual(200, resp.status) resp, tenant_quota_set = self.client.get_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(defualt_quota_set, tenant_quota_set) class QuotasTestXML(QuotasTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/test_authorization.py0000664000175000017500000004144412332757070026323 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class AuthorizationTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): # No network resources required for this test cls.set_network_resources() super(AuthorizationTestJSON, cls).setUpClass() if not cls.multi_user: msg = "Need >1 user" raise cls.skipException(msg) cls.client = cls.os.servers_client cls.images_client = cls.os.images_client cls.keypairs_client = cls.os.keypairs_client cls.security_client = cls.os.security_groups_client if CONF.compute.allow_tenant_isolation: creds = cls.isolated_creds.get_alt_creds() cls.alt_manager = clients.Manager(credentials=creds) else: # Use the alt_XXX credentials in the config file cls.alt_manager = clients.AltManager() cls.alt_client = cls.alt_manager.servers_client cls.alt_images_client = cls.alt_manager.images_client cls.alt_keypairs_client = cls.alt_manager.keypairs_client cls.alt_security_client = cls.alt_manager.security_groups_client resp, server = cls.create_test_server(wait_until='ACTIVE') resp, cls.server = cls.client.get_server(server['id']) name = data_utils.rand_name('image') resp, body = cls.images_client.create_image(server['id'], name) image_id = data_utils.parse_image_id(resp['location']) cls.images_client.wait_for_image_status(image_id, 'ACTIVE') resp, cls.image = cls.images_client.get_image(image_id) cls.keypairname = data_utils.rand_name('keypair') resp, keypair = \ cls.keypairs_client.create_keypair(cls.keypairname) name = data_utils.rand_name('security') description = data_utils.rand_name('description') resp, cls.security_group = cls.security_client.create_security_group( name, description) parent_group_id = cls.security_group['id'] ip_protocol = 'tcp' from_port = 22 to_port = 22 resp, cls.rule = cls.security_client.create_security_group_rule( parent_group_id, ip_protocol, from_port, to_port) @classmethod def tearDownClass(cls): if cls.multi_user: cls.images_client.delete_image(cls.image['id']) cls.keypairs_client.delete_keypair(cls.keypairname) cls.security_client.delete_security_group(cls.security_group['id']) super(AuthorizationTestJSON, cls).tearDownClass() @test.attr(type='gate') def test_get_server_for_alt_account_fails(self): # A GET request for a server on another user's account should fail self.assertRaises(exceptions.NotFound, self.alt_client.get_server, self.server['id']) @test.attr(type='gate') def test_delete_server_for_alt_account_fails(self): # A DELETE request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.delete_server, self.server['id']) @test.attr(type='gate') def test_update_server_for_alt_account_fails(self): # An update server request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.update_server, self.server['id'], name='test') @test.attr(type='gate') def test_list_server_addresses_for_alt_account_fails(self): # A list addresses request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.list_addresses, self.server['id']) @test.attr(type='gate') def test_list_server_addresses_by_network_for_alt_account_fails(self): # A list address/network request for another user's server should fail server_id = self.server['id'] self.assertRaises(exceptions.NotFound, self.alt_client.list_addresses_by_network, server_id, 'public') @test.attr(type='gate') def test_list_servers_with_alternate_tenant(self): # A list on servers from one tenant should not # show on alternate tenant # Listing servers from alternate tenant alt_server_ids = [] resp, body = self.alt_client.list_servers() alt_server_ids = [s['id'] for s in body['servers']] self.assertNotIn(self.server['id'], alt_server_ids) @test.attr(type='gate') def test_change_password_for_alt_account_fails(self): # A change password request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.change_password, self.server['id'], 'newpass') @test.attr(type='gate') def test_reboot_server_for_alt_account_fails(self): # A reboot request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.reboot, self.server['id'], 'HARD') @test.attr(type='gate') def test_rebuild_server_for_alt_account_fails(self): # A rebuild request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.rebuild, self.server['id'], self.image_ref_alt) @test.attr(type='gate') def test_resize_server_for_alt_account_fails(self): # A resize request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.resize, self.server['id'], self.flavor_ref_alt) @test.attr(type='gate') def test_create_image_for_alt_account_fails(self): # A create image request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_images_client.create_image, self.server['id'], 'testImage') @test.attr(type='gate') def test_create_server_with_unauthorized_image(self): # Server creation with another user's image should fail self.assertRaises(exceptions.BadRequest, self.alt_client.create_server, 'test', self.image['id'], self.flavor_ref) @test.attr(type='gate') def test_create_server_fails_when_tenant_incorrect(self): # A create server request should fail if the tenant id does not match # the current user # Change the base URL to impersonate another user self.alt_client.auth_provider.set_alt_auth_data( request_part='url', auth_data=self.client.auth_provider.auth_data ) self.assertRaises(exceptions.BadRequest, self.alt_client.create_server, 'test', self.image['id'], self.flavor_ref) @test.attr(type='gate') def test_create_keypair_in_analt_user_tenant(self): # A create keypair request should fail if the tenant id does not match # the current user # POST keypair with other user tenant k_name = data_utils.rand_name('keypair-') try: # Change the base URL to impersonate another user self.alt_keypairs_client.auth_provider.set_alt_auth_data( request_part='url', auth_data=self.keypairs_client.auth_provider.auth_data ) resp = {} resp['status'] = None self.assertRaises(exceptions.BadRequest, self.alt_keypairs_client.create_keypair, k_name) finally: # Next request the base_url is back to normal if (resp['status'] is not None): resp, _ = self.alt_keypairs_client.delete_keypair(k_name) LOG.error("Create keypair request should not happen " "if the tenant id does not match the current user") @test.attr(type='gate') def test_get_keypair_of_alt_account_fails(self): # A GET request for another user's keypair should fail self.assertRaises(exceptions.NotFound, self.alt_keypairs_client.get_keypair, self.keypairname) @test.attr(type='gate') def test_delete_keypair_of_alt_account_fails(self): # A DELETE request for another user's keypair should fail self.assertRaises(exceptions.NotFound, self.alt_keypairs_client.delete_keypair, self.keypairname) @test.attr(type='gate') def test_get_image_for_alt_account_fails(self): # A GET request for an image on another user's account should fail self.assertRaises(exceptions.NotFound, self.alt_images_client.get_image, self.image['id']) @test.attr(type='gate') def test_delete_image_for_alt_account_fails(self): # A DELETE request for another user's image should fail self.assertRaises(exceptions.NotFound, self.alt_images_client.delete_image, self.image['id']) @test.attr(type='gate') def test_create_security_group_in_analt_user_tenant(self): # A create security group request should fail if the tenant id does not # match the current user # POST security group with other user tenant s_name = data_utils.rand_name('security-') s_description = data_utils.rand_name('security') try: # Change the base URL to impersonate another user self.alt_security_client.auth_provider.set_alt_auth_data( request_part='url', auth_data=self.security_client.auth_provider.auth_data ) resp = {} resp['status'] = None self.assertRaises(exceptions.BadRequest, self.alt_security_client.create_security_group, s_name, s_description) finally: # Next request the base_url is back to normal if resp['status'] is not None: self.alt_security_client.delete_security_group(resp['id']) LOG.error("Create Security Group request should not happen if" "the tenant id does not match the current user") @test.attr(type='gate') def test_get_security_group_of_alt_account_fails(self): # A GET request for another user's security group should fail self.assertRaises(exceptions.NotFound, self.alt_security_client.get_security_group, self.security_group['id']) @test.attr(type='gate') def test_delete_security_group_of_alt_account_fails(self): # A DELETE request for another user's security group should fail self.assertRaises(exceptions.NotFound, self.alt_security_client.delete_security_group, self.security_group['id']) @test.attr(type='gate') def test_create_security_group_rule_in_analt_user_tenant(self): # A create security group rule request should fail if the tenant id # does not match the current user # POST security group rule with other user tenant parent_group_id = self.security_group['id'] ip_protocol = 'icmp' from_port = -1 to_port = -1 try: # Change the base URL to impersonate another user self.alt_security_client.auth_provider.set_alt_auth_data( request_part='url', auth_data=self.security_client.auth_provider.auth_data ) resp = {} resp['status'] = None self.assertRaises(exceptions.BadRequest, self.alt_security_client. create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) finally: # Next request the base_url is back to normal if resp['status'] is not None: self.alt_security_client.delete_security_group_rule(resp['id']) LOG.error("Create security group rule request should not " "happen if the tenant id does not match the" " current user") @test.attr(type='gate') def test_delete_security_group_rule_of_alt_account_fails(self): # A DELETE request for another user's security group rule # should fail self.assertRaises(exceptions.NotFound, self.alt_security_client.delete_security_group_rule, self.rule['id']) @test.attr(type='gate') def test_set_metadata_of_alt_account_server_fails(self): # A set metadata for another user's server should fail req_metadata = {'meta1': 'data1', 'meta2': 'data2'} self.assertRaises(exceptions.NotFound, self.alt_client.set_server_metadata, self.server['id'], req_metadata) @test.attr(type='gate') def test_set_metadata_of_alt_account_image_fails(self): # A set metadata for another user's image should fail req_metadata = {'meta1': 'value1', 'meta2': 'value2'} self.assertRaises(exceptions.NotFound, self.alt_images_client.set_image_metadata, self.image['id'], req_metadata) @test.attr(type='gate') def test_get_metadata_of_alt_account_server_fails(self): # A get metadata for another user's server should fail req_metadata = {'meta1': 'data1'} self.client.set_server_metadata(self.server['id'], req_metadata) self.addCleanup(self.client.delete_server_metadata_item, self.server['id'], 'meta1') self.assertRaises(exceptions.NotFound, self.alt_client.get_server_metadata_item, self.server['id'], 'meta1') @test.attr(type='gate') def test_get_metadata_of_alt_account_image_fails(self): # A get metadata for another user's image should fail req_metadata = {'meta1': 'value1'} self.addCleanup(self.images_client.delete_image_metadata_item, self.image['id'], 'meta1') self.images_client.set_image_metadata(self.image['id'], req_metadata) self.assertRaises(exceptions.NotFound, self.alt_images_client.get_image_metadata_item, self.image['id'], 'meta1') @test.attr(type='gate') def test_delete_metadata_of_alt_account_server_fails(self): # A delete metadata for another user's server should fail req_metadata = {'meta1': 'data1'} self.addCleanup(self.client.delete_server_metadata_item, self.server['id'], 'meta1') self.client.set_server_metadata(self.server['id'], req_metadata) self.assertRaises(exceptions.NotFound, self.alt_client.delete_server_metadata_item, self.server['id'], 'meta1') @test.attr(type='gate') def test_delete_metadata_of_alt_account_image_fails(self): # A delete metadata for another user's image should fail req_metadata = {'meta1': 'data1'} self.addCleanup(self.images_client.delete_image_metadata_item, self.image['id'], 'meta1') self.images_client.set_image_metadata(self.image['id'], req_metadata) self.assertRaises(exceptions.NotFound, self.alt_images_client.delete_image_metadata_item, self.image['id'], 'meta1') @test.attr(type='gate') def test_get_console_output_of_alt_account_server_fails(self): # A Get Console Output for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.get_console_output, self.server['id'], 10) class AuthorizationTestXML(AuthorizationTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/0000775000175000017500000000000012332757136023076 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_fixed_ips.py0000664000175000017500000000414512332757070026462 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class FixedIPsTestJson(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(FixedIPsTestJson, cls).setUpClass() if CONF.service_available.neutron: msg = ("%s skipped as neutron is available" % cls.__name__) raise cls.skipException(msg) cls.client = cls.os_adm.fixed_ips_client resp, server = cls.create_test_server(wait_until='ACTIVE') resp, server = cls.servers_client.get_server(server['id']) for ip_set in server['addresses']: for ip in server['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': cls.ip = ip['addr'] break if cls.ip: break @test.attr(type='gate') def test_list_fixed_ip_details(self): resp, fixed_ip = self.client.get_fixed_ip_details(self.ip) self.assertEqual(fixed_ip['address'], self.ip) @test.attr(type='gate') def test_set_reserve(self): body = {"reserve": "None"} resp, body = self.client.reserve_fixed_ip(self.ip, body) self.assertEqual(resp.status, 202) @test.attr(type='gate') def test_set_unreserve(self): body = {"unreserve": "None"} resp, body = self.client.reserve_fixed_ip(self.ip, body) self.assertEqual(resp.status, 202) class FixedIPsTestXml(FixedIPsTestJson): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_flavors_extra_specs_negative.py0000664000175000017500000001236012332757070032444 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # Copyright 2013 IBM Corp. # # 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 tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Negative Tests Flavor Extra Spec API extension. SET, UNSET, UPDATE Flavor Extra specs require admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass() if not test.is_extension_enabled('FlavorExtraData', 'compute'): msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client flavor_name = data_utils.rand_name('test_flavor') ram = 512 vcpus = 1 disk = 10 ephemeral = 10 cls.new_flavor_id = data_utils.rand_int_id(start=1000) swap = 1024 rxtx = 1 # Create a flavor resp, cls.flavor = cls.client.create_flavor(flavor_name, ram, vcpus, disk, cls.new_flavor_id, ephemeral=ephemeral, swap=swap, rxtx=rxtx) @classmethod def tearDownClass(cls): resp, body = cls.client.delete_flavor(cls.flavor['id']) cls.client.wait_for_resource_deletion(cls.flavor['id']) super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass() @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_set_keys(self): # Test to SET flavor extra spec as a user without admin privileges. specs = {"key1": "value1", "key2": "value2"} self.assertRaises(exceptions.Unauthorized, self.flavors_client.set_flavor_extra_spec, self.flavor['id'], specs) @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_update_specific_key(self): # non admin user is not allowed to update flavor extra spec specs = {"key1": "value1", "key2": "value2"} resp, body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) self.assertEqual(resp.status, 200) self.assertEqual(body['key1'], 'value1') self.assertRaises(exceptions.Unauthorized, self.flavors_client. update_flavor_extra_spec, self.flavor['id'], 'key1', key1='value1_new') @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_unset_keys(self): specs = {"key1": "value1", "key2": "value2"} set_resp, set_body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) self.assertRaises(exceptions.Unauthorized, self.flavors_client.unset_flavor_extra_spec, self.flavor['id'], 'key1') @test.attr(type=['negative', 'gate']) def test_flavor_unset_nonexistent_key(self): nonexistent_key = data_utils.rand_name('flavor_key') self.assertRaises(exceptions.NotFound, self.client.unset_flavor_extra_spec, self.flavor['id'], nonexistent_key) @test.attr(type=['negative', 'gate']) def test_flavor_get_nonexistent_key(self): self.assertRaises(exceptions.NotFound, self.flavors_client.get_flavor_extra_spec_with_key, self.flavor['id'], "nonexistent_key") @test.attr(type=['negative', 'gate']) def test_flavor_update_mismatch_key(self): # the key will be updated should be match the key in the body self.assertRaises(exceptions.BadRequest, self.client.update_flavor_extra_spec, self.flavor['id'], "key2", key1="value") @test.attr(type=['negative', 'gate']) def test_flavor_update_more_key(self): # there should be just one item in the request body self.assertRaises(exceptions.BadRequest, self.client.update_flavor_extra_spec, self.flavor['id'], "key1", key1="value", key2="value") class FlavorsExtraSpecsNegativeTestXML(FlavorsExtraSpecsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_hosts_negative.py0000664000175000017500000001450412332757070027532 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # # 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 tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class HostsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests hosts API using admin privileges. """ @classmethod def setUpClass(cls): super(HostsAdminNegativeTestJSON, cls).setUpClass() cls.client = cls.os_adm.hosts_client cls.non_admin_client = cls.os.hosts_client def _get_host_name(self): resp, hosts = self.client.list_hosts() self.assertEqual(200, resp.status) self.assertTrue(len(hosts) >= 1) hostname = hosts[0]['host_name'] return hostname @test.attr(type=['negative', 'gate']) def test_list_hosts_with_non_admin_user(self): self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_hosts) @test.attr(type=['negative', 'gate']) def test_show_host_detail_with_nonexistent_hostname(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.show_host_detail, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_show_host_detail_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.show_host_detail, hostname) @test.attr(type=['negative', 'gate']) def test_update_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.update_host, hostname, status='enable', maintenance_mode='enable') @test.skip_because(bug="1261964", interface="xml") @test.attr(type=['negative', 'gate']) def test_update_host_with_extra_param(self): # only 'status' and 'maintenance_mode' are the valid params. hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname, status='enable', maintenance_mode='enable', param='XXX') @test.attr(type=['negative', 'gate']) def test_update_host_with_invalid_status(self): # 'status' can only be 'enable' or 'disable' hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname, status='invalid', maintenance_mode='enable') @test.attr(type=['negative', 'gate']) def test_update_host_with_invalid_maintenance_mode(self): # 'maintenance_mode' can only be 'enable' or 'disable' hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname, status='enable', maintenance_mode='invalid') @test.attr(type=['negative', 'gate']) def test_update_host_without_param(self): # 'status' or 'maintenance_mode' needed for host update hostname = self._get_host_name() self.assertRaises(exceptions.BadRequest, self.client.update_host, hostname) @test.attr(type=['negative', 'gate']) def test_update_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.update_host, nonexitent_hostname, status='enable', maintenance_mode='enable') @test.attr(type=['negative', 'gate']) def test_startup_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.startup_host, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_startup_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.startup_host, hostname) @test.attr(type=['negative', 'gate']) def test_shutdown_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.shutdown_host, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_shutdown_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.shutdown_host, hostname) @test.attr(type=['negative', 'gate']) def test_reboot_nonexistent_host(self): nonexitent_hostname = data_utils.rand_name('rand_hostname') self.assertRaises(exceptions.NotFound, self.client.reboot_host, nonexitent_hostname) @test.attr(type=['negative', 'gate']) def test_reboot_host_with_non_admin_user(self): hostname = self._get_host_name() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.reboot_host, hostname) class HostsAdminNegativeTestXML(HostsAdminNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_flavors.py0000664000175000017500000003265012332757070026166 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests Flavors API Create and Delete that require admin privileges """ @classmethod def setUpClass(cls): super(FlavorsAdminTestJSON, cls).setUpClass() if not test.is_extension_enabled('FlavorExtraData', 'compute'): msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client cls.user_client = cls.os.flavors_client cls.flavor_name_prefix = 'test_flavor_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 cls.ephemeral = 10 cls.swap = 1024 cls.rxtx = 2 def flavor_clean_up(self, flavor_id): resp, body = self.client.delete_flavor(flavor_id) self.assertEqual(resp.status, 202) self.client.wait_for_resource_deletion(flavor_id) def _create_flavor(self, flavor_id): # Create a flavor and ensure it is listed # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(flavor['ram'], self.ram) self.assertEqual(flavor['swap'], self.swap) self.assertEqual(flavor['rxtx_factor'], self.rxtx) self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'], self.ephemeral) self.assertEqual(flavor['os-flavor-access:is_public'], True) # Verify flavor is retrieved resp, flavor = self.client.get_flavor_details(flavor['id']) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) return flavor['id'] @test.attr(type='gate') def test_create_flavor_with_int_id(self): flavor_id = data_utils.rand_int_id(start=1000) new_flavor_id = self._create_flavor(flavor_id) self.assertEqual(new_flavor_id, str(flavor_id)) @test.attr(type='gate') def test_create_flavor_with_uuid_id(self): flavor_id = str(uuid.uuid4()) new_flavor_id = self._create_flavor(flavor_id) self.assertEqual(new_flavor_id, flavor_id) @test.attr(type='gate') def test_create_flavor_with_none_id(self): # If nova receives a request with None as flavor_id, # nova generates flavor_id of uuid. flavor_id = None new_flavor_id = self._create_flavor(flavor_id) self.assertEqual(new_flavor_id, str(uuid.UUID(new_flavor_id))) @test.attr(type='gate') def test_create_flavor_verify_entry_in_list_details(self): # Create a flavor and ensure it's details are listed # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.addCleanup(self.flavor_clean_up, flavor['id']) flag = False # Verify flavor is retrieved resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) @test.attr(type='gate') def test_create_list_flavor_without_extra_data(self): # Create a flavor and ensure it is listed # This operation requires the user to have 'admin' role def verify_flavor_response_extension(flavor): # check some extensions for the flavor create/show/detail response self.assertEqual(flavor['swap'], '') self.assertEqual(int(flavor['rxtx_factor']), 1) self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0) self.assertEqual(flavor['os-flavor-access:is_public'], True) flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['ram'], self.ram) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(int(flavor['id']), new_flavor_id) verify_flavor_response_extension(flavor) # Verify flavor is retrieved resp, flavor = self.client.get_flavor_details(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) verify_flavor_response_extension(flavor) # Check if flavor is present in list resp, flavors = self.user_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: verify_flavor_response_extension(flavor) flag = True self.assertTrue(flag) @test.attr(type='gate') def test_list_non_public_flavor(self): # Create a flavor with os-flavor-access:is_public false. # The flavor should not be present in list_details as the # tenant is not automatically added access list. # This operation requires the user to have 'admin' role flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) # Verify flavor is retrieved flag = False resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertFalse(flag) # Verify flavor is not retrieved with other user flag = False resp, flavors = self.user_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertFalse(flag) @test.attr(type='gate') def test_create_server_with_non_public_flavor(self): # Create a flavor with os-flavor-access:is_public false flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) # Verify flavor is not used by other user self.assertRaises(exceptions.BadRequest, self.os.servers_client.create_server, 'test', self.image_ref, flavor['id']) @test.attr(type='gate') def test_list_public_flavor_with_other_user(self): # Create a Flavor with public access. # Try to List/Get flavor with another user flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) # Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="True") self.addCleanup(self.flavor_clean_up, flavor['id']) flag = False self.new_client = self.flavors_client # Verify flavor is retrieved with new user resp, flavors = self.new_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) @test.attr(type='gate') def test_is_public_string_variations(self): flavor_id_not_public = data_utils.rand_int_id(start=1000) flavor_name_not_public = data_utils.rand_name(self.flavor_name_prefix) flavor_id_public = data_utils.rand_int_id(start=1000) flavor_name_public = data_utils.rand_name(self.flavor_name_prefix) # Create a non public flavor resp, flavor = self.client.create_flavor(flavor_name_not_public, self.ram, self.vcpus, self.disk, flavor_id_not_public, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) # Create a public flavor resp, flavor = self.client.create_flavor(flavor_name_public, self.ram, self.vcpus, self.disk, flavor_id_public, is_public="True") self.addCleanup(self.flavor_clean_up, flavor['id']) def _flavor_lookup(flavors, flavor_name): for flavor in flavors: if flavor['name'] == flavor_name: return flavor return None def _test_string_variations(variations, flavor_name): for string in variations: params = {'is_public': string} r, flavors = self.client.list_flavors_with_detail(params) self.assertEqual(r.status, 200) flavor = _flavor_lookup(flavors, flavor_name) self.assertIsNotNone(flavor) _test_string_variations(['f', 'false', 'no', '0'], flavor_name_not_public) _test_string_variations(['t', 'true', 'yes', '1'], flavor_name_public) @test.attr(type='gate') def test_create_flavor_using_string_ram(self): flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) ram = " 1024 " resp, flavor = self.client.create_flavor(flavor_name, ram, self.vcpus, self.disk, new_flavor_id) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(flavor['ram'], int(ram)) self.assertEqual(int(flavor['id']), new_flavor_id) class FlavorsAdminTestXML(FlavorsAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_services_negative.py0000664000175000017500000000510212332757070030207 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import exceptions from tempest import test class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests Services API. List and Enable/Disable require admin privileges. """ @classmethod def setUpClass(cls): super(ServicesAdminNegativeTestJSON, cls).setUpClass() cls.client = cls.os_adm.services_client cls.non_admin_client = cls.services_client @test.attr(type=['negative', 'gate']) def test_list_services_with_non_admin_user(self): self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_services) @test.attr(type=['negative', 'gate']) def test_get_service_by_invalid_params(self): # return all services if send the request with invalid parameter resp, services = self.client.list_services() params = {'xxx': 'nova-compute'} resp, services_xxx = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(len(services), len(services_xxx)) @test.attr(type=['negative', 'gate']) def test_get_service_by_invalid_service_and_valid_host(self): resp, services = self.client.list_services() host_name = services[0]['host'] params = {'host': host_name, 'binary': 'xxx'} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(services)) @test.attr(type=['negative', 'gate']) def test_get_service_with_valid_service_and_invalid_host(self): resp, services = self.client.list_services() binary_name = services[0]['binary'] params = {'host': 'xxx', 'binary': binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(0, len(services)) class ServicesAdminNegativeTestXML(ServicesAdminNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_instance_usage_audit_log.py0000664000175000017500000000453612332757070031533 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import urllib from tempest.api.compute import base from tempest import test class InstanceUsageAuditLogTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(InstanceUsageAuditLogTestJSON, cls).setUpClass() cls.adm_client = cls.os_adm.instance_usages_audit_log_client @test.attr(type='gate') def test_list_instance_usage_audit_logs(self): # list instance usage audit logs resp, body = self.adm_client.list_instance_usage_audit_logs() self.assertEqual(200, resp.status) expected_items = ['total_errors', 'total_instances', 'log', 'num_hosts_running', 'num_hosts_done', 'num_hosts', 'hosts_not_run', 'overall_status', 'period_ending', 'period_beginning', 'num_hosts_not_run'] for item in expected_items: self.assertIn(item, body) @test.attr(type='gate') def test_get_instance_usage_audit_log(self): # Get instance usage audit log before specified time now = datetime.datetime.now() resp, body = self.adm_client.get_instance_usage_audit_log( urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S"))) self.assertEqual(200, resp.status) expected_items = ['total_errors', 'total_instances', 'log', 'num_hosts_running', 'num_hosts_done', 'num_hosts', 'hosts_not_run', 'overall_status', 'period_ending', 'period_beginning', 'num_hosts_not_run'] for item in expected_items: self.assertIn(item, body) class InstanceUsageAuditLogTestXML(InstanceUsageAuditLogTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_hosts.py0000664000175000017500000000643212332757070025651 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest import test class HostsAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests hosts API using admin privileges. """ @classmethod def setUpClass(cls): super(HostsAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.hosts_client @test.attr(type='gate') def test_list_hosts(self): resp, hosts = self.client.list_hosts() self.assertEqual(200, resp.status) self.assertTrue(len(hosts) >= 2, str(hosts)) @test.attr(type='gate') def test_list_hosts_with_zone(self): self.useFixture(fixtures.LockFixture('availability_zone')) resp, hosts = self.client.list_hosts() host = hosts[0] zone_name = host['zone'] params = {'zone': zone_name} resp, hosts = self.client.list_hosts(params) self.assertEqual(200, resp.status) self.assertTrue(len(hosts) >= 1) self.assertIn(host, hosts) @test.attr(type='gate') def test_list_hosts_with_a_blank_zone(self): # If send the request with a blank zone, the request will be successful # and it will return all the hosts list params = {'zone': ''} resp, hosts = self.client.list_hosts(params) self.assertNotEqual(0, len(hosts)) self.assertEqual(200, resp.status) @test.attr(type='gate') def test_list_hosts_with_nonexistent_zone(self): # If send the request with a nonexistent zone, the request will be # successful and no hosts will be retured params = {'zone': 'xxx'} resp, hosts = self.client.list_hosts(params) self.assertEqual(0, len(hosts)) self.assertEqual(200, resp.status) @test.attr(type='gate') def test_show_host_detail(self): resp, hosts = self.client.list_hosts() self.assertEqual(200, resp.status) hosts = [host for host in hosts if host['service'] == 'compute'] self.assertTrue(len(hosts) >= 1) for host in hosts: hostname = host['host_name'] resp, resources = self.client.show_host_detail(hostname) self.assertEqual(200, resp.status) self.assertTrue(len(resources) >= 1) host_resource = resources[0]['resource'] self.assertIsNotNone(host_resource) self.assertIsNotNone(host_resource['cpu']) self.assertIsNotNone(host_resource['disk_gb']) self.assertIsNotNone(host_resource['memory_mb']) self.assertIsNotNone(host_resource['project']) self.assertEqual(hostname, host_resource['host']) class HostsAdminTestXML(HostsAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_flavors_negative_xml.py0000664000175000017500000002557712332757070030742 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute.admin import test_flavors_negative from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsAdminNegativeTestXML(test_flavors_negative. FlavorsAdminNegativeTestJSON): """ Tests Flavors API Create and Delete that require admin privileges """ _interface = 'xml' def flavor_clean_up(self, flavor_id): resp, body = self.client.delete_flavor(flavor_id) self.assertEqual(resp.status, 202) self.client.wait_for_resource_deletion(flavor_id) @test.attr(type=['negative', 'gate']) def test_invalid_is_public_string(self): # the 'is_public' parameter can be 'none/true/false' if it exists self.assertRaises(exceptions.BadRequest, self.client.list_flavors_with_detail, {'is_public': 'invalid'}) @test.attr(type=['negative', 'gate']) def test_create_flavor_using_invalid_ram(self): # the 'ram' attribute must be positive integer flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, flavor_name, -1, self.vcpus, self.disk, new_flavor_id) @test.attr(type=['negative', 'gate']) def test_create_flavor_using_invalid_vcpus(self): # the 'vcpu' attribute must be positive integer flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, flavor_name, self.ram, -1, self.disk, new_flavor_id) @test.attr(type=['negative', 'gate']) def test_create_flavor_with_name_length_less_than_1(self): # ensure name length >= 1 new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, '', self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_name_length_exceeds_255(self): # ensure name do not exceed 255 characters new_flavor_name = 'a' * 256 new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_name(self): # the regex of flavor_name is '^[\w\.\- ]*$' invalid_flavor_name = data_utils.rand_name('invalid-!@#$%-') new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, invalid_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_flavor_id(self): # the regex of flavor_id is '^[\w\.\- ]*$', and it cannot contain # leading and/or trailing whitespace new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) invalid_flavor_id = '!@#$%' self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, invalid_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_id_length_exceeds_255(self): # the length of flavor_id should not exceed 255 characters new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) invalid_flavor_id = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, invalid_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_root_gb(self): # root_gb attribute should be non-negative ( >= 0) integer new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, -1, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_ephemeral_gb(self): # ephemeral_gb attribute should be non-negative ( >= 0) integer new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=-1, swap=self.swap, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_swap(self): # swap attribute should be non-negative ( >= 0) integer new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=-1, rxtx=self.rxtx, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_rxtx_factor(self): # rxtx_factor attribute should be a positive float new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=-1.5, is_public='False') @test.attr(type=['negative', 'gate']) def test_create_flavor_with_invalid_is_public(self): # is_public attribute should be boolean new_flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.BadRequest, self.client.create_flavor, new_flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx, is_public='Invalid') @test.attr(type=['negative', 'gate']) def test_create_flavor_already_exists(self): flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.assertEqual(200, resp.status) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertRaises(exceptions.Conflict, self.client.create_flavor, flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) @test.attr(type=['negative', 'gate']) def test_delete_nonexistent_flavor(self): nonexistent_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.NotFound, self.client.delete_flavor, nonexistent_flavor_id) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_migrations.py0000664000175000017500000000403612332757070026663 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest import config from tempest import test CONF = config.CONF class MigrationsAdminTest(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(MigrationsAdminTest, cls).setUpClass() cls.client = cls.os_adm.migrations_client @test.attr(type='gate') def test_list_migrations(self): # Admin can get the migrations list resp, _ = self.client.list_migrations() self.assertEqual(200, resp.status) @testtools.skipUnless(CONF.compute_feature_enabled.resize, 'Resize not available.') @test.attr(type='gate') def test_list_migrations_in_flavor_resize_situation(self): # Admin can get the migrations list which contains the resized server resp, server = self.create_test_server(wait_until="ACTIVE") server_id = server['id'] resp, _ = self.servers_client.resize(server_id, self.flavor_ref_alt) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'VERIFY_RESIZE') self.servers_client.confirm_resize(server_id) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') resp, body = self.client.list_migrations() self.assertEqual(200, resp.status) instance_uuids = [x['instance_uuid'] for x in body] self.assertIn(server_id, instance_uuids) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_agents.py0000664000175000017500000001160612332757070025771 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest.openstack.common import log from tempest import test LOG = log.getLogger(__name__) class AgentsAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests Agents API """ @classmethod def setUpClass(cls): super(AgentsAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.agents_client def setUp(self): super(AgentsAdminTestJSON, self).setUp() params = self._param_helper( hypervisor='common', os='linux', architecture='x86_64', version='7.0', url='xxx://xxxx/xxx/xxx', md5hash='add6bb58e139be103324d04d82d8f545') resp, body = self.client.create_agent(**params) self.assertEqual(200, resp.status) self.agent_id = body['agent_id'] def tearDown(self): try: self.client.delete_agent(self.agent_id) except exceptions.NotFound: pass except Exception: LOG.exception('Exception raised deleting agent %s', self.agent_id) super(AgentsAdminTestJSON, self).tearDown() def _param_helper(self, **kwargs): rand_key = 'architecture' if rand_key in kwargs: # NOTE: The rand_name is for avoiding agent conflicts. # If you try to create an agent with the same hypervisor, # os and architecture as an exising agent, Nova will return # an HTTPConflict or HTTPServerError. kwargs[rand_key] = data_utils.rand_name(kwargs[rand_key]) return kwargs @test.attr(type='gate') def test_create_agent(self): # Create an agent. params = self._param_helper( hypervisor='kvm', os='win', architecture='x86', version='7.0', url='xxx://xxxx/xxx/xxx', md5hash='add6bb58e139be103324d04d82d8f545') resp, body = self.client.create_agent(**params) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_agent, body['agent_id']) for expected_item, value in params.items(): self.assertEqual(value, body[expected_item]) @test.attr(type='gate') def test_update_agent(self): # Update an agent. params = self._param_helper( version='8.0', url='xxx://xxxx/xxx/xxx2', md5hash='add6bb58e139be103324d04d82d8f547') resp, body = self.client.update_agent(self.agent_id, **params) self.assertEqual(200, resp.status) for expected_item, value in params.items(): self.assertEqual(value, body[expected_item]) @test.attr(type='gate') def test_delete_agent(self): # Delete an agent. resp, _ = self.client.delete_agent(self.agent_id) self.assertEqual(200, resp.status) # Verify the list doesn't contain the deleted agent. resp, agents = self.client.list_agents() self.assertEqual(200, resp.status) self.assertNotIn(self.agent_id, map(lambda x: x['agent_id'], agents)) @test.attr(type='gate') def test_list_agents(self): # List all agents. resp, agents = self.client.list_agents() self.assertEqual(200, resp.status) self.assertTrue(len(agents) > 0, 'Cannot get any agents.(%s)' % agents) self.assertIn(self.agent_id, map(lambda x: x['agent_id'], agents)) @test.attr(type='gate') def test_list_agents_with_filter(self): # List the agent builds by the filter. params = self._param_helper( hypervisor='xen', os='linux', architecture='x86', version='7.0', url='xxx://xxxx/xxx/xxx1', md5hash='add6bb58e139be103324d04d82d8f546') resp, agent_xen = self.client.create_agent(**params) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_agent, agent_xen['agent_id']) agent_id_xen = agent_xen['agent_id'] params_filter = {'hypervisor': agent_xen['hypervisor']} resp, agents = self.client.list_agents(params_filter) self.assertEqual(200, resp.status) self.assertTrue(len(agents) > 0, 'Cannot get any agents.(%s)' % agents) self.assertIn(agent_id_xen, map(lambda x: x['agent_id'], agents)) self.assertNotIn(self.agent_id, map(lambda x: x['agent_id'], agents)) tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_servers_negative.py0000664000175000017500000001335112332757070030062 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # # 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 uuid import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests Servers API using admin privileges """ @classmethod def setUpClass(cls): super(ServersAdminNegativeTestJSON, cls).setUpClass() cls.client = cls.os_adm.servers_client cls.non_adm_client = cls.servers_client cls.flavors_client = cls.os_adm.flavors_client cls.identity_client = cls._get_identity_admin_client() tenant = cls.identity_client.get_tenant_by_name( cls.client.tenant_name) cls.tenant_id = tenant['id'] cls.s1_name = data_utils.rand_name('server') resp, server = cls.create_test_server(name=cls.s1_name, wait_until='ACTIVE') cls.s1_id = server['id'] def _get_unused_flavor_id(self): flavor_id = data_utils.rand_int_id(start=1000) while True: try: resp, body = self.flavors_client.get_flavor_details(flavor_id) except exceptions.NotFound: break flavor_id = data_utils.rand_int_id(start=1000) return flavor_id @test.attr(type=['negative', 'gate']) def test_resize_server_using_overlimit_ram(self): flavor_name = data_utils.rand_name("flavor-") flavor_id = self._get_unused_flavor_id() resp, quota_set = self.quotas_client.get_default_quota_set( self.tenant_id) ram = int(quota_set['ram']) + 1 vcpus = 8 disk = 10 resp, flavor_ref = self.flavors_client.create_flavor(flavor_name, ram, vcpus, disk, flavor_id) self.addCleanup(self.flavors_client.delete_flavor, flavor_id) self.assertRaises(exceptions.OverLimit, self.client.resize, self.servers[0]['id'], flavor_ref['id']) @test.attr(type=['negative', 'gate']) def test_resize_server_using_overlimit_vcpus(self): flavor_name = data_utils.rand_name("flavor-") flavor_id = self._get_unused_flavor_id() ram = 512 resp, quota_set = self.quotas_client.get_default_quota_set( self.tenant_id) vcpus = int(quota_set['cores']) + 1 disk = 10 resp, flavor_ref = self.flavors_client.create_flavor(flavor_name, ram, vcpus, disk, flavor_id) self.addCleanup(self.flavors_client.delete_flavor, flavor_id) self.assertRaises(exceptions.OverLimit, self.client.resize, self.servers[0]['id'], flavor_ref['id']) @test.attr(type=['negative', 'gate']) def test_reset_state_server_invalid_state(self): self.assertRaises(exceptions.BadRequest, self.client.reset_state, self.s1_id, state='invalid') @test.attr(type=['negative', 'gate']) def test_reset_state_server_invalid_type(self): self.assertRaises(exceptions.BadRequest, self.client.reset_state, self.s1_id, state=1) @test.attr(type=['negative', 'gate']) def test_reset_state_server_nonexistent_server(self): self.assertRaises(exceptions.NotFound, self.client.reset_state, '999') @test.attr(type=['negative', 'gate']) def test_get_server_diagnostics_by_non_admin(self): # Non-admin user can not view server diagnostics according to policy self.assertRaises(exceptions.Unauthorized, self.non_adm_client.get_server_diagnostics, self.s1_id) @test.attr(type=['negative', 'gate']) def test_migrate_non_existent_server(self): # migrate a non existent server self.assertRaises(exceptions.NotFound, self.client.migrate_server, str(uuid.uuid4())) @testtools.skipUnless(CONF.compute_feature_enabled.suspend, 'Suspend is not available.') @test.attr(type=['negative', 'gate']) def test_migrate_server_invalid_state(self): # create server. resp, server = self.create_test_server(wait_until='ACTIVE') self.assertEqual(202, resp.status) server_id = server['id'] # suspend the server. resp, _ = self.client.suspend_server(server_id) self.assertEqual(202, resp.status) self.client.wait_for_server_status(server_id, 'SUSPENDED') # migrate an suspended server should fail self.assertRaises(exceptions.Conflict, self.client.migrate_server, server_id) class ServersAdminNegativeTestXML(ServersAdminNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_availability_zone_negative.py0000664000175000017500000000262012332757070032073 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import exceptions from tempest import test class AZAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests Availability Zone API List """ @classmethod def setUpClass(cls): super(AZAdminNegativeTestJSON, cls).setUpClass() cls.non_adm_client = cls.availability_zone_client @test.attr(type=['negative', 'gate']) def test_get_availability_zone_list_detail_with_non_admin_user(self): # List of availability zones and available services with # non-administrator user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_availability_zone_list_detail) class AZAdminNegativeTestXML(AZAdminNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_quotas_negative.py0000664000175000017500000001516212332757070027707 0ustar chuckchuck00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class QuotasAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): force_tenant_isolation = True @classmethod def setUpClass(cls): super(QuotasAdminNegativeTestJSON, cls).setUpClass() cls.client = cls.os.quotas_client cls.adm_client = cls.os_adm.quotas_client cls.sg_client = cls.security_groups_client # NOTE(afazekas): these test cases should always create and use a new # tenant most of them should be skipped if we can't do that cls.demo_tenant_id = cls.isolated_creds.get_primary_creds().tenant_id @test.attr(type=['negative', 'gate']) def test_update_quota_normal_user(self): self.assertRaises(exceptions.Unauthorized, self.client.update_quota_set, self.demo_tenant_id, ram=0) # TODO(afazekas): Add dedicated tenant to the skiped quota tests # it can be moved into the setUpClass as well @test.attr(type=['negative', 'gate']) def test_create_server_when_cpu_quota_is_full(self): # Disallow server creation when tenant's vcpu quota is full resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_vcpu_quota = quota_set['cores'] vcpu_quota = 0 # Set the quota to zero to conserve resources resp, quota_set = self.adm_client.update_quota_set(self.demo_tenant_id, force=True, cores=vcpu_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, cores=default_vcpu_quota) self.assertRaises(exceptions.OverLimit, self.create_test_server) @test.attr(type=['negative', 'gate']) def test_create_server_when_memory_quota_is_full(self): # Disallow server creation when tenant's memory quota is full resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_mem_quota = quota_set['ram'] mem_quota = 0 # Set the quota to zero to conserve resources self.adm_client.update_quota_set(self.demo_tenant_id, force=True, ram=mem_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, ram=default_mem_quota) self.assertRaises(exceptions.OverLimit, self.create_test_server) @test.attr(type=['negative', 'gate']) def test_create_server_when_instances_quota_is_full(self): # Once instances quota limit is reached, disallow server creation resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_instances_quota = quota_set['instances'] instances_quota = 0 # Set quota to zero to disallow server creation self.adm_client.update_quota_set(self.demo_tenant_id, force=True, instances=instances_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, instances=default_instances_quota) self.assertRaises(exceptions.OverLimit, self.create_test_server) @test.skip_because(bug="1186354", condition=CONF.service_available.neutron) @test.attr(type='gate') def test_security_groups_exceed_limit(self): # Negative test: Creation Security Groups over limit should FAIL resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_sg_quota = quota_set['security_groups'] sg_quota = 0 # Set the quota to zero to conserve resources resp, quota_set =\ self.adm_client.update_quota_set(self.demo_tenant_id, force=True, security_groups=sg_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, security_groups=default_sg_quota) # Check we cannot create anymore self.assertRaises(exceptions.OverLimit, self.sg_client.create_security_group, "sg-overlimit", "sg-desc") @test.skip_because(bug="1186354", condition=CONF.service_available.neutron) @test.attr(type=['negative', 'gate']) def test_security_groups_rules_exceed_limit(self): # Negative test: Creation of Security Group Rules should FAIL # when we reach limit maxSecurityGroupRules resp, quota_set = self.adm_client.get_quota_set(self.demo_tenant_id) default_sg_rules_quota = quota_set['security_group_rules'] sg_rules_quota = 0 # Set the quota to zero to conserve resources resp, quota_set =\ self.adm_client.update_quota_set( self.demo_tenant_id, force=True, security_group_rules=sg_rules_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, security_group_rules=default_sg_rules_quota) s_name = data_utils.rand_name('securitygroup-') s_description = data_utils.rand_name('description-') resp, securitygroup =\ self.sg_client.create_security_group(s_name, s_description) self.addCleanup(self.sg_client.delete_security_group, securitygroup['id']) secgroup_id = securitygroup['id'] ip_protocol = 'tcp' # Check we cannot create SG rule anymore self.assertRaises(exceptions.OverLimit, self.sg_client.create_security_group_rule, secgroup_id, ip_protocol, 1025, 1025) class QuotasAdminNegativeTestXML(QuotasAdminNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_simple_tenant_usage_negative.py0000664000175000017500000000537512332757070032426 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime from tempest.api.compute import base from tempest import exceptions from tempest import test class TenantUsagesNegativeTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(TenantUsagesNegativeTestJSON, cls).setUpClass() cls.adm_client = cls.os_adm.tenant_usages_client cls.client = cls.os.tenant_usages_client cls.identity_client = cls._get_identity_admin_client() now = datetime.datetime.now() cls.start = cls._parse_strtime(now - datetime.timedelta(days=1)) cls.end = cls._parse_strtime(now + datetime.timedelta(days=1)) @classmethod def _parse_strtime(cls, at): # Returns formatted datetime return at.strftime('%Y-%m-%dT%H:%M:%S.%f') @test.attr(type=['negative', 'gate']) def test_get_usage_tenant_with_empty_tenant_id(self): # Get usage for a specific tenant empty params = {'start': self.start, 'end': self.end} self.assertRaises(exceptions.NotFound, self.adm_client.get_tenant_usage, '', params) @test.attr(type=['negative', 'gate']) def test_get_usage_tenant_with_invalid_date(self): # Get usage for tenant with invalid date params = {'start': self.end, 'end': self.start} resp, tenants = self.identity_client.list_tenants() tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == self.client.tenant_name][0] self.assertRaises(exceptions.BadRequest, self.adm_client.get_tenant_usage, tenant_id, params) @test.attr(type=['negative', 'gate']) def test_list_usage_all_tenants_with_non_admin_user(self): # Get usage for all tenants with non admin user params = {'start': self.start, 'end': self.end, 'detailed': int(bool(True))} self.assertRaises(exceptions.Unauthorized, self.client.list_tenant_usages, params) class TenantUsagesNegativeTestXML(TenantUsagesNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_hypervisor.py0000664000175000017500000001006412332757070026717 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class HypervisorAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests Hypervisors API that require admin privileges """ @classmethod def setUpClass(cls): super(HypervisorAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.hypervisor_client def _list_hypervisors(self): # List of hypervisors resp, hypers = self.client.get_hypervisor_list() self.assertEqual(200, resp.status) return hypers def assertHypervisors(self, hypers): self.assertTrue(len(hypers) > 0, "No hypervisors found: %s" % hypers) @test.attr(type='gate') def test_get_hypervisor_list(self): # List of hypervisor and available hypervisors hostname hypers = self._list_hypervisors() self.assertHypervisors(hypers) @test.attr(type='gate') def test_get_hypervisor_list_details(self): # Display the details of the all hypervisor resp, hypers = self.client.get_hypervisor_list_details() self.assertEqual(200, resp.status) self.assertHypervisors(hypers) @test.attr(type='gate') def test_get_hypervisor_show_details(self): # Display the details of the specified hypervisor hypers = self._list_hypervisors() self.assertHypervisors(hypers) resp, details = (self.client. get_hypervisor_show_details(hypers[0]['id'])) self.assertEqual(200, resp.status) self.assertTrue(len(details) > 0) self.assertEqual(details['hypervisor_hostname'], hypers[0]['hypervisor_hostname']) @test.attr(type='gate') def test_get_hypervisor_show_servers(self): # Show instances about the specific hypervisors hypers = self._list_hypervisors() self.assertHypervisors(hypers) hostname = hypers[0]['hypervisor_hostname'] resp, hypervisors = self.client.get_hypervisor_servers(hostname) self.assertEqual(200, resp.status) self.assertTrue(len(hypervisors) > 0) @test.attr(type='gate') def test_get_hypervisor_stats(self): # Verify the stats of the all hypervisor resp, stats = self.client.get_hypervisor_stats() self.assertEqual(200, resp.status) self.assertTrue(len(stats) > 0) @test.attr(type='gate') def test_get_hypervisor_uptime(self): # Verify that GET shows the specified hypervisor uptime hypers = self._list_hypervisors() has_valid_uptime = False for hyper in hypers: # because hypervisors might be disabled, this loops looking # for any good hit. try: resp, uptime = self.client.get_hypervisor_uptime(hyper['id']) if (resp.status == 200) and (len(uptime) > 0): has_valid_uptime = True break except Exception: pass self.assertTrue( has_valid_uptime, "None of the hypervisors had a valid uptime: %s" % hypers) @test.attr(type='gate') def test_search_hypervisor(self): hypers = self._list_hypervisors() self.assertHypervisors(hypers) resp, hypers = self.client.search_hypervisor( hypers[0]['hypervisor_hostname']) self.assertEqual(200, resp.status) self.assertHypervisors(hypers) class HypervisorAdminTestXML(HypervisorAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_flavors_access_negative.py0000664000175000017500000001562212332757070031371 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests Flavor Access API extension. Add and remove Flavor Access require admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsAccessNegativeTestJSON, cls).setUpClass() if not test.is_extension_enabled('FlavorExtraData', 'compute'): msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client admin_client = cls._get_identity_admin_client() cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client. tenant_name) cls.tenant_id = cls.tenant['id'] cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm. flavors_client. tenant_name) cls.adm_tenant_id = cls.adm_tenant['id'] cls.flavor_name_prefix = 'test_flavor_access_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 @test.attr(type=['negative', 'gate']) def test_flavor_access_list_with_public_flavor(self): # Test to list flavor access with exceptions by querying public flavor flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='True') self.addCleanup(self.client.delete_flavor, new_flavor['id']) self.assertEqual(resp.status, 200) self.assertRaises(exceptions.NotFound, self.client.list_flavor_access, new_flavor_id) @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_add(self): # Test to add flavor access as a user without admin privileges. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) self.assertRaises(exceptions.Unauthorized, self.flavors_client.add_flavor_access, new_flavor['id'], self.tenant_id) @test.attr(type=['negative', 'gate']) def test_flavor_non_admin_remove(self): # Test to remove flavor access as a user without admin privileges. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # Add flavor access to a tenant. self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.addCleanup(self.client.remove_flavor_access, new_flavor['id'], self.tenant_id) self.assertRaises(exceptions.Unauthorized, self.flavors_client.remove_flavor_access, new_flavor['id'], self.tenant_id) @test.attr(type=['negative', 'gate']) def test_add_flavor_access_duplicate(self): # Create a new flavor. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # Add flavor access to a tenant. self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.addCleanup(self.client.remove_flavor_access, new_flavor['id'], self.tenant_id) # An exception should be raised when adding flavor access to the same # tenant self.assertRaises(exceptions.Conflict, self.client.add_flavor_access, new_flavor['id'], self.tenant_id) @test.attr(type=['negative', 'gate']) def test_remove_flavor_access_not_found(self): # Create a new flavor. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # An exception should be raised when flavor access is not found self.assertRaises(exceptions.NotFound, self.client.remove_flavor_access, new_flavor['id'], str(uuid.uuid4())) class FlavorsAdminNegativeTestXML(FlavorsAccessNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_hypervisor_negative.py0000664000175000017500000001133112332757070030577 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class HypervisorAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests Hypervisors API that require admin privileges """ @classmethod def setUpClass(cls): super(HypervisorAdminNegativeTestJSON, cls).setUpClass() cls.client = cls.os_adm.hypervisor_client cls.non_adm_client = cls.hypervisor_client def _list_hypervisors(self): # List of hypervisors resp, hypers = self.client.get_hypervisor_list() self.assertEqual(200, resp.status) return hypers @test.attr(type=['negative', 'gate']) def test_show_nonexistent_hypervisor(self): nonexistent_hypervisor_id = str(uuid.uuid4()) self.assertRaises( exceptions.NotFound, self.client.get_hypervisor_show_details, nonexistent_hypervisor_id) @test.attr(type=['negative', 'gate']) def test_show_hypervisor_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_show_details, hypers[0]['id']) @test.attr(type=['negative', 'gate']) def test_show_servers_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_servers, hypers[0]['id']) @test.attr(type=['negative', 'gate']) def test_show_servers_with_nonexistent_hypervisor(self): nonexistent_hypervisor_id = str(uuid.uuid4()) self.assertRaises( exceptions.NotFound, self.client.get_hypervisor_servers, nonexistent_hypervisor_id) @test.attr(type=['negative', 'gate']) def test_get_hypervisor_stats_with_non_admin_user(self): self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_stats) @test.attr(type=['negative', 'gate']) def test_get_nonexistent_hypervisor_uptime(self): nonexistent_hypervisor_id = str(uuid.uuid4()) self.assertRaises( exceptions.NotFound, self.client.get_hypervisor_uptime, nonexistent_hypervisor_id) @test.attr(type=['negative', 'gate']) def test_get_hypervisor_uptime_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_uptime, hypers[0]['id']) @test.attr(type=['negative', 'gate']) def test_get_hypervisor_list_with_non_admin_user(self): # List of hypervisor and available services with non admin user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_list) @test.attr(type=['negative', 'gate']) def test_get_hypervisor_list_details_with_non_admin_user(self): # List of hypervisor details and available services with non admin user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_list_details) @test.attr(type=['negative', 'gate']) def test_search_nonexistent_hypervisor(self): nonexistent_hypervisor_name = data_utils.rand_name('test_hypervisor') self.assertRaises( exceptions.NotFound, self.client.search_hypervisor, nonexistent_hypervisor_name) @test.attr(type=['negative', 'gate']) def test_search_hypervisor_with_non_admin_user(self): hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) self.assertRaises( exceptions.Unauthorized, self.non_adm_client.search_hypervisor, hypers[0]['hypervisor_hostname']) class HypervisorAdminNegativeTestXML(HypervisorAdminNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/__init__.py0000664000175000017500000000000012332757070025172 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_servers.py0000664000175000017500000002001412332757070026172 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # # 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 tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class ServersAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests Servers API using admin privileges """ _host_key = 'OS-EXT-SRV-ATTR:host' @classmethod @test.safe_setup def setUpClass(cls): super(ServersAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.servers_client cls.non_admin_client = cls.servers_client cls.flavors_client = cls.os_adm.flavors_client cls.s1_name = data_utils.rand_name('server') resp, server = cls.create_test_server(name=cls.s1_name, wait_until='ACTIVE') cls.s1_id = server['id'] cls.s2_name = data_utils.rand_name('server') resp, server = cls.create_test_server(name=cls.s2_name, wait_until='ACTIVE') cls.s2_id = server['id'] @test.attr(type='gate') def test_list_servers_by_admin(self): # Listing servers by admin user returns empty list by default resp, body = self.client.list_servers_with_detail() servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @test.attr(type='gate') def test_list_servers_filter_by_error_status(self): # Filter the list of servers by server error status params = {'status': 'error'} resp, server = self.client.reset_state(self.s1_id, state='error') resp, body = self.non_admin_client.list_servers(params) # Reset server's state to 'active' resp, server = self.client.reset_state(self.s1_id, state='active') # Verify server's state resp, server = self.client.get_server(self.s1_id) self.assertEqual(server['status'], 'ACTIVE') servers = body['servers'] # Verify error server in list result self.assertIn(self.s1_id, map(lambda x: x['id'], servers)) self.assertNotIn(self.s2_id, map(lambda x: x['id'], servers)) @test.attr(type='gate') def test_list_servers_by_admin_with_all_tenants(self): # Listing servers by admin user with all tenants parameter # Here should be listed all servers params = {'all_tenants': ''} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] servers_name = map(lambda x: x['name'], servers) self.assertIn(self.s1_name, servers_name) self.assertIn(self.s2_name, servers_name) @test.attr(type='gate') def test_list_servers_filter_by_exist_host(self): # Filter the list of servers by existent host name = data_utils.rand_name('server') flavor = self.flavor_ref image_id = self.image_ref resp, test_server = self.client.create_server( name, image_id, flavor) self.assertEqual('202', resp['status']) self.addCleanup(self.client.delete_server, test_server['id']) self.client.wait_for_server_status(test_server['id'], 'ACTIVE') resp, server = self.client.get_server(test_server['id']) self.assertEqual(server['status'], 'ACTIVE') hostname = server[self._host_key] params = {'host': hostname} resp, body = self.client.list_servers(params) self.assertEqual('200', resp['status']) servers = body['servers'] nonexistent_params = {'host': 'nonexistent_host'} resp, nonexistent_body = self.client.list_servers( nonexistent_params) self.assertEqual('200', resp['status']) nonexistent_servers = nonexistent_body['servers'] self.assertIn(test_server['id'], map(lambda x: x['id'], servers)) self.assertNotIn(test_server['id'], map(lambda x: x['id'], nonexistent_servers)) @test.attr(type='gate') def test_reset_state_server(self): # Reset server's state to 'error' resp, server = self.client.reset_state(self.s1_id) self.assertEqual(202, resp.status) # Verify server's state resp, server = self.client.get_server(self.s1_id) self.assertEqual(server['status'], 'ERROR') # Reset server's state to 'active' resp, server = self.client.reset_state(self.s1_id, state='active') self.assertEqual(202, resp.status) # Verify server's state resp, server = self.client.get_server(self.s1_id) self.assertEqual(server['status'], 'ACTIVE') @test.attr(type='gate') @test.skip_because(bug="1240043") def test_get_server_diagnostics_by_admin(self): # Retrieve server diagnostics by admin user resp, diagnostic = self.client.get_server_diagnostics(self.s1_id) self.assertEqual(200, resp.status) basic_attrs = ['rx_packets', 'rx_errors', 'rx_drop', 'tx_packets', 'tx_errors', 'tx_drop', 'read_req', 'write_req', 'cpu', 'memory'] for key in basic_attrs: self.assertIn(key, str(diagnostic.keys())) @test.attr(type='gate') def test_rebuild_server_in_error_state(self): # The server in error state should be rebuilt using the provided # image and changed to ACTIVE state # resetting vm state require admin privilege resp, server = self.client.reset_state(self.s1_id, state='error') self.assertEqual(202, resp.status) resp, rebuilt_server = self.non_admin_client.rebuild( self.s1_id, self.image_ref_alt) self.addCleanup(self.non_admin_client.wait_for_server_status, self.s1_id, 'ACTIVE') self.addCleanup(self.non_admin_client.rebuild, self.s1_id, self.image_ref) # Verify the properties in the initial response are correct self.assertEqual(self.s1_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertEqual(self.image_ref_alt, rebuilt_image_id) self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id']) self.non_admin_client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE', raise_on_error=False) # Verify the server properties after rebuilding resp, server = self.non_admin_client.get_server(rebuilt_server['id']) rebuilt_image_id = server['image']['id'] self.assertEqual(self.image_ref_alt, rebuilt_image_id) @test.attr(type='gate') def test_reset_network_inject_network_info(self): # Reset Network of a Server resp, server = self.create_test_server(wait_until='ACTIVE') resp, server_body = self.client.reset_network(server['id']) self.assertEqual(202, resp.status) # Inject the Network Info into Server resp, server_body = self.client.inject_network_info(server['id']) self.assertEqual(202, resp.status) @test.attr(type='gate') def test_create_server_with_scheduling_hint(self): # Create a server with scheduler hints. hints = { 'same_host': self.s1_id } resp, server = self.create_test_server(sched_hints=hints, wait_until='ACTIVE') self.assertEqual('202', resp['status']) class ServersAdminTestXML(ServersAdminTestJSON): _host_key = ( '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host') _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_aggregates_negative.py0000664000175000017500000002101512332757070030476 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class AggregatesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests Aggregates API that require admin privileges """ @classmethod def setUpClass(cls): super(AggregatesAdminNegativeTestJSON, cls).setUpClass() cls.client = cls.os_adm.aggregates_client cls.user_client = cls.aggregates_client cls.aggregate_name_prefix = 'test_aggregate_' cls.az_name_prefix = 'test_az_' resp, hosts_all = cls.os_adm.hosts_client.list_hosts() hosts = map(lambda x: x['host_name'], filter(lambda y: y['service'] == 'compute', hosts_all)) cls.host = hosts[0] @test.attr(type=['negative', 'gate']) def test_aggregate_create_as_user(self): # Regular user is not allowed to create an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) self.assertRaises(exceptions.Unauthorized, self.user_client.create_aggregate, name=aggregate_name) @test.attr(type=['negative', 'gate']) def test_aggregate_create_aggregate_name_length_less_than_1(self): # the length of aggregate name should >= 1 and <=255 self.assertRaises(exceptions.BadRequest, self.client.create_aggregate, name='') @test.attr(type=['negative', 'gate']) def test_aggregate_create_aggregate_name_length_exceeds_255(self): # the length of aggregate name should >= 1 and <=255 aggregate_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.client.create_aggregate, name=aggregate_name) @test.attr(type=['negative', 'gate']) def test_aggregate_create_with_existent_aggregate_name(self): # creating an aggregate with existent aggregate name is forbidden aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Conflict, self.client.create_aggregate, name=aggregate_name) @test.attr(type=['negative', 'gate']) def test_aggregate_delete_as_user(self): # Regular user is not allowed to delete an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.delete_aggregate, aggregate['id']) @test.attr(type=['negative', 'gate']) def test_aggregate_list_as_user(self): # Regular user is not allowed to list aggregates. self.assertRaises(exceptions.Unauthorized, self.user_client.list_aggregates) @test.attr(type=['negative', 'gate']) def test_aggregate_get_details_as_user(self): # Regular user is not allowed to get aggregate details. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.get_aggregate, aggregate['id']) @test.attr(type=['negative', 'gate']) def test_aggregate_delete_with_invalid_id(self): # Delete an aggregate with invalid id should raise exceptions. self.assertRaises(exceptions.NotFound, self.client.delete_aggregate, -1) @test.attr(type=['negative', 'gate']) def test_aggregate_get_details_with_invalid_id(self): # Get aggregate details with invalid id should raise exceptions. self.assertRaises(exceptions.NotFound, self.client.get_aggregate, -1) @test.attr(type=['negative', 'gate']) def test_aggregate_add_non_exist_host(self): # Adding a non-exist host to an aggregate should raise exceptions. resp, hosts_all = self.os_adm.hosts_client.list_hosts() hosts = map(lambda x: x['host_name'], hosts_all) while True: non_exist_host = data_utils.rand_name('nonexist_host_') if non_exist_host not in hosts: break aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.NotFound, self.client.add_host, aggregate['id'], non_exist_host) @test.attr(type=['negative', 'gate']) def test_aggregate_add_host_as_user(self): # Regular user is not allowed to add a host to an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.add_host, aggregate['id'], self.host) @test.attr(type=['negative', 'gate']) def test_aggregate_add_existent_host(self): self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.add_host(aggregate['id'], self.host) self.assertEqual(200, resp.status) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) self.assertRaises(exceptions.Conflict, self.client.add_host, aggregate['id'], self.host) @test.attr(type=['negative', 'gate']) def test_aggregate_remove_host_as_user(self): # Regular user is not allowed to remove a host from an aggregate. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.add_host(aggregate['id'], self.host) self.assertEqual(200, resp.status) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) self.assertRaises(exceptions.Unauthorized, self.user_client.remove_host, aggregate['id'], self.host) @test.attr(type=['negative', 'gate']) def test_aggregate_remove_nonexistent_host(self): non_exist_host = data_utils.rand_name('nonexist_host_') aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.NotFound, self.client.remove_host, aggregate['id'], non_exist_host) class AggregatesAdminNegativeTestXML(AggregatesAdminNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_aggregates.py0000664000175000017500000002455012332757070026623 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common import tempest_fixtures as fixtures from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests Aggregates API that require admin privileges """ _host_key = 'OS-EXT-SRV-ATTR:host' @classmethod def setUpClass(cls): super(AggregatesAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.aggregates_client cls.aggregate_name_prefix = 'test_aggregate_' cls.az_name_prefix = 'test_az_' resp, hosts_all = cls.os_adm.hosts_client.list_hosts() hosts = map(lambda x: x['host_name'], filter(lambda y: y['service'] == 'compute', hosts_all)) cls.host = hosts[0] def _try_delete_aggregate(self, aggregate_id): # delete aggregate, if it exists try: self.client.delete_aggregate(aggregate_id) # if aggregate not found, it depict it was deleted in the test except exceptions.NotFound: pass @test.attr(type='gate') def test_aggregate_create_delete(self): # Create and delete an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self._try_delete_aggregate, aggregate['id']) self.assertEqual(200, resp.status) self.assertEqual(aggregate_name, aggregate['name']) self.assertIsNone(aggregate['availability_zone']) resp, _ = self.client.delete_aggregate(aggregate['id']) self.assertEqual(200, resp.status) self.client.wait_for_resource_deletion(aggregate['id']) @test.attr(type='gate') def test_aggregate_create_delete_with_az(self): # Create and delete an aggregate. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) az_name = data_utils.rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate( name=aggregate_name, availability_zone=az_name) self.addCleanup(self._try_delete_aggregate, aggregate['id']) self.assertEqual(200, resp.status) self.assertEqual(aggregate_name, aggregate['name']) self.assertEqual(az_name, aggregate['availability_zone']) resp, _ = self.client.delete_aggregate(aggregate['id']) self.assertEqual(200, resp.status) self.client.wait_for_resource_deletion(aggregate['id']) @test.attr(type='gate') def test_aggregate_create_verify_entry_in_list(self): # Create an aggregate and ensure it is listed. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, aggregates = self.client.list_aggregates() self.assertEqual(200, resp.status) self.assertIn((aggregate['id'], aggregate['availability_zone']), map(lambda x: (x['id'], x['availability_zone']), aggregates)) @test.attr(type='gate') def test_aggregate_create_update_metadata_get_details(self): # Create an aggregate and ensure its details are returned. aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.get_aggregate(aggregate['id']) self.assertEqual(200, resp.status) self.assertEqual(aggregate['name'], body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertEqual({}, body["metadata"]) # set the metadata of the aggregate meta = {"key": "value"} resp, body = self.client.set_metadata(aggregate['id'], meta) self.assertEqual(200, resp.status) self.assertEqual(meta, body["metadata"]) # verify the metadata has been set resp, body = self.client.get_aggregate(aggregate['id']) self.assertEqual(200, resp.status) self.assertEqual(meta, body["metadata"]) @test.attr(type='gate') def test_aggregate_create_update_with_az(self): # Update an aggregate and ensure properties are updated correctly aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) az_name = data_utils.rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate( name=aggregate_name, availability_zone=az_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertEqual(200, resp.status) self.assertEqual(aggregate_name, aggregate['name']) self.assertEqual(az_name, aggregate['availability_zone']) self.assertIsNotNone(aggregate['id']) aggregate_id = aggregate['id'] new_aggregate_name = aggregate_name + '_new' new_az_name = az_name + '_new' resp, resp_aggregate = self.client.update_aggregate(aggregate_id, new_aggregate_name, new_az_name) self.assertEqual(200, resp.status) self.assertEqual(new_aggregate_name, resp_aggregate['name']) self.assertEqual(new_az_name, resp_aggregate['availability_zone']) resp, aggregates = self.client.list_aggregates() self.assertEqual(200, resp.status) self.assertIn((aggregate_id, new_aggregate_name, new_az_name), map(lambda x: (x['id'], x['name'], x['availability_zone']), aggregates)) @test.attr(type='gate') def test_aggregate_add_remove_host(self): # Add an host to the given aggregate and remove. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.add_host(aggregate['id'], self.host) self.assertEqual(200, resp.status) self.assertEqual(aggregate_name, body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertIn(self.host, body['hosts']) resp, body = self.client.remove_host(aggregate['id'], self.host) self.assertEqual(200, resp.status) self.assertEqual(aggregate_name, body['name']) self.assertEqual(aggregate['availability_zone'], body['availability_zone']) self.assertNotIn(self.host, body['hosts']) @test.attr(type='gate') def test_aggregate_add_host_list(self): # Add an host to the given aggregate and list. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) resp, aggregates = self.client.list_aggregates() aggs = filter(lambda x: x['id'] == aggregate['id'], aggregates) self.assertEqual(1, len(aggs)) agg = aggs[0] self.assertEqual(aggregate_name, agg['name']) self.assertIsNone(agg['availability_zone']) self.assertIn(self.host, agg['hosts']) @test.attr(type='gate') def test_aggregate_add_host_get_details(self): # Add an host to the given aggregate and get details. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(name=aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) resp, body = self.client.get_aggregate(aggregate['id']) self.assertEqual(aggregate_name, body['name']) self.assertIsNone(body['availability_zone']) self.assertIn(self.host, body['hosts']) @test.attr(type='gate') def test_aggregate_add_host_create_server_with_az(self): # Add an host to the given aggregate and create a server. self.useFixture(fixtures.LockFixture('availability_zone')) aggregate_name = data_utils.rand_name(self.aggregate_name_prefix) az_name = data_utils.rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate( name=aggregate_name, availability_zone=az_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) server_name = data_utils.rand_name('test_server_') admin_servers_client = self.os_adm.servers_client resp, server = self.create_test_server(name=server_name, availability_zone=az_name, wait_until='ACTIVE') resp, body = admin_servers_client.get_server(server['id']) self.assertEqual(self.host, body[self._host_key]) class AggregatesAdminTestXML(AggregatesAdminTestJSON): _host_key = ( '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host') _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_flavors_access.py0000664000175000017500000001111412332757070027477 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest): """ Tests Flavor Access API extension. Add and remove Flavor Access require admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsAccessTestJSON, cls).setUpClass() if not test.is_extension_enabled('FlavorExtraData', 'compute'): msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client admin_client = cls._get_identity_admin_client() cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client. tenant_name) cls.tenant_id = cls.tenant['id'] cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm. flavors_client. tenant_name) cls.adm_tenant_id = cls.adm_tenant['id'] cls.flavor_name_prefix = 'test_flavor_access_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 @test.attr(type='gate') def test_flavor_access_list_with_private_flavor(self): # Test to make sure that list flavor access on a newly created # private flavor will return an empty access list flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) self.assertEqual(resp.status, 200) resp, flavor_access = self.client.list_flavor_access(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(len(flavor_access), 0, str(flavor_access)) @test.attr(type='gate') def test_flavor_access_add_remove(self): # Test to add and remove flavor access to a given tenant. flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = data_utils.rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) # Add flavor access to a tenant. resp_body = { "tenant_id": str(self.tenant_id), "flavor_id": str(new_flavor['id']), } add_resp, add_body = \ self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.assertEqual(add_resp.status, 200) self.assertIn(resp_body, add_body) # The flavor is present in list. resp, flavors = self.flavors_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) self.assertIn(new_flavor['id'], map(lambda x: x['id'], flavors)) # Remove flavor access from a tenant. remove_resp, remove_body = \ self.client.remove_flavor_access(new_flavor['id'], self.tenant_id) self.assertEqual(remove_resp.status, 200) self.assertNotIn(resp_body, remove_body) # The flavor is not present in list. resp, flavors = self.flavors_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors)) class FlavorsAdminTestXML(FlavorsAccessTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_availability_zone.py0000664000175000017500000000313612332757070030214 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class AZAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests Availability Zone API List """ @classmethod def setUpClass(cls): super(AZAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.availability_zone_client @test.attr(type='gate') def test_get_availability_zone_list(self): # List of availability zone resp, availability_zone = self.client.get_availability_zone_list() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) @test.attr(type='gate') def test_get_availability_zone_list_detail(self): # List of availability zones and available services resp, availability_zone = \ self.client.get_availability_zone_list_detail() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) class AZAdminTestXML(AZAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_quotas.py0000664000175000017500000001407512332757070026027 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest): force_tenant_isolation = True @classmethod def setUpClass(cls): super(QuotasAdminTestJSON, cls).setUpClass() cls.adm_client = cls.os_adm.quotas_client # NOTE(afazekas): these test cases should always create and use a new # tenant most of them should be skipped if we can't do that cls.demo_tenant_id = cls.isolated_creds.get_primary_creds().tenant_id cls.default_quota_set = set(('injected_file_content_bytes', 'metadata_items', 'injected_files', 'ram', 'floating_ips', 'fixed_ips', 'key_pairs', 'injected_file_path_bytes', 'instances', 'security_group_rules', 'cores', 'security_groups')) @test.attr(type='smoke') def test_get_default_quotas(self): # Admin can get the default resource quota set for a tenant expected_quota_set = self.default_quota_set | set(['id']) resp, quota_set = self.adm_client.get_default_quota_set( self.demo_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(sorted(expected_quota_set), sorted(quota_set.keys())) self.assertEqual(quota_set['id'], self.demo_tenant_id) @test.attr(type='gate') def test_update_all_quota_resources_for_tenant(self): # Admin can update all the resource quota limits for a tenant resp, default_quota_set = self.adm_client.get_default_quota_set( self.demo_tenant_id) new_quota_set = {'injected_file_content_bytes': 20480, 'metadata_items': 256, 'injected_files': 10, 'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10, 'key_pairs': 200, 'injected_file_path_bytes': 512, 'instances': 20, 'security_group_rules': 20, 'cores': 2, 'security_groups': 20} # Update limits for all quota resources resp, quota_set = self.adm_client.update_quota_set( self.demo_tenant_id, force=True, **new_quota_set) default_quota_set.pop('id') self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, **default_quota_set) self.assertEqual(200, resp.status) self.assertEqual(new_quota_set, quota_set) # TODO(afazekas): merge these test cases @test.attr(type='gate') def test_get_updated_quotas(self): # Verify that GET shows the updated quota set of tenant tenant_name = data_utils.rand_name('cpu_quota_tenant_') tenant_desc = tenant_name + '-desc' identity_client = self.os_adm.identity_client _, tenant = identity_client.create_tenant(name=tenant_name, description=tenant_desc) tenant_id = tenant['id'] self.addCleanup(identity_client.delete_tenant, tenant_id) self.adm_client.update_quota_set(tenant_id, ram='5120') resp, quota_set = self.adm_client.get_quota_set(tenant_id) self.assertEqual(200, resp.status) self.assertEqual(5120, quota_set['ram']) # Verify that GET shows the updated quota set of user user_name = data_utils.rand_name('cpu_quota_user_') password = data_utils.rand_name('password-') email = user_name + '@testmail.tm' _, user = identity_client.create_user(name=user_name, password=password, tenant_id=tenant_id, email=email) user_id = user['id'] self.addCleanup(identity_client.delete_user, user_id) self.adm_client.update_quota_set(tenant_id, user_id=user_id, ram='2048') resp, quota_set = self.adm_client.get_quota_set(tenant_id, user_id=user_id) self.assertEqual(200, resp.status) self.assertEqual(2048, quota_set['ram']) @test.attr(type='gate') def test_delete_quota(self): # Admin can delete the resource quota set for a tenant tenant_name = data_utils.rand_name('ram_quota_tenant_') tenant_desc = tenant_name + '-desc' identity_client = self.os_adm.identity_client _, tenant = identity_client.create_tenant(name=tenant_name, description=tenant_desc) tenant_id = tenant['id'] self.addCleanup(identity_client.delete_tenant, tenant_id) resp, quota_set_default = self.adm_client.get_quota_set(tenant_id) ram_default = quota_set_default['ram'] resp, body = self.adm_client.update_quota_set(tenant_id, ram='5120') self.assertEqual(200, resp.status) resp, body = self.adm_client.delete_quota_set(tenant_id) self.assertEqual(202, resp.status) resp, quota_set_new = self.adm_client.get_quota_set(tenant_id) self.assertEqual(200, resp.status) self.assertEqual(ram_default, quota_set_new['ram']) class QuotasAdminTestXML(QuotasAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_simple_tenant_usage.py0000664000175000017500000000556512332757070030545 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import time from tempest.api.compute import base from tempest import test class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(TenantUsagesTestJSON, cls).setUpClass() cls.adm_client = cls.os_adm.tenant_usages_client cls.client = cls.os.tenant_usages_client cls.identity_client = cls._get_identity_admin_client() resp, tenants = cls.identity_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] # Create a server in the demo tenant resp, server = cls.create_test_server(wait_until='ACTIVE') time.sleep(2) now = datetime.datetime.now() cls.start = cls._parse_strtime(now - datetime.timedelta(days=1)) cls.end = cls._parse_strtime(now + datetime.timedelta(days=1)) @classmethod def _parse_strtime(cls, at): # Returns formatted datetime return at.strftime('%Y-%m-%dT%H:%M:%S.%f') @test.attr(type='gate') def test_list_usage_all_tenants(self): # Get usage for all tenants params = {'start': self.start, 'end': self.end, 'detailed': int(bool(True))} resp, tenant_usage = self.adm_client.list_tenant_usages(params) self.assertEqual(200, resp.status) self.assertEqual(len(tenant_usage), 8) @test.attr(type='gate') def test_get_usage_tenant(self): # Get usage for a specific tenant params = {'start': self.start, 'end': self.end} resp, tenant_usage = self.adm_client.get_tenant_usage( self.tenant_id, params) self.assertEqual(200, resp.status) self.assertEqual(len(tenant_usage), 8) @test.attr(type='gate') def test_get_usage_tenant_with_non_admin_user(self): # Get usage for a specific tenant with non admin user params = {'start': self.start, 'end': self.end} resp, tenant_usage = self.client.get_tenant_usage( self.tenant_id, params) self.assertEqual(200, resp.status) self.assertEqual(len(tenant_usage), 8) class TenantUsagesTestXML(TenantUsagesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_security_groups.py0000664000175000017500000001000012332757070027741 0ustar chuckchuck00000000000000# Copyright 2013 NTT Data # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import test CONF = config.CONF class SecurityGroupsTestAdminJSON(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(SecurityGroupsTestAdminJSON, cls).setUpClass() cls.adm_client = cls.os_adm.security_groups_client cls.client = cls.security_groups_client def _delete_security_group(self, securitygroup_id, admin=True): if admin: resp, _ = self.adm_client.delete_security_group(securitygroup_id) else: resp, _ = self.client.delete_security_group(securitygroup_id) self.assertEqual(202, resp.status) @testtools.skipIf(CONF.service_available.neutron, "Skipped because neutron do not support all_tenants" "search filter.") @test.attr(type='smoke') def test_list_security_groups_list_all_tenants_filter(self): # Admin can list security groups of all tenants # List of all security groups created security_group_list = [] # Create two security groups for a non-admin tenant for i in range(2): name = data_utils.rand_name('securitygroup-') description = data_utils.rand_name('description-') resp, securitygroup = (self.client .create_security_group(name, description)) self.assertEqual(200, resp.status) self.addCleanup(self._delete_security_group, securitygroup['id'], admin=False) security_group_list.append(securitygroup) client_tenant_id = securitygroup['tenant_id'] # Create two security groups for admin tenant for i in range(2): name = data_utils.rand_name('securitygroup-') description = data_utils.rand_name('description-') resp, adm_securitygroup = (self.adm_client .create_security_group(name, description)) self.assertEqual(200, resp.status) self.addCleanup(self._delete_security_group, adm_securitygroup['id']) security_group_list.append(adm_securitygroup) # Fetch all security groups based on 'all_tenants' search filter param = {'all_tenants': 'true'} resp, fetched_list = self.adm_client.list_security_groups(params=param) self.assertEqual(200, resp.status) sec_group_id_list = map(lambda sg: sg['id'], fetched_list) # Now check if all created Security Groups are present in fetched list for sec_group in security_group_list: self.assertIn(sec_group['id'], sec_group_id_list) # Fetch all security groups for non-admin user with 'all_tenants' # search filter resp, fetched_list = self.client.list_security_groups(params=param) self.assertEqual(200, resp.status) # Now check if all created Security Groups are present in fetched list for sec_group in fetched_list: self.assertEqual(sec_group['tenant_id'], client_tenant_id, "Failed to get all security groups for " "non admin user.") class SecurityGroupsTestAdminXML(SecurityGroupsTestAdminJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_flavors_negative.py0000664000175000017500000001021312332757070030037 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test load_tests = test.NegativeAutoTest.load_tests class FlavorsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """ Tests Flavors API Create and Delete that require admin privileges """ @classmethod def setUpClass(cls): super(FlavorsAdminNegativeTestJSON, cls).setUpClass() if not test.is_extension_enabled('FlavorExtraData', 'compute'): msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client cls.user_client = cls.os.flavors_client cls.flavor_name_prefix = 'test_flavor_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 cls.ephemeral = 10 cls.swap = 1024 cls.rxtx = 2 @test.attr(type=['negative', 'gate']) def test_get_flavor_details_for_deleted_flavor(self): # Delete a flavor and ensure it is not listed # Create a test flavor flavor_name = data_utils.rand_name(self.flavor_name_prefix) # no need to specify flavor_id, we can get the flavor_id from a # response of create_flavor() call. resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, '', ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) # Delete the flavor new_flavor_id = flavor['id'] resp_delete, body = self.client.delete_flavor(new_flavor_id) self.assertEqual(200, resp.status) self.assertEqual(202, resp_delete.status) # Deleted flavors can be seen via detailed GET resp, flavor = self.client.get_flavor_details(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) # Deleted flavors should not show up in a list however resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) flag = True for flavor in flavors: if flavor['name'] == flavor_name: flag = False self.assertTrue(flag) @test.attr(type=['negative', 'gate']) def test_create_flavor_as_user(self): # only admin user can create a flavor flavor_name = data_utils.rand_name(self.flavor_name_prefix) new_flavor_id = str(uuid.uuid4()) self.assertRaises(exceptions.Unauthorized, self.user_client.create_flavor, flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) @test.attr(type=['negative', 'gate']) def test_delete_flavor_as_user(self): # only admin user can delete a flavor self.assertRaises(exceptions.Unauthorized, self.user_client.delete_flavor, self.flavor_ref_alt) @test.SimpleNegativeAutoTest class FlavorCreateNegativeTestJSON(base.BaseV2ComputeAdminTest, test.NegativeAutoTest): _interface = 'json' _service = 'compute' _schema_file = 'compute/admin/flavor_create.json' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_flavors_extra_specs.py0000664000175000017500000001216012332757070030560 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import test class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest): """ Tests Flavor Extra Spec API extension. SET, UNSET, UPDATE Flavor Extra specs require admin privileges. GET Flavor Extra specs can be performed even by without admin privileges. """ @classmethod def setUpClass(cls): super(FlavorsExtraSpecsTestJSON, cls).setUpClass() if not test.is_extension_enabled('FlavorExtraData', 'compute'): msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client flavor_name = data_utils.rand_name('test_flavor') ram = 512 vcpus = 1 disk = 10 ephemeral = 10 cls.new_flavor_id = data_utils.rand_int_id(start=1000) swap = 1024 rxtx = 1 # Create a flavor so as to set/get/unset extra specs resp, cls.flavor = cls.client.create_flavor(flavor_name, ram, vcpus, disk, cls.new_flavor_id, ephemeral=ephemeral, swap=swap, rxtx=rxtx) @classmethod def tearDownClass(cls): resp, body = cls.client.delete_flavor(cls.flavor['id']) cls.client.wait_for_resource_deletion(cls.flavor['id']) super(FlavorsExtraSpecsTestJSON, cls).tearDownClass() @test.attr(type='gate') def test_flavor_set_get_update_show_unset_keys(self): # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra # spec as a user with admin privileges. # Assigning extra specs values that are to be set specs = {"key1": "value1", "key2": "value2"} # SET extra specs to the flavor created in setUp set_resp, set_body = \ self.client.set_flavor_extra_spec(self.flavor['id'], specs) self.assertEqual(set_resp.status, 200) self.assertEqual(set_body, specs) # GET extra specs and verify get_resp, get_body = \ self.client.get_flavor_extra_spec(self.flavor['id']) self.assertEqual(get_resp.status, 200) self.assertEqual(get_body, specs) # UPDATE the value of the extra specs key1 update_resp, update_body = \ self.client.update_flavor_extra_spec(self.flavor['id'], "key1", key1="value") self.assertEqual(update_resp.status, 200) self.assertEqual({"key1": "value"}, update_body) # GET extra specs and verify the value of the key2 # is the same as before get_resp, get_body = \ self.client.get_flavor_extra_spec(self.flavor['id']) self.assertEqual(get_resp.status, 200) self.assertEqual(get_body, {"key1": "value", "key2": "value2"}) # UNSET extra specs that were set in this test unset_resp, _ = \ self.client.unset_flavor_extra_spec(self.flavor['id'], "key1") self.assertEqual(unset_resp.status, 200) unset_resp, _ = \ self.client.unset_flavor_extra_spec(self.flavor['id'], "key2") self.assertEqual(unset_resp.status, 200) @test.attr(type='gate') def test_flavor_non_admin_get_all_keys(self): specs = {"key1": "value1", "key2": "value2"} set_resp, set_body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) resp, body = self.flavors_client.get_flavor_extra_spec( self.flavor['id']) self.assertEqual(resp.status, 200) for key in specs: self.assertEqual(body[key], specs[key]) @test.attr(type='gate') def test_flavor_non_admin_get_specific_key(self): specs = {"key1": "value1", "key2": "value2"} resp, body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) self.assertEqual(resp.status, 200) self.assertEqual(body['key1'], 'value1') self.assertIn('key2', body) resp, body = self.flavors_client.get_flavor_extra_spec_with_key( self.flavor['id'], 'key1') self.assertEqual(resp.status, 200) self.assertEqual(body['key1'], 'value1') self.assertNotIn('key2', body) class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_fixed_ips_negative.py0000664000175000017500000000611712332757070030345 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class FixedIPsNegativeTestJson(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(FixedIPsNegativeTestJson, cls).setUpClass() if CONF.service_available.neutron: msg = ("%s skipped as neutron is available" % cls.__name__) raise cls.skipException(msg) cls.client = cls.os_adm.fixed_ips_client cls.non_admin_client = cls.fixed_ips_client resp, server = cls.create_test_server(wait_until='ACTIVE') resp, server = cls.servers_client.get_server(server['id']) for ip_set in server['addresses']: for ip in server['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': cls.ip = ip['addr'] break if cls.ip: break @test.attr(type=['negative', 'gate']) def test_list_fixed_ip_details_with_non_admin_user(self): self.assertRaises(exceptions.Unauthorized, self.non_admin_client.get_fixed_ip_details, self.ip) @test.attr(type=['negative', 'gate']) def test_set_reserve_with_non_admin_user(self): body = {"reserve": "None"} self.assertRaises(exceptions.Unauthorized, self.non_admin_client.reserve_fixed_ip, self.ip, body) @test.attr(type=['negative', 'gate']) def test_set_unreserve_with_non_admin_user(self): body = {"unreserve": "None"} self.assertRaises(exceptions.Unauthorized, self.non_admin_client.reserve_fixed_ip, self.ip, body) @test.attr(type=['negative', 'gate']) def test_set_reserve_with_invalid_ip(self): # NOTE(maurosr): since this exercises the same code snippet, we do it # only for reserve action body = {"reserve": "None"} self.assertRaises(exceptions.NotFound, self.client.reserve_fixed_ip, "my.invalid.ip", body) @test.attr(type=['negative', 'gate']) def test_fixed_ip_with_invalid_action(self): body = {"invalid_action": "None"} self.assertRaises(exceptions.BadRequest, self.client.reserve_fixed_ip, self.ip, body) class FixedIPsNegativeTestXml(FixedIPsNegativeTestJson): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py0000664000175000017500000000377712332757070033423 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import urllib from tempest.api.compute import base from tempest import exceptions from tempest import test class InstanceUsageAuditLogNegativeTestJSON(base.BaseV2ComputeAdminTest): @classmethod def setUpClass(cls): super(InstanceUsageAuditLogNegativeTestJSON, cls).setUpClass() cls.adm_client = cls.os_adm.instance_usages_audit_log_client @test.attr(type=['negative', 'gate']) def test_instance_usage_audit_logs_with_nonadmin_user(self): # the instance_usage_audit_logs API just can be accessed by admin user self.assertRaises(exceptions.Unauthorized, self.instance_usages_audit_log_client. list_instance_usage_audit_logs) now = datetime.datetime.now() self.assertRaises(exceptions.Unauthorized, self.instance_usages_audit_log_client. get_instance_usage_audit_log, urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S"))) @test.attr(type=['negative', 'gate']) def test_get_instance_usage_audit_logs_with_invalid_time(self): self.assertRaises(exceptions.BadRequest, self.adm_client.get_instance_usage_audit_log, "invalid_time") class InstanceUsageAuditLogNegativeTestXML( InstanceUsageAuditLogNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/admin/test_services.py0000664000175000017500000000566512332757070026343 0ustar chuckchuck00000000000000# Copyright 2013 NEC Corporation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest): """ Tests Services API. List and Enable/Disable require admin privileges. """ @classmethod def setUpClass(cls): super(ServicesAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.services_client @test.attr(type='gate') def test_list_services(self): resp, services = self.client.list_services() self.assertEqual(200, resp.status) self.assertNotEqual(0, len(services)) @test.attr(type='gate') def test_get_service_by_service_binary_name(self): binary_name = 'nova-compute' params = {'binary': binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertNotEqual(0, len(services)) for service in services: self.assertEqual(binary_name, service['binary']) @test.attr(type='gate') def test_get_service_by_host_name(self): resp, services = self.client.list_services() host_name = services[0]['host'] services_on_host = [service for service in services if service['host'] == host_name] params = {'host': host_name} resp, services = self.client.list_services(params) # we could have a periodic job checkin between the 2 service # lookups, so only compare binary lists. s1 = map(lambda x: x['binary'], services) s2 = map(lambda x: x['binary'], services_on_host) # sort the lists before comparing, to take out dependency # on order. self.assertEqual(sorted(s1), sorted(s2)) @test.attr(type='gate') def test_get_service_by_service_and_host_name(self): resp, services = self.client.list_services() host_name = services[0]['host'] binary_name = services[0]['binary'] params = {'host': host_name, 'binary': binary_name} resp, services = self.client.list_services(params) self.assertEqual(200, resp.status) self.assertEqual(1, len(services)) self.assertEqual(host_name, services[0]['host']) self.assertEqual(binary_name, services[0]['binary']) class ServicesAdminTestXML(ServicesAdminTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/0000775000175000017500000000000012332757136024464 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/test_list_floating_ips.py0000664000175000017500000000630112332757070031603 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class FloatingIPDetailsTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(FloatingIPDetailsTestJSON, cls).setUpClass() cls.client = cls.floating_ips_client cls.floating_ip = [] cls.floating_ip_id = [] for i in range(3): resp, body = cls.client.create_floating_ip() cls.floating_ip.append(body) cls.floating_ip_id.append(body['id']) @classmethod def tearDownClass(cls): for i in range(3): cls.client.delete_floating_ip(cls.floating_ip_id[i]) super(FloatingIPDetailsTestJSON, cls).tearDownClass() @test.attr(type='gate') def test_list_floating_ips(self): # Positive test:Should return the list of floating IPs resp, body = self.client.list_floating_ips() self.assertEqual(200, resp.status) floating_ips = body self.assertNotEqual(0, len(floating_ips), "Expected floating IPs. Got zero.") for i in range(3): self.assertIn(self.floating_ip[i], floating_ips) @test.attr(type='gate') def test_get_floating_ip_details(self): # Positive test:Should be able to GET the details of floatingIP # Creating a floating IP for which details are to be checked resp, body = self.client.create_floating_ip() floating_ip_id = body['id'] self.addCleanup(self.client.delete_floating_ip, floating_ip_id) floating_ip_instance_id = body['instance_id'] floating_ip_ip = body['ip'] floating_ip_fixed_ip = body['fixed_ip'] resp, body = \ self.client.get_floating_ip_details(floating_ip_id) self.assertEqual(200, resp.status) # Comparing the details of floating IP self.assertEqual(floating_ip_instance_id, body['instance_id']) self.assertEqual(floating_ip_ip, body['ip']) self.assertEqual(floating_ip_fixed_ip, body['fixed_ip']) self.assertEqual(floating_ip_id, body['id']) @test.attr(type='gate') def test_list_floating_ip_pools(self): # Positive test:Should return the list of floating IP Pools resp, floating_ip_pools = self.client.list_floating_ip_pools() self.assertEqual(200, resp.status) self.assertNotEqual(0, len(floating_ip_pools), "Expected floating IP Pools. Got zero.") class FloatingIPDetailsTestXML(FloatingIPDetailsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/test_floating_ips_actions.py0000664000175000017500000001315112332757070032271 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute.floating_ips import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class FloatingIPsTestJSON(base.BaseFloatingIPsTest): server_id = None floating_ip = None @classmethod @test.safe_setup def setUpClass(cls): super(FloatingIPsTestJSON, cls).setUpClass() cls.client = cls.floating_ips_client cls.floating_ip_id = None # Server creation resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] # Floating IP creation resp, body = cls.client.create_floating_ip() cls.floating_ip_id = body['id'] cls.floating_ip = body['ip'] @classmethod def tearDownClass(cls): # Deleting the floating IP which is created in this method if cls.floating_ip_id: resp, body = cls.client.delete_floating_ip(cls.floating_ip_id) super(FloatingIPsTestJSON, cls).tearDownClass() def _try_delete_floating_ip(self, floating_ip_id): # delete floating ip, if it exists try: self.client.delete_floating_ip(floating_ip_id) # if not found, it depicts it was deleted in the test except exceptions.NotFound: pass @test.attr(type='gate') def test_allocate_floating_ip(self): # Positive test:Allocation of a new floating IP to a project # should be successful resp, body = self.client.create_floating_ip() floating_ip_id_allocated = body['id'] self.addCleanup(self.client.delete_floating_ip, floating_ip_id_allocated) self.assertEqual(200, resp.status) resp, floating_ip_details = \ self.client.get_floating_ip_details(floating_ip_id_allocated) # Checking if the details of allocated IP is in list of floating IP resp, body = self.client.list_floating_ips() self.assertIn(floating_ip_details, body) @test.attr(type='gate') def test_delete_floating_ip(self): # Positive test:Deletion of valid floating IP from project # should be successful # Creating the floating IP that is to be deleted in this method resp, floating_ip_body = self.client.create_floating_ip() self.addCleanup(self._try_delete_floating_ip, floating_ip_body['id']) # Storing the details of floating IP before deleting it cli_resp = self.client.get_floating_ip_details(floating_ip_body['id']) resp, floating_ip_details = cli_resp # Deleting the floating IP from the project resp, body = self.client.delete_floating_ip(floating_ip_body['id']) self.assertEqual(202, resp.status) # Check it was really deleted. self.client.wait_for_resource_deletion(floating_ip_body['id']) @test.attr(type='gate') def test_associate_disassociate_floating_ip(self): # Positive test:Associate and disassociate the provided floating IP # to a specific server should be successful # Association of floating IP to fixed IP address resp, body = self.client.associate_floating_ip_to_server( self.floating_ip, self.server_id) self.assertEqual(202, resp.status) # Disassociation of floating IP that was associated in this method resp, body = self.client.disassociate_floating_ip_from_server( self.floating_ip, self.server_id) self.assertEqual(202, resp.status) @test.attr(type='gate') def test_associate_already_associated_floating_ip(self): # positive test:Association of an already associated floating IP # to specific server should change the association of the Floating IP # Create server so as to use for Multiple association new_name = data_utils.rand_name('floating_server') resp, body = self.create_test_server(name=new_name) self.servers_client.wait_for_server_status(body['id'], 'ACTIVE') self.new_server_id = body['id'] # Associating floating IP for the first time resp, _ = self.client.associate_floating_ip_to_server( self.floating_ip, self.server_id) # Associating floating IP for the second time resp, body = self.client.associate_floating_ip_to_server( self.floating_ip, self.new_server_id) self.addCleanup(self.servers_client.delete_server, self.new_server_id) if (resp['status'] is not None): self.addCleanup(self.client.disassociate_floating_ip_from_server, self.floating_ip, self.new_server_id) # Make sure no longer associated with old server self.assertRaises((exceptions.NotFound, exceptions.UnprocessableEntity), self.client.disassociate_floating_ip_from_server, self.floating_ip, self.server_id) class FloatingIPsTestXML(FloatingIPsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/__init__.py0000664000175000017500000000000012332757070026560 0ustar chuckchuck00000000000000././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.pytempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.0000664000175000017500000000723512332757070033610 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute.floating_ips import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class FloatingIPsNegativeTestJSON(base.BaseFloatingIPsTest): server_id = None @classmethod def setUpClass(cls): super(FloatingIPsNegativeTestJSON, cls).setUpClass() cls.client = cls.floating_ips_client # Server creation resp, server = cls.create_test_server(wait_until='ACTIVE') cls.server_id = server['id'] # Generating a nonexistent floatingIP id cls.floating_ip_ids = [] resp, body = cls.client.list_floating_ips() for i in range(len(body)): cls.floating_ip_ids.append(body[i]['id']) while True: cls.non_exist_id = data_utils.rand_int_id(start=999) if CONF.service_available.neutron: cls.non_exist_id = str(uuid.uuid4()) if cls.non_exist_id not in cls.floating_ip_ids: break @test.attr(type=['negative', 'gate']) def test_allocate_floating_ip_from_nonexistent_pool(self): # Negative test:Allocation of a new floating IP from a nonexistent_pool # to a project should fail self.assertRaises(exceptions.NotFound, self.client.create_floating_ip, "non_exist_pool") @test.attr(type=['negative', 'gate']) def test_delete_nonexistent_floating_ip(self): # Negative test:Deletion of a nonexistent floating IP # from project should fail # Deleting the non existent floating IP self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip, self.non_exist_id) @test.attr(type=['negative', 'gate']) def test_associate_nonexistent_floating_ip(self): # Negative test:Association of a non existent floating IP # to specific server should fail # Associating non existent floating IP self.assertRaises(exceptions.NotFound, self.client.associate_floating_ip_to_server, "0.0.0.0", self.server_id) @test.attr(type=['negative', 'gate']) def test_dissociate_nonexistent_floating_ip(self): # Negative test:Dissociation of a non existent floating IP should fail # Dissociating non existent floating IP self.assertRaises(exceptions.NotFound, self.client.disassociate_floating_ip_from_server, "0.0.0.0", self.server_id) @test.attr(type=['negative', 'gate']) def test_associate_ip_to_server_without_passing_floating_ip(self): # Negative test:Association of empty floating IP to specific server # should raise NotFound exception self.assertRaises(exceptions.NotFound, self.client.associate_floating_ip_to_server, '', self.server_id) class FloatingIPsNegativeTestXML(FloatingIPsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py0000664000175000017500000000323112332757070033464 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import uuid from tempest.api.compute import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class FloatingIPDetailsNegativeTestJSON(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): super(FloatingIPDetailsNegativeTestJSON, cls).setUpClass() cls.client = cls.floating_ips_client @test.attr(type=['negative', 'gate']) def test_get_nonexistent_floating_ip_details(self): # Negative test:Should not be able to GET the details # of non-existent floating IP # Creating a non-existent floatingIP id if CONF.service_available.neutron: non_exist_id = str(uuid.uuid4()) else: non_exist_id = data_utils.rand_int_id(start=999) self.assertRaises(exceptions.NotFound, self.client.get_floating_ip_details, non_exist_id) class FloatingIPDetailsNegativeTestXML(FloatingIPDetailsNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/floating_ips/base.py0000664000175000017500000000176312332757070025754 0ustar chuckchuck00000000000000# Copyright 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base class BaseFloatingIPsTest(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): # Floating IP actions might need a full network configuration cls.set_network_resources(network=True, subnet=True, router=True, dhcp=True) super(BaseFloatingIPsTest, cls).setUpClass() tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v2/0000775000175000017500000000000012332757136022335 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/v2/__init__.py0000664000175000017500000000000012332757070024431 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/0000775000175000017500000000000012332757136025254 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/test_security_groups_negative.py0000664000175000017500000002213512332757070034015 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from tempest.api.compute.security_groups import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF class SecurityGroupsNegativeTestJSON(base.BaseSecurityGroupsTest): @classmethod def setUpClass(cls): super(SecurityGroupsNegativeTestJSON, cls).setUpClass() cls.client = cls.security_groups_client cls.neutron_available = CONF.service_available.neutron def _generate_a_non_existent_security_group_id(self): security_group_id = [] resp, body = self.client.list_security_groups() for i in range(len(body)): security_group_id.append(body[i]['id']) # Generate a non-existent security group id while True: non_exist_id = data_utils.rand_int_id(start=999) if self.neutron_available: non_exist_id = data_utils.rand_uuid() if non_exist_id not in security_group_id: break return non_exist_id @test.attr(type=['negative', 'smoke']) def test_security_group_get_nonexistent_group(self): # Negative test:Should not be able to GET the details # of non-existent Security Group non_exist_id = self._generate_a_non_existent_security_group_id() self.assertRaises(exceptions.NotFound, self.client.get_security_group, non_exist_id) @test.skip_because(bug="1161411", condition=CONF.service_available.neutron) @test.attr(type=['negative', 'smoke']) def test_security_group_create_with_invalid_group_name(self): # Negative test: Security Group should not be created with group name # as an empty string/with white spaces/chars more than 255 s_description = data_utils.rand_name('description-') # Create Security Group with empty string as group name self.assertRaises(exceptions.BadRequest, self.client.create_security_group, "", s_description) # Create Security Group with white space in group name self.assertRaises(exceptions.BadRequest, self.client.create_security_group, " ", s_description) # Create Security Group with group name longer than 255 chars s_name = 'securitygroup-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, s_description) @test.skip_because(bug="1161411", condition=CONF.service_available.neutron) @test.attr(type=['negative', 'smoke']) def test_security_group_create_with_invalid_group_description(self): # Negative test:Security Group should not be created with description # as an empty string/with white spaces/chars more than 255 s_name = data_utils.rand_name('securitygroup-') # Create Security Group with empty string as description self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, "") # Create Security Group with white space in description self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, " ") # Create Security Group with group description longer than 255 chars s_description = 'description-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, s_description) @testtools.skipIf(CONF.service_available.neutron, "Neutron allows duplicate names for security groups") @test.attr(type=['negative', 'smoke']) def test_security_group_create_with_duplicate_name(self): # Negative test:Security Group with duplicate name should not # be created s_name = data_utils.rand_name('securitygroup-') s_description = data_utils.rand_name('description-') resp, security_group =\ self.create_security_group(s_name, s_description) self.assertEqual(200, resp.status) # Now try the Security Group with the same 'Name' self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, s_description) @test.attr(type=['negative', 'smoke']) def test_delete_the_default_security_group(self): # Negative test:Deletion of the "default" Security Group should Fail default_security_group_id = None resp, body = self.client.list_security_groups() for i in range(len(body)): if body[i]['name'] == 'default': default_security_group_id = body[i]['id'] break # Deleting the "default" Security Group self.assertRaises(exceptions.BadRequest, self.client.delete_security_group, default_security_group_id) @test.attr(type=['negative', 'smoke']) def test_delete_nonexistent_security_group(self): # Negative test:Deletion of a non-existent Security Group should fail non_exist_id = self._generate_a_non_existent_security_group_id() self.assertRaises(exceptions.NotFound, self.client.delete_security_group, non_exist_id) @test.attr(type=['negative', 'smoke']) def test_delete_security_group_without_passing_id(self): # Negative test:Deletion of a Security Group with out passing ID # should Fail self.assertRaises(exceptions.NotFound, self.client.delete_security_group, '') @testtools.skipIf(CONF.service_available.neutron, "Neutron not check the security_group_id") @test.attr(type=['negative', 'smoke']) def test_update_security_group_with_invalid_sg_id(self): # Update security_group with invalid sg_id should fail s_name = data_utils.rand_name('sg-') s_description = data_utils.rand_name('description-') # Create a non int sg_id sg_id_invalid = data_utils.rand_name('sg-') self.assertRaises(exceptions.BadRequest, self.client.update_security_group, sg_id_invalid, name=s_name, description=s_description) @testtools.skipIf(CONF.service_available.neutron, "Neutron not check the security_group_name") @test.attr(type=['negative', 'smoke']) def test_update_security_group_with_invalid_sg_name(self): # Update security_group with invalid sg_name should fail resp, securitygroup = self.create_security_group() self.assertEqual(200, resp.status) self.assertIn('id', securitygroup) securitygroup_id = securitygroup['id'] # Update Security Group with group name longer than 255 chars s_new_name = 'securitygroup-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self.client.update_security_group, securitygroup_id, name=s_new_name) @testtools.skipIf(CONF.service_available.neutron, "Neutron not check the security_group_description") @test.attr(type=['negative', 'smoke']) def test_update_security_group_with_invalid_sg_des(self): # Update security_group with invalid sg_des should fail resp, securitygroup = self.create_security_group() self.assertEqual(200, resp.status) self.assertIn('id', securitygroup) securitygroup_id = securitygroup['id'] # Update Security Group with group description longer than 255 chars s_new_des = 'des-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self.client.update_security_group, securitygroup_id, description=s_new_des) @test.attr(type=['negative', 'smoke']) def test_update_non_existent_security_group(self): # Update a non-existent Security Group should Fail non_exist_id = self._generate_a_non_existent_security_group_id() s_name = data_utils.rand_name('sg-') s_description = data_utils.rand_name('description-') self.assertRaises(exceptions.NotFound, self.client.update_security_group, non_exist_id, name=s_name, description=s_description) class SecurityGroupsNegativeTestXML(SecurityGroupsNegativeTestJSON): _interface = 'xml' ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/test_security_group_rules_negative.pytempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/test_security_group_rules_negati0000664000175000017500000001522312332757070034062 0ustar chuckchuck00000000000000# Copyright 2013 Huawei Technologies Co.,LTD. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute.security_groups import base from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest import test CONF = config.CONF def not_existing_id(): if CONF.service_available.neutron: return data_utils.rand_uuid() else: return data_utils.rand_int_id(start=999) class SecurityGroupRulesNegativeTestJSON(base.BaseSecurityGroupsTest): @classmethod def setUpClass(cls): super(SecurityGroupRulesNegativeTestJSON, cls).setUpClass() cls.client = cls.security_groups_client @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_with_non_existent_id(self): # Negative test: Creation of Security Group rule should FAIL # with non existent Parent group id # Adding rules to the non existent Security Group id parent_group_id = not_existing_id() ip_protocol = 'tcp' from_port = 22 to_port = 22 self.assertRaises(exceptions.NotFound, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_with_invalid_id(self): # Negative test: Creation of Security Group rule should FAIL # with Parent group id which is not integer # Adding rules to the non int Security Group id parent_group_id = data_utils.rand_name('non_int_id') ip_protocol = 'tcp' from_port = 22 to_port = 22 self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_duplicate(self): # Negative test: Create Security Group rule duplicate should fail # Creating a Security Group to add rule to it resp, sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = 'tcp' from_port = 22 to_port = 22 resp, rule = \ self.client.create_security_group_rule(parent_group_id, ip_protocol, from_port, to_port) self.addCleanup(self.client.delete_security_group_rule, rule['id']) self.assertEqual(200, resp.status) # Add the same rule to the group should fail self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_with_invalid_ip_protocol(self): # Negative test: Creation of Security Group rule should FAIL # with invalid ip_protocol # Creating a Security Group to add rule to it resp, sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = data_utils.rand_name('999') from_port = 22 to_port = 22 self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_with_invalid_from_port(self): # Negative test: Creation of Security Group rule should FAIL # with invalid from_port # Creating a Security Group to add rule to it resp, sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = 'tcp' from_port = data_utils.rand_int_id(start=65536) to_port = 22 self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_with_invalid_to_port(self): # Negative test: Creation of Security Group rule should FAIL # with invalid to_port # Creating a Security Group to add rule to it resp, sg = self.create_security_group() # Adding rules to the created Security Group parent_group_id = sg['id'] ip_protocol = 'tcp' from_port = 22 to_port = data_utils.rand_int_id(start=65536) self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @test.attr(type=['negative', 'smoke']) def test_create_security_group_rule_with_invalid_port_range(self): # Negative test: Creation of Security Group rule should FAIL # with invalid port range. # Creating a Security Group to add rule to it. resp, sg = self.create_security_group() # Adding a rule to the created Security Group secgroup_id = sg['id'] ip_protocol = 'tcp' from_port = 22 to_port = 21 self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, secgroup_id, ip_protocol, from_port, to_port) @test.attr(type=['negative', 'smoke']) def test_delete_security_group_rule_with_non_existent_id(self): # Negative test: Deletion of Security Group rule should be FAIL # with non existent id non_existent_rule_id = not_existing_id() self.assertRaises(exceptions.NotFound, self.client.delete_security_group_rule, non_existent_rule_id) class SecurityGroupRulesNegativeTestXML(SecurityGroupRulesNegativeTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/test_security_group_rules.py0000664000175000017500000001433712332757070033167 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute.security_groups import base from tempest import config from tempest import test CONF = config.CONF class SecurityGroupRulesTestJSON(base.BaseSecurityGroupsTest): @classmethod def setUpClass(cls): super(SecurityGroupRulesTestJSON, cls).setUpClass() cls.client = cls.security_groups_client cls.neutron_available = CONF.service_available.neutron @test.attr(type='smoke') def test_security_group_rules_create(self): # Positive test: Creation of Security Group rule # should be successful # Creating a Security Group to add rules to it resp, security_group = self.create_security_group() securitygroup_id = security_group['id'] # Adding rules to the created Security Group ip_protocol = 'tcp' from_port = 22 to_port = 22 resp, rule = \ self.client.create_security_group_rule(securitygroup_id, ip_protocol, from_port, to_port) self.addCleanup(self.client.delete_security_group_rule, rule['id']) self.assertEqual(200, resp.status) @test.attr(type='smoke') def test_security_group_rules_create_with_optional_arguments(self): # Positive test: Creation of Security Group rule # with optional arguments # should be successful secgroup1 = None secgroup2 = None # Creating a Security Group to add rules to it resp, security_group = self.create_security_group() secgroup1 = security_group['id'] # Creating a Security Group so as to assign group_id to the rule resp, security_group = self.create_security_group() secgroup2 = security_group['id'] # Adding rules to the created Security Group with optional arguments parent_group_id = secgroup1 ip_protocol = 'tcp' from_port = 22 to_port = 22 cidr = '10.2.3.124/24' group_id = secgroup2 resp, rule = \ self.client.create_security_group_rule(parent_group_id, ip_protocol, from_port, to_port, cidr=cidr, group_id=group_id) self.addCleanup(self.client.delete_security_group_rule, rule['id']) self.assertEqual(200, resp.status) @test.attr(type='smoke') def test_security_group_rules_list(self): # Positive test: Created Security Group rules should be # in the list of all rules # Creating a Security Group to add rules to it resp, security_group = self.create_security_group() securitygroup_id = security_group['id'] # Add a first rule to the created Security Group ip_protocol1 = 'tcp' from_port1 = 22 to_port1 = 22 resp, rule = \ self.client.create_security_group_rule(securitygroup_id, ip_protocol1, from_port1, to_port1) rule1_id = rule['id'] # Delete the Security Group rule1 at the end of this method self.addCleanup(self.client.delete_security_group_rule, rule1_id) # Add a second rule to the created Security Group ip_protocol2 = 'icmp' from_port2 = -1 to_port2 = -1 resp, rule = \ self.client.create_security_group_rule(securitygroup_id, ip_protocol2, from_port2, to_port2) rule2_id = rule['id'] # Delete the Security Group rule2 at the end of this method self.addCleanup(self.client.delete_security_group_rule, rule2_id) # Get rules of the created Security Group resp, rules = \ self.client.list_security_group_rules(securitygroup_id) self.assertTrue(any([i for i in rules if i['id'] == rule1_id])) self.assertTrue(any([i for i in rules if i['id'] == rule2_id])) @test.attr(type='smoke') def test_security_group_rules_delete_when_peer_group_deleted(self): # Positive test:rule will delete when peer group deleting # Creating a Security Group to add rules to it resp, security_group = self.create_security_group() sg1_id = security_group['id'] # Creating other Security Group to access to group1 resp, security_group = self.create_security_group() sg2_id = security_group['id'] # Adding rules to the Group1 ip_protocol = 'tcp' from_port = 22 to_port = 22 resp, rule = \ self.client.create_security_group_rule(sg1_id, ip_protocol, from_port, to_port, group_id=sg2_id) self.assertEqual(200, resp.status) # Delete group2 resp, body = self.client.delete_security_group(sg2_id) self.assertEqual(202, resp.status) # Get rules of the Group1 resp, rules = \ self.client.list_security_group_rules(sg1_id) # The group1 has no rules because group2 has deleted self.assertEqual(0, len(rules)) class SecurityGroupRulesTestXML(SecurityGroupRulesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/__init__.py0000664000175000017500000000000012332757070027350 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/test_security_groups.py0000664000175000017500000001553412332757070032140 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute.security_groups import base from tempest.common.utils import data_utils from tempest import exceptions from tempest import test class SecurityGroupsTestJSON(base.BaseSecurityGroupsTest): @classmethod def setUpClass(cls): super(SecurityGroupsTestJSON, cls).setUpClass() cls.client = cls.security_groups_client @test.attr(type='smoke') def test_security_groups_create_list_delete(self): # Positive test:Should return the list of Security Groups # Create 3 Security Groups for i in range(3): resp, securitygroup = self.create_security_group() self.assertEqual(200, resp.status) # Fetch all Security Groups and verify the list # has all created Security Groups resp, fetched_list = self.client.list_security_groups() self.assertEqual(200, resp.status) # Now check if all the created Security Groups are in fetched list missing_sgs = \ [sg for sg in self.security_groups if sg not in fetched_list] self.assertFalse(missing_sgs, "Failed to find Security Group %s in fetched " "list" % ', '.join(m_group['name'] for m_group in missing_sgs)) # Delete all security groups for sg in self.security_groups: resp, _ = self.client.delete_security_group(sg['id']) self.assertEqual(202, resp.status) self.client.wait_for_resource_deletion(sg['id']) # Now check if all the created Security Groups are deleted resp, fetched_list = self.client.list_security_groups() deleted_sgs = \ [sg for sg in self.security_groups if sg in fetched_list] self.assertFalse(deleted_sgs, "Failed to delete Security Group %s " "list" % ', '.join(m_group['name'] for m_group in deleted_sgs)) @test.attr(type='smoke') def test_security_group_create_get_delete(self): # Security Group should be created, fetched and deleted # with char space between name along with # leading and trailing spaces s_name = ' %s ' % data_utils.rand_name('securitygroup ') resp, securitygroup = self.create_security_group(name=s_name) self.assertEqual(200, resp.status) self.assertIn('name', securitygroup) securitygroup_name = securitygroup['name'] self.assertEqual(securitygroup_name, s_name, "The created Security Group name is " "not equal to the requested name") # Now fetch the created Security Group by its 'id' resp, fetched_group = \ self.client.get_security_group(securitygroup['id']) self.assertEqual(200, resp.status) self.assertEqual(securitygroup, fetched_group, "The fetched Security Group is different " "from the created Group") @test.attr(type='smoke') def test_server_security_groups(self): # Checks that security groups may be added and linked to a server # and not deleted if the server is active. # Create a couple security groups that we will use # for the server resource this test creates resp, sg = self.create_security_group() resp, sg2 = self.create_security_group() # Create server and add the security group created # above to the server we just created server_name = data_utils.rand_name('server') resp, server = self.create_test_server(name=server_name) server_id = server['id'] self.servers_client.wait_for_server_status(server_id, 'ACTIVE') resp, body = self.servers_client.add_security_group(server_id, sg['name']) # Check that we are not able to delete the security # group since it is in use by an active server self.assertRaises(exceptions.BadRequest, self.client.delete_security_group, sg['id']) # Reboot and add the other security group resp, body = self.servers_client.reboot(server_id, 'HARD') self.servers_client.wait_for_server_status(server_id, 'ACTIVE') resp, body = self.servers_client.add_security_group(server_id, sg2['name']) # Check that we are not able to delete the other security # group since it is in use by an active server self.assertRaises(exceptions.BadRequest, self.client.delete_security_group, sg2['id']) # Shutdown the server and then verify we can destroy the # security groups, since no active server instance is using them self.servers_client.delete_server(server_id) self.servers_client.wait_for_server_termination(server_id) self.client.delete_security_group(sg['id']) self.assertEqual(202, resp.status) self.client.delete_security_group(sg2['id']) self.assertEqual(202, resp.status) @test.attr(type='smoke') def test_update_security_groups(self): # Update security group name and description # Create a security group resp, securitygroup = self.create_security_group() self.assertEqual(200, resp.status) self.assertIn('id', securitygroup) securitygroup_id = securitygroup['id'] # Update the name and description s_new_name = data_utils.rand_name('sg-hth-') s_new_des = data_utils.rand_name('description-hth-') resp, sg_new = \ self.client.update_security_group(securitygroup_id, name=s_new_name, description=s_new_des) self.assertEqual(200, resp.status) # get the security group resp, fetched_group = \ self.client.get_security_group(securitygroup_id) self.assertEqual(s_new_name, fetched_group['name']) self.assertEqual(s_new_des, fetched_group['description']) class SecurityGroupsTestXML(SecurityGroupsTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/security_groups/base.py0000664000175000017500000000167212332757070026543 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base class BaseSecurityGroupsTest(base.BaseV2ComputeTest): @classmethod def setUpClass(cls): # A network and a subnet will be created for these tests cls.set_network_resources(network=True, subnet=True) super(BaseSecurityGroupsTest, cls).setUpClass() tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/certificates/0000775000175000017500000000000012332757136024453 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/certificates/__init__.py0000664000175000017500000000000012332757070026547 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/certificates/test_certificates.py0000664000175000017500000000260712332757070030533 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.api.compute import base from tempest import test class CertificatesTestJSON(base.BaseV2ComputeTest): @test.attr(type='gate') def test_create_root_certificate(self): # create certificates resp, body = self.certificates_client.create_certificate() self.assertEqual(200, resp.status) self.assertIn('data', body) self.assertIn('private_key', body) @test.attr(type='gate') def test_get_root_certificate(self): # get the root certificate resp, body = self.certificates_client.get_certificate('root') self.assertEqual(200, resp.status) self.assertIn('data', body) self.assertIn('private_key', body) class CertificatesTestXML(CertificatesTestJSON): _interface = 'xml' tempest-2014.1.dev4108.gf22b6cc/tempest/api/compute/base.py0000664000175000017500000004034512332757070023275 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time from tempest import clients from tempest.common.utils import data_utils from tempest import config from tempest import exceptions from tempest.openstack.common import log as logging import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class BaseComputeTest(tempest.test.BaseTestCase): """Base test case class for all Compute API tests.""" _api_version = 3 force_tenant_isolation = False @classmethod def setUpClass(cls): cls.set_network_resources() super(BaseComputeTest, cls).setUpClass() # TODO(andreaf) WE should care also for the alt_manager here # but only once client lazy load in the manager is done os = cls.get_client_manager() cls.os = os cls.build_interval = CONF.compute.build_interval cls.build_timeout = CONF.compute.build_timeout cls.ssh_user = CONF.compute.ssh_user cls.image_ref = CONF.compute.image_ref cls.image_ref_alt = CONF.compute.image_ref_alt cls.flavor_ref = CONF.compute.flavor_ref cls.flavor_ref_alt = CONF.compute.flavor_ref_alt cls.image_ssh_user = CONF.compute.image_ssh_user cls.image_ssh_password = CONF.compute.image_ssh_password cls.servers = [] cls.images = [] cls.multi_user = cls.get_multi_user() cls.security_groups = [] if cls._api_version == 2: cls.servers_client = cls.os.servers_client cls.flavors_client = cls.os.flavors_client cls.images_client = cls.os.images_client cls.extensions_client = cls.os.extensions_client cls.floating_ips_client = cls.os.floating_ips_client cls.keypairs_client = cls.os.keypairs_client cls.security_groups_client = cls.os.security_groups_client cls.quotas_client = cls.os.quotas_client cls.limits_client = cls.os.limits_client cls.volumes_extensions_client = cls.os.volumes_extensions_client cls.volumes_client = cls.os.volumes_client cls.interfaces_client = cls.os.interfaces_client cls.fixed_ips_client = cls.os.fixed_ips_client cls.availability_zone_client = cls.os.availability_zone_client cls.agents_client = cls.os.agents_client cls.aggregates_client = cls.os.aggregates_client cls.services_client = cls.os.services_client cls.instance_usages_audit_log_client = \ cls.os.instance_usages_audit_log_client cls.hypervisor_client = cls.os.hypervisor_client cls.certificates_client = cls.os.certificates_client cls.migrations_client = cls.os.migrations_client elif cls._api_version == 3: if not CONF.compute_feature_enabled.api_v3: skip_msg = ("%s skipped as nova v3 api is not available" % cls.__name__) raise cls.skipException(skip_msg) cls.servers_client = cls.os.servers_v3_client cls.images_client = cls.os.image_client cls.flavors_client = cls.os.flavors_v3_client cls.services_client = cls.os.services_v3_client cls.extensions_client = cls.os.extensions_v3_client cls.availability_zone_client = cls.os.availability_zone_v3_client cls.interfaces_client = cls.os.interfaces_v3_client cls.hypervisor_client = cls.os.hypervisor_v3_client cls.keypairs_client = cls.os.keypairs_v3_client cls.volumes_client = cls.os.volumes_client cls.certificates_client = cls.os.certificates_v3_client cls.keypairs_client = cls.os.keypairs_v3_client cls.aggregates_client = cls.os.aggregates_v3_client cls.hosts_client = cls.os.hosts_v3_client cls.quotas_client = cls.os.quotas_v3_client cls.version_client = cls.os.version_v3_client cls.migrations_client = cls.os.migrations_v3_client else: msg = ("Unexpected API version is specified (%s)" % cls._api_version) raise exceptions.InvalidConfiguration(message=msg) @classmethod def get_multi_user(cls): multi_user = True # Determine if there are two regular users that can be # used in testing. If the test cases are allowed to create # users (config.compute.allow_tenant_isolation is true, # then we allow multi-user. if not CONF.compute.allow_tenant_isolation: user1 = CONF.identity.username user2 = CONF.identity.alt_username if not user2 or user1 == user2: multi_user = False else: user2_password = CONF.identity.alt_password user2_tenant_name = CONF.identity.alt_tenant_name if not user2_password or not user2_tenant_name: msg = ("Alternate user specified but not alternate " "tenant or password: alt_tenant_name=%s " "alt_password=%s" % (user2_tenant_name, user2_password)) raise exceptions.InvalidConfiguration(msg) return multi_user @classmethod def clear_servers(cls): for server in cls.servers: try: cls.servers_client.delete_server(server['id']) except Exception: pass for server in cls.servers: try: cls.servers_client.wait_for_server_termination(server['id']) except Exception: pass @classmethod def server_check_teardown(cls): """Checks is the shared server clean enough for subsequent test. Method will delete the server when it's dirty. The setUp method is responsible for creating a new server. Exceptions raised in tearDown class are fails the test case, This method supposed to use only by tierDown methods, when the shared server_id is stored in the server_id of the class. """ if getattr(cls, 'server_id', None) is not None: try: cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE') except Exception as exc: LOG.exception(exc) cls.servers_client.delete_server(cls.server_id) cls.servers_client.wait_for_server_termination(cls.server_id) cls.server_id = None raise @classmethod def clear_images(cls): for image_id in cls.images: try: cls.images_client.delete_image(image_id) except exceptions.NotFound: # The image may have already been deleted which is OK. pass except Exception: LOG.exception('Exception raised deleting image %s' % image_id) @classmethod def clear_security_groups(cls): for sg in cls.security_groups: try: resp, body =\ cls.security_groups_client.delete_security_group(sg['id']) except exceptions.NotFound: # The security group may have already been deleted which is OK. pass except Exception as exc: LOG.info('Exception raised deleting security group %s', sg['id']) LOG.exception(exc) @classmethod def tearDownClass(cls): cls.clear_images() cls.clear_servers() cls.clear_security_groups() cls.clear_isolated_creds() super(BaseComputeTest, cls).tearDownClass() @classmethod def create_test_server(cls, **kwargs): """Wrapper utility that returns a test server.""" name = data_utils.rand_name(cls.__name__ + "-instance") if 'name' in kwargs: name = kwargs.pop('name') flavor = kwargs.get('flavor', cls.flavor_ref) image_id = kwargs.get('image_id', cls.image_ref) resp, body = cls.servers_client.create_server( name, image_id, flavor, **kwargs) # handle the case of multiple servers servers = [body] if 'min_count' in kwargs or 'max_count' in kwargs: # Get servers created which name match with name param. r, b = cls.servers_client.list_servers() servers = [s for s in b['servers'] if s['name'].startswith(name)] if 'wait_until' in kwargs: for server in servers: try: cls.servers_client.wait_for_server_status( server['id'], kwargs['wait_until']) except Exception as ex: if ('preserve_server_on_error' not in kwargs or kwargs['preserve_server_on_error'] is False): for server in servers: try: cls.servers_client.delete_server(server['id']) except Exception: pass raise ex cls.servers.extend(servers) return resp, body @classmethod def create_security_group(cls, name=None, description=None): if name is None: name = data_utils.rand_name(cls.__name__ + "-securitygroup") if description is None: description = data_utils.rand_name('description-') resp, body = \ cls.security_groups_client.create_security_group(name, description) cls.security_groups.append(body) return resp, body def wait_for(self, condition): """Repeatedly calls condition() until a timeout.""" start_time = int(time.time()) while True: try: condition() except Exception: pass else: return if int(time.time()) - start_time >= self.build_timeout: condition() return time.sleep(self.build_interval) @staticmethod def _delete_volume(volumes_client, volume_id): """Deletes the given volume and waits for it to be gone.""" try: resp, _ = volumes_client.delete_volume(volume_id) # TODO(mriedem): We should move the wait_for_resource_deletion # into the delete_volume method as a convenience to the caller. volumes_client.wait_for_resource_deletion(volume_id) except exceptions.NotFound: LOG.warn("Unable to delete volume '%s' since it was not found. " "Maybe it was already deleted?" % volume_id) @classmethod def prepare_instance_network(cls): if (CONF.compute.ssh_auth_method != 'disabled' and CONF.compute.ssh_connect_method == 'floating'): cls.set_network_resources(network=True, subnet=True, router=True, dhcp=True) @classmethod def create_image_from_server(cls, server_id, **kwargs): """Wrapper utility that returns an image created from the server.""" name = data_utils.rand_name(cls.__name__ + "-image") if 'name' in kwargs: name = kwargs.pop('name') if cls._api_version == 2: resp, image = cls.images_client.create_image(server_id, name) elif cls._api_version == 3: resp, image = cls.servers_client.create_image(server_id, name) image_id = data_utils.parse_image_id(resp['location']) cls.images.append(image_id) if 'wait_until' in kwargs: cls.images_client.wait_for_image_status(image_id, kwargs['wait_until']) if cls._api_version == 2: resp, image = cls.images_client.get_image(image_id) elif cls._api_version == 3: resp, image = cls.images_client.get_image_meta(image_id) if kwargs['wait_until'] == 'ACTIVE': if kwargs.get('wait_for_server', True): cls.servers_client.wait_for_server_status(server_id, 'ACTIVE') return resp, image @classmethod def rebuild_server(cls, server_id, **kwargs): # Destroy an existing server and creates a new one if server_id: try: cls.servers_client.delete_server(server_id) cls.servers_client.wait_for_server_termination(server_id) except Exception: LOG.exception('Failed to delete server %s' % server_id) resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs) if cls._api_version == 2: cls.password = server['adminPass'] elif cls._api_version == 3: cls.password = server['admin_password'] return server['id'] @classmethod def delete_volume(cls, volume_id): """Deletes the given volume and waits for it to be gone.""" if cls._api_version == 2: cls._delete_volume(cls.volumes_extensions_client, volume_id) elif cls._api_version == 3: cls._delete_volume(cls.volumes_client, volume_id) class BaseV2ComputeTest(BaseComputeTest): _api_version = 2 _interface = "json" class BaseV2ComputeAdminTest(BaseV2ComputeTest): """Base test case class for Compute Admin V2 API tests.""" @classmethod def setUpClass(cls): super(BaseV2ComputeAdminTest, cls).setUpClass() if (CONF.compute.allow_tenant_isolation or cls.force_tenant_isolation is True): creds = cls.isolated_creds.get_admin_creds() cls.os_adm = clients.Manager(credentials=creds, interface=cls._interface) else: try: cls.os_adm = clients.ComputeAdminManager( interface=cls._interface) except exceptions.InvalidCredentials: msg = ("Missing Compute Admin API credentials " "in configuration.") raise cls.skipException(msg) class BaseV3ComputeTest(BaseComputeTest): _api_version = 3 _interface = "json" class BaseV3ComputeAdminTest(BaseV3ComputeTest): """Base test case class for all Compute Admin API V3 tests.""" @classmethod def setUpClass(cls): super(BaseV3ComputeAdminTest, cls).setUpClass() if CONF.compute.allow_tenant_isolation: creds = cls.isolated_creds.get_admin_creds() os_adm = clients.Manager(credentials=creds, interface=cls._interface) else: try: cls.os_adm = clients.ComputeAdminManager( interface=cls._interface) except exceptions.InvalidCredentials: msg = ("Missing Compute Admin API credentials " "in configuration.") raise cls.skipException(msg) cls.os_adm = os_adm cls.servers_admin_client = cls.os_adm.servers_v3_client cls.services_admin_client = cls.os_adm.services_v3_client cls.availability_zone_admin_client = \ cls.os_adm.availability_zone_v3_client cls.hypervisor_admin_client = cls.os_adm.hypervisor_v3_client cls.flavors_admin_client = cls.os_adm.flavors_v3_client cls.aggregates_admin_client = cls.os_adm.aggregates_v3_client cls.hosts_admin_client = cls.os_adm.hosts_v3_client cls.quotas_admin_client = cls.os_adm.quotas_v3_client cls.agents_admin_client = cls.os_adm.agents_v3_client cls.migrations_admin_client = cls.os_adm.migrations_v3_client tempest-2014.1.dev4108.gf22b6cc/tempest/cli/0000775000175000017500000000000012332757136020330 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/0000775000175000017500000000000012332757136023655 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_nova.py0000664000175000017500000001403412332757070026230 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import subprocess import testtools import tempest.cli from tempest import config from tempest.openstack.common import log as logging import tempest.test CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyNovaClientTest(tempest.cli.ClientTestBase): """ This is a first pass at a simple read only python-novaclient test. This only exercises client commands that are read only. This should test commands: * as a regular user * as a admin user * with and without optional parameters * initially just check return codes, and later test command outputs """ @classmethod def setUpClass(cls): if not CONF.service_available.nova: msg = ("%s skipped as Nova is not available" % cls.__name__) raise cls.skipException(msg) super(SimpleReadOnlyNovaClientTest, cls).setUpClass() def test_admin_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.nova, 'this-does-nova-exist') # NOTE(jogo): Commands in order listed in 'nova help' # Positional arguments: def test_admin_absolute_limites(self): self.nova('absolute-limits') self.nova('absolute-limits', params='--reserved') def test_admin_aggregate_list(self): self.nova('aggregate-list') def test_admin_availability_zone_list(self): self.assertIn("internal", self.nova('availability-zone-list')) def test_admin_cloudpipe_list(self): self.nova('cloudpipe-list') def test_admin_credentials(self): self.nova('credentials') @testtools.skipIf(CONF.service_available.neutron, "Neutron does not provide this feature") def test_admin_dns_domains(self): self.nova('dns-domains') @tempest.test.skip_because(bug="1157349") def test_admin_dns_list(self): self.nova('dns-list') def test_admin_endpoints(self): self.nova('endpoints') def test_admin_flavor_acces_list(self): self.assertRaises(subprocess.CalledProcessError, self.nova, 'flavor-access-list') # Failed to get access list for public flavor type self.assertRaises(subprocess.CalledProcessError, self.nova, 'flavor-access-list', params='--flavor m1.tiny') def test_admin_flavor_list(self): self.assertIn("Memory_MB", self.nova('flavor-list')) def test_admin_floating_ip_bulk_list(self): self.nova('floating-ip-bulk-list') def test_admin_floating_ip_list(self): self.nova('floating-ip-list') def test_admin_floating_ip_pool_list(self): self.nova('floating-ip-pool-list') def test_admin_host_list(self): self.nova('host-list') def test_admin_hypervisor_list(self): self.nova('hypervisor-list') def test_admin_image_list(self): self.nova('image-list') @tempest.test.skip_because(bug="1157349") def test_admin_interface_list(self): self.nova('interface-list') def test_admin_keypair_list(self): self.nova('keypair-list') def test_admin_list(self): self.nova('list') self.nova('list', params='--all-tenants 1') self.nova('list', params='--all-tenants 0') self.assertRaises(subprocess.CalledProcessError, self.nova, 'list', params='--all-tenants bad') def test_admin_network_list(self): self.nova('network-list') def test_admin_rate_limits(self): self.nova('rate-limits') def test_admin_secgroup_list(self): self.nova('secgroup-list') @tempest.test.skip_because(bug="1157349") def test_admin_secgroup_list_rules(self): self.nova('secgroup-list-rules') def test_admin_servce_list(self): self.nova('service-list') def test_admin_usage(self): self.nova('usage') def test_admin_usage_list(self): self.nova('usage-list') @testtools.skipIf(not CONF.service_available.cinder, "Skipped as Cinder is not available") def test_admin_volume_list(self): self.nova('volume-list') @testtools.skipIf(not CONF.service_available.cinder, "Skipped as Cinder is not available") def test_admin_volume_snapshot_list(self): self.nova('volume-snapshot-list') @testtools.skipIf(not CONF.service_available.cinder, "Skipped as Cinder is not available") def test_admin_volume_type_list(self): self.nova('volume-type-list') def test_admin_help(self): self.nova('help') def test_admin_list_extensions(self): self.nova('list-extensions') def test_admin_net_list(self): self.nova('net-list') def test_agent_list(self): self.nova('agent-list') self.nova('agent-list', flags='--debug') def test_migration_list(self): self.nova('migration-list') self.nova('migration-list', flags='--debug') # Optional arguments: def test_admin_version(self): self.nova('', flags='--version') def test_admin_debug_list(self): self.nova('list', flags='--debug') def test_admin_timeout(self): self.nova('list', flags='--timeout %d' % CONF.cli.timeout) def test_admin_timing(self): self.nova('list', flags='--timing') tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_nova_manage.py0000664000175000017500000000645512332757070027550 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import subprocess import tempest.cli from tempest import config from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyNovaManageTest(tempest.cli.ClientTestBase): """ This is a first pass at a simple read only nova-manage test. This only exercises client commands that are read only. This should test commands: * with and without optional parameters * initially just check return codes, and later test command outputs """ @classmethod def setUpClass(cls): if not CONF.service_available.nova: msg = ("%s skipped as Nova is not available" % cls.__name__) raise cls.skipException(msg) if not CONF.cli.has_manage: msg = ("%s skipped as *-manage commands not available" % cls.__name__) raise cls.skipException(msg) super(SimpleReadOnlyNovaManageTest, cls).setUpClass() def test_admin_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.nova_manage, 'this-does-nova-exist') # NOTE(jogo): Commands in order listed in 'nova-manage -h' # test flags def test_help_flag(self): self.nova_manage('', '-h') def test_version_flag(self): # Bug 1159957: nova-manage --version writes to stderr self.assertNotEqual("", self.nova_manage('', '--version', merge_stderr=True)) self.assertEqual(self.nova_manage('version'), self.nova_manage('', '--version', merge_stderr=True)) def test_debug_flag(self): self.assertNotEqual("", self.nova_manage('flavor list', '--debug')) def test_verbose_flag(self): self.assertNotEqual("", self.nova_manage('flavor list', '--verbose')) # test actions def test_version(self): self.assertNotEqual("", self.nova_manage('version')) def test_flavor_list(self): self.assertNotEqual("", self.nova_manage('flavor list')) def test_db_archive_deleted_rows(self): # make sure command doesn't error out self.nova_manage('db archive_deleted_rows 50') def test_db_sync(self): # make sure command doesn't error out self.nova_manage('db sync') def test_db_version(self): self.assertNotEqual("", self.nova_manage('db version')) def test_cell_list(self): # make sure command doesn't error out self.nova_manage('cell list') def test_host_list(self): # make sure command doesn't error out self.nova_manage('host list') tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/README.txt0000664000175000017500000000010112332757070025340 0ustar chuckchuck00000000000000This directory consists of simple read only python client tests. tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_cinder.py0000664000175000017500000001272112332757070026532 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import re import subprocess import testtools import tempest.cli from tempest import config CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyCinderClientTest(tempest.cli.ClientTestBase): """Basic, read-only tests for Cinder CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ @classmethod def setUpClass(cls): if not CONF.service_available.cinder: msg = ("%s skipped as Cinder is not available" % cls.__name__) raise cls.skipException(msg) super(SimpleReadOnlyCinderClientTest, cls).setUpClass() def test_cinder_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.cinder, 'this-does-not-exist') def test_cinder_absolute_limit_list(self): roles = self.parser.listing(self.cinder('absolute-limits')) self.assertTableStruct(roles, ['Name', 'Value']) def test_cinder_backup_list(self): self.cinder('backup-list') def test_cinder_extra_specs_list(self): self.cinder('extra-specs-list') def test_cinder_volumes_list(self): self.cinder('list') self.cinder('list', params='--all-tenants 1') self.cinder('list', params='--all-tenants 0') self.assertRaises(subprocess.CalledProcessError, self.cinder, 'list', params='--all-tenants bad') def test_cinder_quota_class_show(self): """This CLI can accept and string as param.""" roles = self.parser.listing(self.cinder('quota-class-show', params='abc')) self.assertTableStruct(roles, ['Property', 'Value']) def test_cinder_quota_defaults(self): """This CLI can accept and string as param.""" roles = self.parser.listing(self.cinder('quota-defaults', params=CONF.identity. admin_tenant_name)) self.assertTableStruct(roles, ['Property', 'Value']) def test_cinder_quota_show(self): """This CLI can accept and string as param.""" roles = self.parser.listing(self.cinder('quota-show', params=CONF.identity. admin_tenant_name)) self.assertTableStruct(roles, ['Property', 'Value']) def test_cinder_rate_limits(self): self.cinder('rate-limits') @testtools.skipUnless(CONF.volume_feature_enabled.snapshot, 'Volume snapshot not available.') def test_cinder_snapshot_list(self): self.cinder('snapshot-list') def test_cinder_type_list(self): self.cinder('type-list') def test_cinder_list_extensions(self): self.cinder('list-extensions') roles = self.parser.listing(self.cinder('list-extensions')) self.assertTableStruct(roles, ['Name', 'Summary', 'Alias', 'Updated']) def test_cinder_credentials(self): self.cinder('credentials') def test_cinder_availability_zone_list(self): self.cinder('availability-zone-list') def test_cinder_endpoints(self): self.cinder('endpoints') def test_cinder_service_list(self): self.cinder('service-list') def test_cinder_transfer_list(self): self.cinder('transfer-list') def test_cinder_bash_completion(self): self.cinder('bash-completion') def test_admin_help(self): help_text = self.cinder('help') lines = help_text.split('\n') self.assertFirstLineStartsWith(lines, 'usage: cinder') commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: commands.append(match.group(1)) commands = set(commands) wanted_commands = set(('absolute-limits', 'list', 'help', 'quota-show', 'type-list', 'snapshot-list')) self.assertFalse(wanted_commands - commands) # Optional arguments: def test_cinder_version(self): self.cinder('', flags='--version') def test_cinder_debug_list(self): self.cinder('list', flags='--debug') def test_cinder_retries_list(self): self.cinder('list', flags='--retries 3') def test_cinder_region_list(self): region = CONF.volume.region if not region: region = CONF.identity.region self.cinder('list', flags='--os-region-name ' + region) tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/heat_templates/0000775000175000017500000000000012332757136026654 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/heat_templates/heat_minimal_hot.yaml0000664000175000017500000000110612332757070033034 0ustar chuckchuck00000000000000heat_template_version: 2013-05-23 description: A minimal HOT test template parameters: instance_image: description: Glance image name type: string instance_type: description: Nova instance type type: string default: m1.small constraints: - allowed_values: [m1.small, m1.medium, m1.large] description: instance_type must be one of m1.small, m1.medium or m1.large resources: instance: type: OS::Nova::Server properties: image: { get_param: instance_image } flavor: { get_param: instance_type } tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/heat_templates/heat_minimal.yaml0000664000175000017500000000107612332757070032170 0ustar chuckchuck00000000000000HeatTemplateFormatVersion: '2012-12-12' Description: Minimal template to test validation Parameters: InstanceImage: Description: Glance image name Type: String InstanceType: Description: Nova instance type Type: String Default: m1.small AllowedValues: [m1.tiny, m1.small, m1.medium, m1.large, m1.nano, m1.xlarge, m1.micro] ConstraintDescription: must be a valid nova instance type. Resources: InstanceResource: Type: OS::Nova::Server Properties: flavor: {Ref: InstanceType} image: {Ref: InstanceImage} tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/__init__.py0000664000175000017500000000000012332757070025751 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_keystone.py0000664000175000017500000001153412332757070027130 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import subprocess import tempest.cli from tempest import config from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyKeystoneClientTest(tempest.cli.ClientTestBase): """Basic, read-only tests for Keystone CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ def test_admin_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.keystone, 'this-does-not-exist') def test_admin_catalog_list(self): out = self.keystone('catalog') catalog = self.parser.details_multiple(out, with_label=True) for svc in catalog: if svc.get('__label'): self.assertTrue(svc['__label'].startswith('Service:'), msg=('Invalid beginning of service block: ' '%s' % svc['__label'])) # check that region and publicURL exists. One might also # check for adminURL and internalURL. id seems to be optional # and is missing in the catalog backend self.assertIn('publicURL', svc.keys()) self.assertIn('region', svc.keys()) def test_admin_endpoint_list(self): out = self.keystone('endpoint-list') endpoints = self.parser.listing(out) self.assertTableStruct(endpoints, [ 'id', 'region', 'publicurl', 'internalurl', 'adminurl', 'service_id']) def test_admin_endpoint_service_match(self): endpoints = self.parser.listing(self.keystone('endpoint-list')) services = self.parser.listing(self.keystone('service-list')) svc_by_id = {} for svc in services: svc_by_id[svc['id']] = svc for endpoint in endpoints: self.assertIn(endpoint['service_id'], svc_by_id) def test_admin_role_list(self): roles = self.parser.listing(self.keystone('role-list')) self.assertTableStruct(roles, ['id', 'name']) def test_admin_service_list(self): services = self.parser.listing(self.keystone('service-list')) self.assertTableStruct(services, ['id', 'name', 'type', 'description']) def test_admin_tenant_list(self): tenants = self.parser.listing(self.keystone('tenant-list')) self.assertTableStruct(tenants, ['id', 'name', 'enabled']) def test_admin_user_list(self): users = self.parser.listing(self.keystone('user-list')) self.assertTableStruct(users, [ 'id', 'name', 'enabled', 'email']) def test_admin_user_role_list(self): user_roles = self.parser.listing(self.keystone('user-role-list')) self.assertTableStruct(user_roles, [ 'id', 'name', 'user_id', 'tenant_id']) def test_admin_discover(self): discovered = self.keystone('discover') self.assertIn('Keystone found at http', discovered) self.assertIn('supports version', discovered) def test_admin_help(self): help_text = self.keystone('help') lines = help_text.split('\n') self.assertFirstLineStartsWith(lines, 'usage: keystone') commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: commands.append(match.group(1)) commands = set(commands) wanted_commands = set(('catalog', 'endpoint-list', 'help', 'token-get', 'discover', 'bootstrap')) self.assertFalse(wanted_commands - commands) def test_admin_bashcompletion(self): self.keystone('bash-completion') # Optional arguments: def test_admin_version(self): self.keystone('', flags='--version') def test_admin_debug_list(self): self.keystone('catalog', flags='--debug') def test_admin_timeout(self): self.keystone('catalog', flags='--timeout %d' % CONF.cli.timeout) tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_glance.py0000664000175000017500000000652712332757070026526 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import subprocess import tempest.cli from tempest import config from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyGlanceClientTest(tempest.cli.ClientTestBase): """Basic, read-only tests for Glance CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ @classmethod def setUpClass(cls): if not CONF.service_available.glance: msg = ("%s skipped as Glance is not available" % cls.__name__) raise cls.skipException(msg) super(SimpleReadOnlyGlanceClientTest, cls).setUpClass() def test_glance_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.glance, 'this-does-not-exist') def test_glance_image_list(self): out = self.glance('image-list') endpoints = self.parser.listing(out) self.assertTableStruct(endpoints, [ 'ID', 'Name', 'Disk Format', 'Container Format', 'Size', 'Status']) def test_glance_member_list(self): tenant_name = '--tenant-id %s' % CONF.identity.admin_tenant_name out = self.glance('member-list', params=tenant_name) endpoints = self.parser.listing(out) self.assertTableStruct(endpoints, ['Image ID', 'Member ID', 'Can Share']) def test_glance_help(self): help_text = self.glance('help') lines = help_text.split('\n') self.assertFirstLineStartsWith(lines, 'usage: glance') commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: commands.append(match.group(1)) commands = set(commands) wanted_commands = set(('image-create', 'image-delete', 'help', 'image-download', 'image-show', 'image-update', 'member-add', 'member-create', 'member-delete', 'member-list')) self.assertFalse(wanted_commands - commands) # Optional arguments: def test_glance_version(self): self.glance('', flags='--version') def test_glance_debug_list(self): self.glance('image-list', flags='--debug') def test_glance_timeout(self): self.glance('image-list', flags='--timeout %d' % CONF.cli.timeout) tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_sahara.py0000664000175000017500000001033412332757070026523 0ustar chuckchuck00000000000000# Copyright (c) 2013 Mirantis 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 logging import subprocess from tempest import cli from tempest import config from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlySaharaClientTest(cli.ClientTestBase): """Basic, read-only tests for Sahara CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ @classmethod def setUpClass(cls): if not CONF.service_available.sahara: msg = "Skipping all Sahara cli tests because it is not available" raise cls.skipException(msg) super(SimpleReadOnlySaharaClientTest, cls).setUpClass() @test.attr(type='negative') def test_sahara_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.sahara, 'this-does-not-exist') def test_sahara_plugins_list(self): plugins = self.parser.listing(self.sahara('plugin-list')) self.assertTableStruct(plugins, [ 'name', 'versions', 'title' ]) def test_sahara_plugins_show(self): result = self.sahara('plugin-show', params='--name vanilla') plugin = self.parser.listing(result) self.assertTableStruct(plugin, [ 'Property', 'Value' ]) def test_sahara_node_group_template_list(self): result = self.sahara('node-group-template-list') node_group_templates = self.parser.listing(result) self.assertTableStruct(node_group_templates, [ 'name', 'id', 'plugin_name', 'node_processes', 'description' ]) def test_sahara_cluster_template_list(self): result = self.sahara('cluster-template-list') cluster_templates = self.parser.listing(result) self.assertTableStruct(cluster_templates, [ 'name', 'id', 'plugin_name', 'node_groups', 'description' ]) def test_sahara_cluster_list(self): result = self.sahara('cluster-list') clusters = self.parser.listing(result) self.assertTableStruct(clusters, [ 'name', 'id', 'status', 'node_count' ]) def test_sahara_data_source_list(self): result = self.sahara('data-source-list') data_sources = self.parser.listing(result) self.assertTableStruct(data_sources, [ 'name', 'id', 'type', 'description' ]) def test_sahara_job_binary_data_list(self): result = self.sahara('job-binary-data-list') job_binary_data_list = self.parser.listing(result) self.assertTableStruct(job_binary_data_list, [ 'id', 'name' ]) def test_sahara_job_binary_list(self): result = self.sahara('job-binary-list') job_binaries = self.parser.listing(result) self.assertTableStruct(job_binaries, [ 'id', 'name', 'description' ]) def test_sahara_job_template_list(self): result = self.sahara('job-template-list') job_templates = self.parser.listing(result) self.assertTableStruct(job_templates, [ 'id', 'name', 'description' ]) def test_sahara_job_list(self): result = self.sahara('job-list') jobs = self.parser.listing(result) self.assertTableStruct(jobs, [ 'id', 'cluster_id', 'status' ]) tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_ceilometer.py0000664000175000017500000000343212332757070027415 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest import cli from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyCeilometerClientTest(cli.ClientTestBase): """Basic, read-only tests for Ceilometer CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ @classmethod def setUpClass(cls): if (not CONF.service_available.ceilometer): msg = ("Skipping all Ceilometer cli tests because it is " "not available") raise cls.skipException(msg) super(SimpleReadOnlyCeilometerClientTest, cls).setUpClass() def test_ceilometer_meter_list(self): self.ceilometer('meter-list') @test.attr(type='slow') def test_ceilometer_resource_list(self): self.ceilometer('resource-list') def test_ceilometermeter_alarm_list(self): self.ceilometer('alarm-list') def test_ceilometer_version(self): self.ceilometer('', flags='--version') tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_heat.py0000664000175000017500000000712512332757070026211 0ustar chuckchuck00000000000000# 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 json import os import yaml import tempest.cli from tempest import config from tempest.openstack.common import log as logging CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyHeatClientTest(tempest.cli.ClientTestBase): """Basic, read-only tests for Heat CLI client. Basic smoke test for the heat CLI commands which do not require creating or modifying stacks. """ @classmethod def setUpClass(cls): if (not CONF.service_available.heat): msg = ("Skipping all Heat cli tests because it is " "not available") raise cls.skipException(msg) super(SimpleReadOnlyHeatClientTest, cls).setUpClass() def test_heat_stack_list(self): self.heat('stack-list') def test_heat_stack_list_debug(self): self.heat('stack-list', flags='--debug') def test_heat_resource_template_fmt_default(self): ret = self.heat('resource-template OS::Nova::Server') self.assertIn('Type: OS::Nova::Server', ret) def test_heat_resource_template_fmt_arg_short_yaml(self): ret = self.heat('resource-template -F yaml OS::Nova::Server') self.assertIn('Type: OS::Nova::Server', ret) self.assertIsInstance(yaml.safe_load(ret), dict) def test_heat_resource_template_fmt_arg_long_json(self): ret = self.heat('resource-template --format json OS::Nova::Server') self.assertIn('"Type": "OS::Nova::Server",', ret) self.assertIsInstance(json.loads(ret), dict) def test_heat_resource_type_list(self): ret = self.heat('resource-type-list') rsrc_types = self.parser.listing(ret) self.assertTableStruct(rsrc_types, ['resource_type']) def test_heat_resource_type_show(self): rsrc_schema = self.heat('resource-type-show OS::Nova::Server') # resource-type-show returns a json resource schema self.assertIsInstance(json.loads(rsrc_schema), dict) def test_heat_template_validate_yaml(self): filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'heat_templates/heat_minimal.yaml') ret = self.heat('template-validate -f %s' % filepath) # On success template-validate returns a json representation # of the template parameters self.assertIsInstance(json.loads(ret), dict) def test_heat_template_validate_hot(self): filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'heat_templates/heat_minimal_hot.yaml') ret = self.heat('template-validate -f %s' % filepath) self.assertIsInstance(json.loads(ret), dict) def test_heat_help(self): self.heat('help') def test_heat_help_cmd(self): # Check requesting help for a specific command works help_text = self.heat('help resource-template') lines = help_text.split('\n') self.assertFirstLineStartsWith(lines, 'usage: heat resource-template') def test_heat_version(self): self.heat('', flags='--version') tempest-2014.1.dev4108.gf22b6cc/tempest/cli/simple_read_only/test_neutron.py0000664000175000017500000001423212332757070026757 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import subprocess from tempest import cli from tempest import config from tempest.openstack.common import log as logging from tempest import test CONF = config.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyNeutronClientTest(cli.ClientTestBase): """Basic, read-only tests for Neutron CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ @classmethod def setUpClass(cls): if (not CONF.service_available.neutron): msg = "Skipping all Neutron cli tests because it is not available" raise cls.skipException(msg) super(SimpleReadOnlyNeutronClientTest, cls).setUpClass() @test.attr(type='smoke') def test_neutron_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.neutron, 'this-does-not-exist') @test.attr(type='smoke') def test_neutron_net_list(self): self.neutron('net-list') @test.attr(type='smoke') def test_neutron_ext_list(self): ext = self.parser.listing(self.neutron('ext-list')) self.assertTableStruct(ext, ['alias', 'name']) @test.attr(type='smoke') @test.requires_ext(extension='dhcp_agent_scheduler', service='network') def test_neutron_dhcp_agent_list_hosting_net(self): self.neutron('dhcp-agent-list-hosting-net', params=CONF.compute.fixed_network_name) @test.attr(type='smoke') @test.requires_ext(extension='agent', service='network') def test_neutron_agent_list(self): agents = self.parser.listing(self.neutron('agent-list')) field_names = ['id', 'agent_type', 'host', 'alive', 'admin_state_up'] self.assertTableStruct(agents, field_names) @test.attr(type='smoke') @test.requires_ext(extension='router', service='network') def test_neutron_floatingip_list(self): self.neutron('floatingip-list') @test.attr(type='smoke') @test.requires_ext(extension='metering', service='network') def test_neutron_meter_label_list(self): self.neutron('meter-label-list') @test.attr(type='smoke') @test.requires_ext(extension='metering', service='network') def test_neutron_meter_label_rule_list(self): self.neutron('meter-label-rule-list') @test.requires_ext(extension='lbaas_agent_scheduler', service='network') def _test_neutron_lbaas_command(self, command): try: self.neutron(command) except cli.CommandFailed as e: if '404 Not Found' not in e.stderr: self.fail('%s: Unexpected failure.' % command) @test.attr(type='smoke') def test_neutron_lb_healthmonitor_list(self): self._test_neutron_lbaas_command('lb-healthmonitor-list') @test.attr(type='smoke') def test_neutron_lb_member_list(self): self._test_neutron_lbaas_command('lb-member-list') @test.attr(type='smoke') def test_neutron_lb_pool_list(self): self._test_neutron_lbaas_command('lb-pool-list') @test.attr(type='smoke') def test_neutron_lb_vip_list(self): self._test_neutron_lbaas_command('lb-vip-list') @test.attr(type='smoke') @test.requires_ext(extension='external-net', service='network') def test_neutron_net_external_list(self): self.neutron('net-external-list') @test.attr(type='smoke') def test_neutron_port_list(self): self.neutron('port-list') @test.attr(type='smoke') @test.requires_ext(extension='quotas', service='network') def test_neutron_quota_list(self): self.neutron('quota-list') @test.attr(type='smoke') @test.requires_ext(extension='router', service='network') def test_neutron_router_list(self): self.neutron('router-list') @test.attr(type='smoke') @test.requires_ext(extension='security-group', service='network') def test_neutron_security_group_list(self): security_grp = self.parser.listing(self.neutron('security-group-list')) self.assertTableStruct(security_grp, ['id', 'name', 'description']) @test.attr(type='smoke') @test.requires_ext(extension='security-group', service='network') def test_neutron_security_group_rule_list(self): self.neutron('security-group-rule-list') @test.attr(type='smoke') def test_neutron_subnet_list(self): self.neutron('subnet-list') @test.attr(type='smoke') def test_neutron_help(self): help_text = self.neutron('help') lines = help_text.split('\n') self.assertFirstLineStartsWith(lines, 'usage: neutron') commands = [] cmds_start = lines.index('Commands for API v2.0:') command_pattern = re.compile('^ {2}([a-z0-9\-\_]+)') for line in lines[cmds_start:]: match = command_pattern.match(line) if match: commands.append(match.group(1)) commands = set(commands) wanted_commands = set(('net-create', 'subnet-list', 'port-delete', 'router-show', 'agent-update', 'help')) self.assertFalse(wanted_commands - commands) # Optional arguments: @test.attr(type='smoke') def test_neutron_version(self): self.neutron('', flags='--version') @test.attr(type='smoke') def test_neutron_debug_net_list(self): self.neutron('net-list', flags='--debug') @test.attr(type='smoke') def test_neutron_quiet_net_list(self): self.neutron('net-list', flags='--quiet') tempest-2014.1.dev4108.gf22b6cc/tempest/cli/__init__.py0000664000175000017500000001406612332757070022445 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import shlex import subprocess import tempest.cli.output_parser from tempest import config from tempest.openstack.common import log as logging import tempest.test LOG = logging.getLogger(__name__) CONF = config.CONF class ClientTestBase(tempest.test.BaseTestCase): @classmethod def setUpClass(cls): if not CONF.cli.enabled: msg = "cli testing disabled" raise cls.skipException(msg) super(ClientTestBase, cls).setUpClass() def __init__(self, *args, **kwargs): self.parser = tempest.cli.output_parser super(ClientTestBase, self).__init__(*args, **kwargs) def nova(self, action, flags='', params='', admin=True, fail_ok=False): """Executes nova command for the given action.""" flags += ' --endpoint-type %s' % CONF.compute.endpoint_type return self.cmd_with_auth( 'nova', action, flags, params, admin, fail_ok) def nova_manage(self, action, flags='', params='', fail_ok=False, merge_stderr=False): """Executes nova-manage command for the given action.""" return self.cmd( 'nova-manage', action, flags, params, fail_ok, merge_stderr) def keystone(self, action, flags='', params='', admin=True, fail_ok=False): """Executes keystone command for the given action.""" return self.cmd_with_auth( 'keystone', action, flags, params, admin, fail_ok) def glance(self, action, flags='', params='', admin=True, fail_ok=False): """Executes glance command for the given action.""" flags += ' --os-endpoint-type %s' % CONF.image.endpoint_type return self.cmd_with_auth( 'glance', action, flags, params, admin, fail_ok) def ceilometer(self, action, flags='', params='', admin=True, fail_ok=False): """Executes ceilometer command for the given action.""" flags += ' --os-endpoint-type %s' % CONF.telemetry.endpoint_type return self.cmd_with_auth( 'ceilometer', action, flags, params, admin, fail_ok) def heat(self, action, flags='', params='', admin=True, fail_ok=False): """Executes heat command for the given action.""" flags += ' --os-endpoint-type %s' % CONF.orchestration.endpoint_type return self.cmd_with_auth( 'heat', action, flags, params, admin, fail_ok) def cinder(self, action, flags='', params='', admin=True, fail_ok=False): """Executes cinder command for the given action.""" flags += ' --endpoint-type %s' % CONF.volume.endpoint_type return self.cmd_with_auth( 'cinder', action, flags, params, admin, fail_ok) def neutron(self, action, flags='', params='', admin=True, fail_ok=False): """Executes neutron command for the given action.""" flags += ' --endpoint-type %s' % CONF.network.endpoint_type return self.cmd_with_auth( 'neutron', action, flags, params, admin, fail_ok) def sahara(self, action, flags='', params='', admin=True, fail_ok=False): """Executes sahara command for the given action.""" flags += ' --endpoint-type %s' % CONF.data_processing.endpoint_type return self.cmd_with_auth( 'sahara', action, flags, params, admin, fail_ok) def cmd_with_auth(self, cmd, action, flags='', params='', admin=True, fail_ok=False): """Executes given command with auth attributes appended.""" # TODO(jogo) make admin=False work creds = ('--os-username %s --os-tenant-name %s --os-password %s ' '--os-auth-url %s' % (CONF.identity.admin_username, CONF.identity.admin_tenant_name, CONF.identity.admin_password, CONF.identity.uri)) flags = creds + ' ' + flags return self.cmd(cmd, action, flags, params, fail_ok) def cmd(self, cmd, action, flags='', params='', fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd), flags, action, params]) LOG.info("running: '%s'" % cmd) cmd = shlex.split(cmd) result = '' result_err = '' stdout = subprocess.PIPE stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE proc = subprocess.Popen( cmd, stdout=stdout, stderr=stderr) result, result_err = proc.communicate() if not fail_ok and proc.returncode != 0: raise CommandFailed(proc.returncode, cmd, result, stderr=result_err) return result def assertTableStruct(self, items, field_names): """Verify that all items has keys listed in field_names.""" for item in items: for field in field_names: self.assertIn(field, item) def assertFirstLineStartsWith(self, lines, beginning): self.assertTrue(lines[0].startswith(beginning), msg=('Beginning of first line has invalid content: %s' % lines[:3])) class CommandFailed(subprocess.CalledProcessError): # adds output attribute for python2.6 def __init__(self, returncode, cmd, output, stderr=""): super(CommandFailed, self).__init__(returncode, cmd) self.output = output self.stderr = stderr tempest-2014.1.dev4108.gf22b6cc/tempest/cli/output_parser.py0000664000175000017500000001134012332757070023612 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Collection of utilities for parsing CLI clients output.""" import re from tempest import exceptions from tempest.openstack.common import log as logging LOG = logging.getLogger(__name__) delimiter_line = re.compile('^\+\-[\+\-]+\-\+$') def details_multiple(output_lines, with_label=False): """Return list of dicts with item details from cli output tables. If with_label is True, key '__label' is added to each items dict. For more about 'label' see OutputParser.tables(). """ items = [] tables_ = tables(output_lines) for table_ in tables_: if 'Property' not in table_['headers'] \ or 'Value' not in table_['headers']: raise exceptions.InvalidStructure() item = {} for value in table_['values']: item[value[0]] = value[1] if with_label: item['__label'] = table_['label'] items.append(item) return items def details(output_lines, with_label=False): """Return dict with details of first item (table) found in output.""" items = details_multiple(output_lines, with_label) return items[0] def listing(output_lines): """Return list of dicts with basic item info parsed from cli output. """ items = [] table_ = table(output_lines) for row in table_['values']: item = {} for col_idx, col_key in enumerate(table_['headers']): item[col_key] = row[col_idx] items.append(item) return items def tables(output_lines): """Find all ascii-tables in output and parse them. Return list of tables parsed from cli output as dicts. (see OutputParser.table()) And, if found, label key (separated line preceding the table) is added to each tables dict. """ tables_ = [] table_ = [] label = None start = False header = False if not isinstance(output_lines, list): output_lines = output_lines.split('\n') for line in output_lines: if delimiter_line.match(line): if not start: start = True elif not header: # we are after head area header = True else: # table ends here start = header = None table_.append(line) parsed = table(table_) parsed['label'] = label tables_.append(parsed) table_ = [] label = None continue if start: table_.append(line) else: if label is None: label = line else: LOG.warn('Invalid line between tables: %s' % line) if len(table_) > 0: LOG.warn('Missing end of table') return tables_ def table(output_lines): """Parse single table from cli output. Return dict with list of column names in 'headers' key and rows in 'values' key. """ table_ = {'headers': [], 'values': []} columns = None if not isinstance(output_lines, list): output_lines = output_lines.split('\n') if not output_lines[-1]: # skip last line if empty (just newline at the end) output_lines = output_lines[:-1] for line in output_lines: if delimiter_line.match(line): columns = _table_columns(line) continue if '|' not in line: LOG.warn('skipping invalid table line: %s' % line) continue row = [] for col in columns: row.append(line[col[0]:col[1]].strip()) if table_['headers']: table_['values'].append(row) else: table_['headers'] = row return table_ def _table_columns(first_table_row): """Find column ranges in output line. Return list of tuples (start,end) for each column detected by plus (+) characters in delimiter line. """ positions = [] start = 1 # there is '+' at 0 while start < len(first_table_row): end = first_table_row.find('+', start) if end == -1: break positions.append((start, end)) start = end + 1 return positions tempest-2014.1.dev4108.gf22b6cc/tempest/cli/README.rst0000664000175000017500000000305412332757070022016 0ustar chuckchuck00000000000000Tempest Field Guide to CLI tests ================================ What are these tests? --------------------- The cli tests test the various OpenStack command line interface tools to ensure that they minimally function. The current scope is read only operations on a cloud that are hard to test via unit tests. Why are these tests in tempest? ------------------------------- These tests exist here because it is extremely difficult to build a functional enough environment in the python-\*client unit tests to provide this kind of testing. Because we already put up a cloud in the gate with devstack + tempest it was decided it was better to have these as a side tree in tempest instead of another QA effort which would split review time. Scope of these tests -------------------- This should stay limited to the scope of testing the cli. Functional testing of the cloud should be elsewhere, this is about exercising the cli code. Example of a good test ---------------------- Tests should be isolated to a single command in one of the python clients. Tests should not modify the cloud. If a test is validating the cli for bad data, it should do it with assertRaises. A reasonable example of an existing test is as follows:: def test_admin_list(self): self.nova('list') self.nova('list', params='--all-tenants 1') self.nova('list', params='--all-tenants 0') self.assertRaises(subprocess.CalledProcessError, self.nova, 'list', params='--all-tenants bad') tempest-2014.1.dev4108.gf22b6cc/setup.cfg0000664000175000017500000000145312332757136017724 0ustar chuckchuck00000000000000[metadata] name = tempest version = 2014.1 summary = OpenStack Integration Testing description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 [entry_points] console_scripts = verify-tempest-config = tempest.cmd.verify_tempest_config:main [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 tempest-2014.1.dev4108.gf22b6cc/tools/0000775000175000017500000000000012332757136017240 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tools/subunit-trace.py0000775000175000017500000001677212332757070022414 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2014 Hewlett-Packard Development Company, L.P. # Copyright 2014 Samsung Electronics # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Trace a subunit stream in reasonable detail and high accuracy.""" import functools import sys import mimeparse import subunit import testtools DAY_SECONDS = 60 * 60 * 24 FAILS = [] RESULTS = {} class Starts(testtools.StreamResult): def __init__(self, output): super(Starts, self).__init__() self._output = output def startTestRun(self): self._neednewline = False self._emitted = set() def status(self, test_id=None, test_status=None, test_tags=None, runnable=True, file_name=None, file_bytes=None, eof=False, mime_type=None, route_code=None, timestamp=None): super(Starts, self).status( test_id, test_status, test_tags=test_tags, runnable=runnable, file_name=file_name, file_bytes=file_bytes, eof=eof, mime_type=mime_type, route_code=route_code, timestamp=timestamp) if not test_id: if not file_bytes: return if not mime_type or mime_type == 'test/plain;charset=utf8': mime_type = 'text/plain; charset=utf-8' primary, sub, parameters = mimeparse.parse_mime_type(mime_type) content_type = testtools.content_type.ContentType( primary, sub, parameters) content = testtools.content.Content( content_type, lambda: [file_bytes]) text = content.as_text() if text and text[-1] not in '\r\n': self._neednewline = True self._output.write(text) elif test_status == 'inprogress' and test_id not in self._emitted: if self._neednewline: self._neednewline = False self._output.write('\n') worker = '' for tag in test_tags or (): if tag.startswith('worker-'): worker = '(' + tag[7:] + ') ' if timestamp: timestr = timestamp.isoformat() else: timestr = '' self._output.write('%s: %s%s [start]\n' % (timestr, worker, test_id)) self._emitted.add(test_id) def cleanup_test_name(name, strip_tags=True, strip_scenarios=False): """Clean up the test name for display. By default we strip out the tags in the test because they don't help us in identifying the test that is run to it's result. Make it possible to strip out the testscenarios information (not to be confused with tempest scenarios) however that's often needed to indentify generated negative tests. """ if strip_tags: tags_start = name.find('[') tags_end = name.find(']') if tags_start > 0 and tags_end > tags_start: newname = name[:tags_start] newname += name[tags_end + 1:] name = newname if strip_scenarios: tags_start = name.find('(') tags_end = name.find(')') if tags_start > 0 and tags_end > tags_start: newname = name[:tags_start] newname += name[tags_end + 1:] name = newname return name def get_duration(timestamps): start, end = timestamps if not start or not end: duration = '' else: delta = end - start duration = '%d.%06ds' % ( delta.days * DAY_SECONDS + delta.seconds, delta.microseconds) return duration def find_worker(test): for tag in test['tags']: if tag.startswith('worker-'): return int(tag[7:]) return 'NaN' # Print out stdout/stderr if it exists, always def print_attachments(stream, test, all_channels=False): """Print out subunit attachments. Print out subunit attachments that contain content. This runs in 2 modes, one for successes where we print out just stdout and stderr, and an override that dumps all the attachments. """ channels = ('stdout', 'stderr') for name, detail in test['details'].items(): # NOTE(sdague): the subunit names are a little crazy, and actually # are in the form pythonlogging:'' (with the colon and quotes) name = name.split(':')[0] if detail.content_type.type == 'test': detail.content_type.type = 'text' if (all_channels or name in channels) and detail.as_text(): title = "Captured %s:" % name stream.write("\n%s\n%s\n" % (title, ('~' * len(title)))) # indent attachment lines 4 spaces to make them visually # offset for line in detail.as_text().split('\n'): stream.write(" %s\n" % line) def show_outcome(stream, test): global RESULTS status = test['status'] # TODO(sdague): ask lifeless why on this? if status == 'exists': return worker = find_worker(test) name = cleanup_test_name(test['id']) duration = get_duration(test['timestamps']) if worker not in RESULTS: RESULTS[worker] = [] RESULTS[worker].append(test) # don't count the end of the return code as a fail if name == 'process-returncode': return if status == 'success': stream.write('{%s} %s [%s] ... ok\n' % ( worker, name, duration)) print_attachments(stream, test) elif status == 'fail': FAILS.append(test) stream.write('{%s} %s [%s] ... FAILED\n' % ( worker, name, duration)) print_attachments(stream, test, all_channels=True) elif status == 'skip': stream.write('{%s} %s ... SKIPPED: %s\n' % ( worker, name, test['details']['reason'].as_text())) else: stream.write('{%s} %s [%s] ... %s\n' % ( worker, name, duration, test['status'])) print_attachments(stream, test, all_channels=True) stream.flush() def print_fails(stream): """Print summary failure report. Currently unused, however there remains debate on inline vs. at end reporting, so leave the utility function for later use. """ if not FAILS: return stream.write("\n==============================\n") stream.write("Failed %s tests - output below:" % len(FAILS)) stream.write("\n==============================\n") for f in FAILS: stream.write("\n%s\n" % f['id']) stream.write("%s\n" % ('-' * len(f['id']))) print_attachments(stream, f, all_channels=True) stream.write('\n') def main(): stream = subunit.ByteStreamToStreamResult( sys.stdin, non_subunit_name='stdout') starts = Starts(sys.stdout) outcomes = testtools.StreamToDict( functools.partial(show_outcome, sys.stdout)) summary = testtools.StreamSummary() result = testtools.CopyStreamResult([starts, outcomes, summary]) result.startTestRun() try: stream.run(result) finally: result.stopTestRun() return (0 if summary.wasSuccessful() else 1) if __name__ == '__main__': sys.exit(main()) tempest-2014.1.dev4108.gf22b6cc/tools/pretty_tox.sh0000775000175000017500000000023012332757070022010 0ustar chuckchuck00000000000000#!/usr/bin/env bash set -o pipefail TESTRARGS=$1 python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py tempest-2014.1.dev4108.gf22b6cc/tools/with_venv.sh0000775000175000017500000000012612332757070021604 0ustar chuckchuck00000000000000#!/bin/bash TOOLS=`dirname $0` VENV=$TOOLS/../.venv source $VENV/bin/activate && "$@" tempest-2014.1.dev4108.gf22b6cc/tools/colorizer.py0000775000175000017500000002715312332757070021632 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright (c) 2013, Nebula, Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Colorizer Code is borrowed from Twisted: # Copyright (c) 2001-2010 Twisted Matrix Laboratories. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """Display a subunit stream through a colorized unittest test runner.""" import heapq import subunit import sys import unittest import testtools class _AnsiColorizer(object): """ A colorizer is an object that loosely wraps around a stream, allowing callers to write text to the stream in a particular color. Colorizer classes must implement C{supported()} and C{write(text, color)}. """ _colors = dict(black=30, red=31, green=32, yellow=33, blue=34, magenta=35, cyan=36, white=37) def __init__(self, stream): self.stream = stream def supported(cls, stream=sys.stdout): """ A class method that returns True if the current platform supports coloring terminal output using this method. Returns False otherwise. """ if not stream.isatty(): return False # auto color only on TTYs try: import curses except ImportError: return False else: try: try: return curses.tigetnum("colors") > 2 except curses.error: curses.setupterm() return curses.tigetnum("colors") > 2 except Exception: # guess false in case of error return False supported = classmethod(supported) def write(self, text, color): """ Write the given text to the stream in the given color. @param text: Text to be written to the stream. @param color: A string label for a color. e.g. 'red', 'white'. """ color = self._colors[color] self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) class _Win32Colorizer(object): """ See _AnsiColorizer docstring. """ def __init__(self, stream): import win32console red, green, blue, bold = (win32console.FOREGROUND_RED, win32console.FOREGROUND_GREEN, win32console.FOREGROUND_BLUE, win32console.FOREGROUND_INTENSITY) self.stream = stream self.screenBuffer = win32console.GetStdHandle( win32console.STD_OUT_HANDLE) self._colors = {'normal': red | green | blue, 'red': red | bold, 'green': green | bold, 'blue': blue | bold, 'yellow': red | green | bold, 'magenta': red | blue | bold, 'cyan': green | blue | bold, 'white': red | green | blue | bold} def supported(cls, stream=sys.stdout): try: import win32console screenBuffer = win32console.GetStdHandle( win32console.STD_OUT_HANDLE) except ImportError: return False import pywintypes try: screenBuffer.SetConsoleTextAttribute( win32console.FOREGROUND_RED | win32console.FOREGROUND_GREEN | win32console.FOREGROUND_BLUE) except pywintypes.error: return False else: return True supported = classmethod(supported) def write(self, text, color): color = self._colors[color] self.screenBuffer.SetConsoleTextAttribute(color) self.stream.write(text) self.screenBuffer.SetConsoleTextAttribute(self._colors['normal']) class _NullColorizer(object): """ See _AnsiColorizer docstring. """ def __init__(self, stream): self.stream = stream def supported(cls, stream=sys.stdout): return True supported = classmethod(supported) def write(self, text, color): self.stream.write(text) def get_elapsed_time_color(elapsed_time): if elapsed_time > 1.0: return 'red' elif elapsed_time > 0.25: return 'yellow' else: return 'green' class NovaTestResult(testtools.TestResult): def __init__(self, stream, descriptions, verbosity): super(NovaTestResult, self).__init__() self.stream = stream self.showAll = verbosity > 1 self.num_slow_tests = 10 self.slow_tests = [] # this is a fixed-sized heap self.colorizer = None # NOTE(vish): reset stdout for the terminal check stdout = sys.stdout sys.stdout = sys.__stdout__ for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: if colorizer.supported(): self.colorizer = colorizer(self.stream) break sys.stdout = stdout self.start_time = None self.last_time = {} self.results = {} self.last_written = None def _writeElapsedTime(self, elapsed): color = get_elapsed_time_color(elapsed) self.colorizer.write(" %.2f" % elapsed, color) def _addResult(self, test, *args): try: name = test.id() except AttributeError: name = 'Unknown.unknown' test_class, test_name = name.rsplit('.', 1) elapsed = (self._now() - self.start_time).total_seconds() item = (elapsed, test_class, test_name) if len(self.slow_tests) >= self.num_slow_tests: heapq.heappushpop(self.slow_tests, item) else: heapq.heappush(self.slow_tests, item) self.results.setdefault(test_class, []) self.results[test_class].append((test_name, elapsed) + args) self.last_time[test_class] = self._now() self.writeTests() def _writeResult(self, test_name, elapsed, long_result, color, short_result, success): if self.showAll: self.stream.write(' %s' % str(test_name).ljust(66)) self.colorizer.write(long_result, color) if success: self._writeElapsedTime(elapsed) self.stream.writeln() else: self.colorizer.write(short_result, color) def addSuccess(self, test): super(NovaTestResult, self).addSuccess(test) self._addResult(test, 'OK', 'green', '.', True) def addFailure(self, test, err): if test.id() == 'process-returncode': return super(NovaTestResult, self).addFailure(test, err) self._addResult(test, 'FAIL', 'red', 'F', False) def addError(self, test, err): super(NovaTestResult, self).addFailure(test, err) self._addResult(test, 'ERROR', 'red', 'E', False) def addSkip(self, test, reason=None, details=None): super(NovaTestResult, self).addSkip(test, reason, details) self._addResult(test, 'SKIP', 'blue', 'S', True) def startTest(self, test): self.start_time = self._now() super(NovaTestResult, self).startTest(test) def writeTestCase(self, cls): if not self.results.get(cls): return if cls != self.last_written: self.colorizer.write(cls, 'white') self.stream.writeln() for result in self.results[cls]: self._writeResult(*result) del self.results[cls] self.stream.flush() self.last_written = cls def writeTests(self): time = self.last_time.get(self.last_written, self._now()) if not self.last_written or (self._now() - time).total_seconds() > 2.0: diff = 3.0 while diff > 2.0: classes = self.results.keys() oldest = min(classes, key=lambda x: self.last_time[x]) diff = (self._now() - self.last_time[oldest]).total_seconds() self.writeTestCase(oldest) else: self.writeTestCase(self.last_written) def done(self): self.stopTestRun() def stopTestRun(self): for cls in list(self.results.iterkeys()): self.writeTestCase(cls) self.stream.writeln() self.writeSlowTests() def writeSlowTests(self): # Pare out 'fast' tests slow_tests = [item for item in self.slow_tests if get_elapsed_time_color(item[0]) != 'green'] if slow_tests: slow_total_time = sum(item[0] for item in slow_tests) slow = ("Slowest %i tests took %.2f secs:" % (len(slow_tests), slow_total_time)) self.colorizer.write(slow, 'yellow') self.stream.writeln() last_cls = None # sort by name for elapsed, cls, name in sorted(slow_tests, key=lambda x: x[1] + x[2]): if cls != last_cls: self.colorizer.write(cls, 'white') self.stream.writeln() last_cls = cls self.stream.write(' %s' % str(name).ljust(68)) self._writeElapsedTime(elapsed) self.stream.writeln() def printErrors(self): if self.showAll: self.stream.writeln() self.printErrorList('ERROR', self.errors) self.printErrorList('FAIL', self.failures) def printErrorList(self, flavor, errors): for test, err in errors: self.colorizer.write("=" * 70, 'red') self.stream.writeln() self.colorizer.write(flavor, 'red') self.stream.writeln(": %s" % test.id()) self.colorizer.write("-" * 70, 'red') self.stream.writeln() self.stream.writeln("%s" % err) test = subunit.ProtocolTestCase(sys.stdin, passthrough=None) if sys.version_info[0:2] <= (2, 6): runner = unittest.TextTestRunner(verbosity=2) else: runner = unittest.TextTestRunner(verbosity=2, resultclass=NovaTestResult) if runner.run(test).wasSuccessful(): exit_code = 0 else: exit_code = 1 sys.exit(exit_code) tempest-2014.1.dev4108.gf22b6cc/tools/generate_sample.sh0000775000175000017500000000013712332757070022730 0ustar chuckchuck00000000000000#!/bin/sh MODULEPATH=tempest.common.generate_sample_tempest tools/config/generate_sample.sh $@ tempest-2014.1.dev4108.gf22b6cc/tools/tempest_auto_config.py0000664000175000017500000003536112332757070023655 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This script aims to configure an initial OpenStack environment with all the # necessary configurations for tempest's run using nothing but OpenStack's # native API. # That includes, creating users, tenants, registering images (cirros), # configuring neutron and so on. # # ASSUMPTION: this script is run by an admin user as it is meant to configure # the OpenStack environment prior to actual use. # Config import ConfigParser import os import tarfile import urllib2 # Default client libs import glanceclient as glance_client import keystoneclient.v2_0.client as keystone_client # Import OpenStack exceptions import glanceclient.exc as glance_exception import keystoneclient.exceptions as keystone_exception TEMPEST_TEMP_DIR = os.getenv("TEMPEST_TEMP_DIR", "/tmp").rstrip('/') TEMPEST_ROOT_DIR = os.getenv("TEMPEST_ROOT_DIR", os.getenv("HOME")).rstrip('/') # Environment variables override defaults TEMPEST_CONFIG_DIR = os.getenv("TEMPEST_CONFIG_DIR", "%s%s" % (TEMPEST_ROOT_DIR, "/etc")).rstrip('/') TEMPEST_CONFIG_FILE = os.getenv("TEMPEST_CONFIG_FILE", "%s%s" % (TEMPEST_CONFIG_DIR, "/tempest.conf")) TEMPEST_CONFIG_SAMPLE = os.getenv("TEMPEST_CONFIG_SAMPLE", "%s%s" % (TEMPEST_CONFIG_DIR, "/tempest.conf.sample")) # Image references IMAGE_DOWNLOAD_CHUNK_SIZE = 8 * 1024 IMAGE_UEC_SOURCE_URL = os.getenv("IMAGE_UEC_SOURCE_URL", "http://download.cirros-cloud.net/0.3.1/" "cirros-0.3.1-x86_64-uec.tar.gz") TEMPEST_IMAGE_ID = os.getenv('IMAGE_ID') TEMPEST_IMAGE_ID_ALT = os.getenv('IMAGE_ID_ALT') IMAGE_STATUS_ACTIVE = 'active' class ClientManager(object): """ Manager that provides access to the official python clients for calling various OpenStack APIs. """ def __init__(self): self.identity_client = None self.image_client = None self.network_client = None self.compute_client = None self.volume_client = None def get_identity_client(self, **kwargs): """ Returns the openstack identity python client :param username: a string representing the username :param password: a string representing the user's password :param tenant_name: a string representing the tenant name of the user :param auth_url: a string representing the auth url of the identity :param insecure: True if we wish to disable ssl certificate validation, False otherwise :returns an instance of openstack identity python client """ if not self.identity_client: self.identity_client = keystone_client.Client(**kwargs) return self.identity_client def get_image_client(self, version="1", *args, **kwargs): """ This method returns OpenStack glance python client :param version: a string representing the version of the glance client to use. :param string endpoint: A user-supplied endpoint URL for the glance service. :param string token: Token for authentication. :param integer timeout: Allows customization of the timeout for client http requests. (optional) :return: a Client object representing the glance client """ if not self.image_client: self.image_client = glance_client.Client(version, *args, **kwargs) return self.image_client def get_tempest_config(path_to_config): """ Gets the tempest configuration file as a ConfigParser object :param path_to_config: path to the config file :return: a ConfigParser object representing the tempest configuration file """ # get the sample config file from the sample config = ConfigParser.ConfigParser() config.readfp(open(path_to_config)) return config def update_config_admin_credentials(config, config_section): """ Updates the tempest config with the admin credentials :param config: a ConfigParser object representing the tempest config file :param config_section: the section name where the admin credentials are """ # Check if credentials are present, default uses the config credentials OS_USERNAME = os.getenv('OS_USERNAME', config.get(config_section, "admin_username")) OS_PASSWORD = os.getenv('OS_PASSWORD', config.get(config_section, "admin_password")) OS_TENANT_NAME = os.getenv('OS_TENANT_NAME', config.get(config_section, "admin_tenant_name")) OS_AUTH_URL = os.getenv('OS_AUTH_URL', config.get(config_section, "uri")) if not (OS_AUTH_URL and OS_USERNAME and OS_PASSWORD and OS_TENANT_NAME): raise Exception("Admin environment variables not found.") # TODO(tkammer): Add support for uri_v3 config_identity_params = {'uri': OS_AUTH_URL, 'admin_username': OS_USERNAME, 'admin_password': OS_PASSWORD, 'admin_tenant_name': OS_TENANT_NAME} update_config_section_with_params(config, config_section, config_identity_params) def update_config_section_with_params(config, config_section, params): """ Updates a given config object with given params :param config: a ConfigParser object representing the tempest config file :param config_section: the section we would like to update :param params: the parameters we wish to update for that section """ for option, value in params.items(): config.set(config_section, option, value) def get_identity_client_kwargs(config, config_section): """ Get the required arguments for the identity python client :param config: a ConfigParser object representing the tempest config file :param config_section: the section name in the configuration where the arguments can be found :return: a dictionary representing the needed arguments for the identity client """ username = config.get(config_section, 'admin_username') password = config.get(config_section, 'admin_password') tenant_name = config.get(config_section, 'admin_tenant_name') auth_url = config.get(config_section, 'uri') dscv = config.get(config_section, 'disable_ssl_certificate_validation') kwargs = {'username': username, 'password': password, 'tenant_name': tenant_name, 'auth_url': auth_url, 'insecure': dscv} return kwargs def create_user_with_tenant(identity_client, username, password, tenant_name): """ Creates a user using a given identity client :param identity_client: openstack identity python client :param username: a string representing the username :param password: a string representing the user's password :param tenant_name: a string representing the tenant name of the user """ # Try to create the necessary tenant tenant_id = None try: tenant_description = "Tenant for Tempest %s user" % username tenant = identity_client.tenants.create(tenant_name, tenant_description) tenant_id = tenant.id except keystone_exception.Conflict: # if already exist, use existing tenant tenant_list = identity_client.tenants.list() for tenant in tenant_list: if tenant.name == tenant_name: tenant_id = tenant.id # Try to create the user try: email = "%s@test.com" % username identity_client.users.create(name=username, password=password, email=email, tenant_id=tenant_id) except keystone_exception.Conflict: # if already exist, use existing user pass def create_users_and_tenants(identity_client, config, config_section): """ Creates the two non admin users and tenants for tempest :param identity_client: openstack identity python client :param config: a ConfigParser object representing the tempest config file :param config_section: the section name of identity in the config """ # Get the necessary params from the config file tenant_name = config.get(config_section, 'tenant_name') username = config.get(config_section, 'username') password = config.get(config_section, 'password') alt_tenant_name = config.get(config_section, 'alt_tenant_name') alt_username = config.get(config_section, 'alt_username') alt_password = config.get(config_section, 'alt_password') # Create the necessary users for the test runs create_user_with_tenant(identity_client, username, password, tenant_name) create_user_with_tenant(identity_client, alt_username, alt_password, alt_tenant_name) def get_image_client_kwargs(identity_client, config, config_section): """ Get the required arguments for the image python client :param identity_client: openstack identity python client :param config: a ConfigParser object representing the tempest config file :param config_section: the section name of identity in the config :return: a dictionary representing the needed arguments for the image client """ token = identity_client.auth_token endpoint = identity_client.\ service_catalog.url_for(service_type='image', endpoint_type='publicURL' ) dscv = config.get(config_section, 'disable_ssl_certificate_validation') kwargs = {'endpoint': endpoint, 'token': token, 'insecure': dscv} return kwargs def images_exist(image_client): """ Checks whether the images ID's located in the environment variable are indeed registered :param image_client: the openstack python client representing the image client """ exist = True if not TEMPEST_IMAGE_ID or not TEMPEST_IMAGE_ID_ALT: exist = False else: try: image_client.images.get(TEMPEST_IMAGE_ID) image_client.images.get(TEMPEST_IMAGE_ID_ALT) except glance_exception.HTTPNotFound: exist = False return exist def download_and_register_uec_images(image_client, download_url, download_folder): """ Downloads and registered the UEC AKI/AMI/ARI images :param image_client: :param download_url: the url of the uec tar file :param download_folder: the destination folder we wish to save the file to """ basename = os.path.basename(download_url) path = os.path.join(download_folder, basename) request = urllib2.urlopen(download_url) # First, download the file with open(path, "wb") as fp: while True: chunk = request.read(IMAGE_DOWNLOAD_CHUNK_SIZE) if not chunk: break fp.write(chunk) # Then extract and register images tar = tarfile.open(path, "r") for name in tar.getnames(): file_obj = tar.extractfile(name) format = "aki" if file_obj.name.endswith(".img"): format = "ami" if file_obj.name.endswith("initrd"): format = "ari" # Register images in image client image_client.images.create(name=file_obj.name, disk_format=format, container_format=format, data=file_obj, is_public="true") tar.close() def create_images(image_client, config, config_section, download_url=IMAGE_UEC_SOURCE_URL, download_folder=TEMPEST_TEMP_DIR): """ Creates images for tempest's use and registers the environment variables IMAGE_ID and IMAGE_ID_ALT with registered images :param image_client: OpenStack python image client :param config: a ConfigParser object representing the tempest config file :param config_section: the section name where the IMAGE ids are set :param download_url: the URL from which we should download the UEC tar :param download_folder: the place where we want to save the download file """ if not images_exist(image_client): # Falls down to the default uec images download_and_register_uec_images(image_client, download_url, download_folder) image_ids = [] for image in image_client.images.list(): image_ids.append(image.id) os.environ["IMAGE_ID"] = image_ids[0] os.environ["IMAGE_ID_ALT"] = image_ids[1] params = {'image_ref': os.getenv("IMAGE_ID"), 'image_ref_alt': os.getenv("IMAGE_ID_ALT")} update_config_section_with_params(config, config_section, params) def main(): """ Main module to control the script """ # Check if config file exists or fall to the default sample otherwise path_to_config = TEMPEST_CONFIG_SAMPLE if os.path.isfile(TEMPEST_CONFIG_FILE): path_to_config = TEMPEST_CONFIG_FILE config = get_tempest_config(path_to_config) update_config_admin_credentials(config, 'identity') client_manager = ClientManager() # Set the identity related info for tempest identity_client_kwargs = get_identity_client_kwargs(config, 'identity') identity_client = client_manager.get_identity_client( **identity_client_kwargs) # Create the necessary users and tenants for tempest run create_users_and_tenants(identity_client, config, 'identity') # Set the image related info for tempest image_client_kwargs = get_image_client_kwargs(identity_client, config, 'identity') image_client = client_manager.get_image_client(**image_client_kwargs) # Create the necessary users and tenants for tempest run create_images(image_client, config, 'compute') # TODO(tkammer): add network implementation if __name__ == "__main__": main() tempest-2014.1.dev4108.gf22b6cc/tools/config/0000775000175000017500000000000012332757136020505 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tools/config/generate_sample.sh0000775000175000017500000000654112332757070024202 0ustar chuckchuck00000000000000#!/usr/bin/env bash print_hint() { echo "Try \`${0##*/} --help' for more information." >&2 } PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:m:l:o: \ --long help,base-dir:,package-name:,output-dir:,module:,library: -- "$@") if [ $? != 0 ] ; then print_hint ; exit 1 ; fi eval set -- "$PARSED_OPTIONS" while true; do case "$1" in -h|--help) echo "${0##*/} [options]" echo "" echo "options:" echo "-h, --help show brief help" echo "-b, --base-dir=DIR project base directory" echo "-p, --package-name=NAME project package name" echo "-o, --output-dir=DIR file output directory" echo "-m, --module=MOD extra python module to interrogate for options" echo "-l, --library=LIB extra library that registers options for discovery" exit 0 ;; -b|--base-dir) shift BASEDIR=`echo $1 | sed -e 's/\/*$//g'` shift ;; -p|--package-name) shift PACKAGENAME=`echo $1` shift ;; -o|--output-dir) shift OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'` shift ;; -m|--module) shift MODULES="$MODULES -m $1" shift ;; -l|--library) shift LIBRARIES="$LIBRARIES -l $1" shift ;; --) break ;; esac done BASEDIR=${BASEDIR:-`pwd`} if ! [ -d $BASEDIR ] then echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1 elif [[ $BASEDIR != /* ]] then BASEDIR=$(cd "$BASEDIR" && pwd) fi PACKAGENAME=${PACKAGENAME:-$(python setup.py --name)} TARGETDIR=$BASEDIR/$PACKAGENAME if ! [ -d $TARGETDIR ] then echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1 fi OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc} # NOTE(bnemec): Some projects put their sample config in etc/, # some in etc/$PACKAGENAME/ if [ -d $OUTPUTDIR/$PACKAGENAME ] then OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME elif ! [ -d $OUTPUTDIR ] then echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2 exit 1 fi BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'` find $TARGETDIR -type f -name "*.pyc" -delete FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \ -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u) RC_FILE="`dirname $0`/oslo.config.generator.rc" if test -r "$RC_FILE" then source "$RC_FILE" fi for mod in ${TEMPEST_CONFIG_GENERATOR_EXTRA_MODULES}; do MODULES="$MODULES -m $mod" done for lib in ${TEMPEST_CONFIG_GENERATOR_EXTRA_LIBRARIES}; do LIBRARIES="$LIBRARIES -l $lib" done export EVENTLET_NO_GREENDNS=yes OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs) [ "$OS_VARS" ] && eval "unset \$OS_VARS" DEFAULT_MODULEPATH=tempest.openstack.common.config.generator MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH} OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample python -m $MODULEPATH $MODULES $LIBRARIES $FILES > $OUTPUTFILE # Hook to allow projects to append custom config file snippets CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null) for CONCAT_FILE in $CONCAT_FILES; do cat $CONCAT_FILE >> $OUTPUTFILE done tempest-2014.1.dev4108.gf22b6cc/tools/config/check_uptodate.sh0000775000175000017500000000125712332757070024030 0ustar chuckchuck00000000000000#!/usr/bin/env bash PROJECT_NAME=${PROJECT_NAME:-tempest} CFGFILE_NAME=${PROJECT_NAME}.conf.sample if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME} elif [ -e etc/${CFGFILE_NAME} ]; then CFGFILE=etc/${CFGFILE_NAME} else echo "${0##*/}: can not find config file" exit 1 fi TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX` trap "rm -rf $TEMPDIR" EXIT tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR} if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE} then echo "${0##*/}: ${PROJECT_NAME}.conf.sample is not up to date." echo "${0##*/}: Please run ${0%%${0##*/}}generate_sample.sh." exit 1 fi tempest-2014.1.dev4108.gf22b6cc/tools/install_venv_common.py0000664000175000017500000001346512332757070023674 0ustar chuckchuck00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 IBM Corp. # # 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. """Provides methods needed by installation script for OpenStack development virtual environments. Since this script is used to bootstrap a virtualenv from the system's Python environment, it should be kept strictly compatible with Python 2.6. Synced in from openstack-common """ from __future__ import print_function import optparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...', end=' ') if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # setuptools and pbr self.pip_install('pip>=1.4') self.pip_install('setuptools') self.pip_install('pbr') self.pip_install('-r', self.requirements, '-r', self.test_requirements) def parse_args(self, argv): """Parses command-line arguments.""" parser = optparse.OptionParser() parser.add_option('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install") return parser.parse_args(argv[1:])[0] class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...', end=' ') if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() tempest-2014.1.dev4108.gf22b6cc/tools/pretty_tox_serial.sh0000775000175000017500000000031512332757070023353 0ustar chuckchuck00000000000000#!/usr/bin/env bash set -o pipefail TESTRARGS=$@ if [ ! -d .testrepository ]; then testr init fi testr run --subunit $TESTRARGS | $(dirname $0)/subunit-trace.py retval=$? testr slowest exit $retval tempest-2014.1.dev4108.gf22b6cc/tools/install_venv.py0000664000175000017500000000453712332757070022324 0ustar chuckchuck00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2010 OpenStack Foundation # Copyright 2013 IBM Corp. # # 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 sys import install_venv_common as install_venv # noqa def print_help(venv, root): help = """ OpenStack development environment setup is complete. OpenStack development uses virtualenv to track and manage Python dependencies while in development and testing. To activate the OpenStack virtualenv for the extent of your current shell session you can run: $ source %s/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ %s/tools/with_venv.sh Also, make test will automatically use the virtualenv. """ print(help % (venv, root)) def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if os.environ.get('tools_path'): root = os.environ['tools_path'] venv = os.path.join(root, '.venv') if os.environ.get('venv'): venv = os.environ['venv'] pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) project = 'Tempest' install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, py_version, project) options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() print_help(venv, root) if __name__ == '__main__': main(sys.argv) tempest-2014.1.dev4108.gf22b6cc/tools/skip_tracker.py0000775000175000017500000001201412332757070022271 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Track test skips via launchpadlib API and raise alerts if a bug is fixed but a skip is still in the Tempest test code """ import logging import os import re from launchpadlib import launchpad BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) TESTDIR = os.path.join(BASEDIR, 'tempest') LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache') def info(msg, *args, **kwargs): logging.info(msg, *args, **kwargs) def debug(msg, *args, **kwargs): logging.debug(msg, *args, **kwargs) def find_skips(start=TESTDIR): """ Returns a list of tuples (method, bug) that represent test methods that have been decorated to skip because of a particular bug. """ results = {} debug("Searching in %s", start) for root, _dirs, files in os.walk(start): for name in files: if name.startswith('test_') and name.endswith('py'): path = os.path.join(root, name) debug("Searching in %s", path) temp_result = find_skips_in_file(path) for method_name, bug_no in temp_result: if results.get(bug_no): result_dict = results.get(bug_no) if result_dict.get(name): result_dict[name].append(method_name) else: result_dict[name] = [method_name] results[bug_no] = result_dict else: results[bug_no] = {name: [method_name]} return results def find_skips_in_file(path): """ Return the skip tuples in a test file """ BUG_RE = re.compile(r'\s*@.*skip_because\(bug=[\'"](\d+)[\'"]') DEF_RE = re.compile(r'\s*def (\w+)\(') bug_found = False results = [] lines = open(path, 'rb').readlines() for x, line in enumerate(lines): if not bug_found: res = BUG_RE.match(line) if res: bug_no = int(res.group(1)) debug("Found bug skip %s on line %d", bug_no, x + 1) bug_found = True else: res = DEF_RE.match(line) if res: method = res.group(1) debug("Found test method %s skips for bug %d", method, bug_no) results.append((method, bug_no)) bug_found = False return results def get_results(result_dict): results = [] for bug_no in result_dict.keys(): for method in result_dict[bug_no]: results.append((method, bug_no)) return results if __name__ == '__main__': logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) results = find_skips() unique_bugs = sorted(set([bug for (method, bug) in get_results(results)])) unskips = [] duplicates = [] info("Total bug skips found: %d", len(results)) info("Total unique bugs causing skips: %d", len(unique_bugs)) lp = launchpad.Launchpad.login_anonymously('grabbing bugs', 'production', LPCACHEDIR) for bug_no in unique_bugs: bug = lp.bugs[bug_no] duplicate = bug.duplicate_of_link if duplicate is not None: dup_id = duplicate.split('/')[-1] duplicates.append((bug_no, dup_id)) for task in bug.bug_tasks: info("Bug #%7s (%12s - %12s)", bug_no, task.importance, task.status) if task.status in ('Fix Released', 'Fix Committed'): unskips.append(bug_no) for bug_id, dup_id in duplicates: if bug_id not in unskips: dup_bug = lp.bugs[dup_id] for task in dup_bug.bug_tasks: info("Bug #%7s is a duplicate of Bug#%7s (%12s - %12s)", bug_id, dup_id, task.importance, task.status) if task.status in ('Fix Released', 'Fix Committed'): unskips.append(bug_id) unskips = sorted(set(unskips)) if unskips: print("The following bugs have been fixed and the corresponding skips") print("should be removed from the test cases:") print() for bug in unskips: message = " %7s in " % bug locations = ["%s" % x for x in results[bug].keys()] message += " and ".join(locations) print(message) tempest-2014.1.dev4108.gf22b6cc/tools/find_stack_traces.py0000775000175000017500000001014512332757070023261 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import gzip import re import StringIO import sys import urllib2 import pprint pp = pprint.PrettyPrinter() NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d" NOVA_REGEX = r"(?P%s) (?P\d+ )?(?P(ERROR|TRACE)) " \ "(?P[\w\.]+) (?P.*)" % (NOVA_TIMESTAMP) class StackTrace(object): timestamp = None pid = None level = "" module = "" msg = "" def __init__(self, timestamp=None, pid=None, level="", module="", msg=""): self.timestamp = timestamp self.pid = pid self.level = level self.module = module self.msg = msg def append(self, msg): self.msg = self.msg + msg def is_same(self, data): return (data['timestamp'] == self.timestamp and data['level'] == self.level) def not_none(self): return self.timestamp is not None def __str__(self): buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module) for line in self.msg.splitlines(): buff = buff + line + "\n" return buff def hunt_for_stacktrace(url): """Return TRACE or ERROR lines out of logs.""" req = urllib2.Request(url) req.add_header('Accept-Encoding', 'gzip') page = urllib2.urlopen(req) buf = StringIO.StringIO(page.read()) f = gzip.GzipFile(fileobj=buf) content = f.read() traces = [] trace = StackTrace() for line in content.splitlines(): m = re.match(NOVA_REGEX, line) if m: data = m.groupdict() if trace.not_none() and trace.is_same(data): trace.append(data['msg'] + "\n") else: trace = StackTrace( timestamp=data.get('timestamp'), pid=data.get('pid'), level=data.get('level'), module=data.get('module'), msg=data.get('msg')) else: if trace.not_none(): traces.append(trace) trace = StackTrace() # once more at the end to pick up any stragglers if trace.not_none(): traces.append(trace) return traces def log_url(url, log): return "%s/%s" % (url, log) def collect_logs(url): page = urllib2.urlopen(url) content = page.read() logs = re.findall('(screen-[\w-]+\.txt\.gz)', content) return logs def usage(): print(""" Usage: find_stack_traces.py Hunts for stack traces in a devstack run. Must provide it a base log url from a tempest devstack run. Should start with http and end with /logs/. Returns a report listing stack traces out of the various files where they are found. """) sys.exit(0) def print_stats(items, fname, verbose=False): errors = len(filter(lambda x: x.level == "ERROR", items)) traces = len(filter(lambda x: x.level == "TRACE", items)) print("%d ERRORS found in %s" % (errors, fname)) print("%d TRACES found in %s" % (traces, fname)) if verbose: for item in items: print(item) print("\n\n") def main(): if len(sys.argv) == 2: url = sys.argv[1] loglist = collect_logs(url) # probably wrong base url if not loglist: usage() for log in loglist: logurl = log_url(url, log) traces = hunt_for_stacktrace(logurl) if traces: print_stats(traces, log, verbose=True) else: usage() if __name__ == '__main__': main() tempest-2014.1.dev4108.gf22b6cc/tools/check_logs.py0000775000175000017500000001414012332757070021713 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2013 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import gzip import os import re import StringIO import sys import urllib2 import yaml is_grenade = (os.environ.get('DEVSTACK_GATE_GRENADE', "0") == "1" or os.environ.get('DEVSTACK_GATE_GRENADE_FORWARD', "0") == "1") dump_all_errors = True # As logs are made clean, add to this set allowed_dirty = set([ 'c-api', 'ceilometer-acentral', 'ceilometer-acompute', 'ceilometer-alarm-evaluator', 'ceilometer-anotification', 'ceilometer-api', 'ceilometer-collector', 'c-vol', 'g-api', 'h-api', 'h-eng', 'ir-cond', 'n-api', 'n-cpu', 'n-net', 'q-agt', 'q-dhcp', 'q-lbaas', 'q-meta', 'q-metering', 'q-svc', 'q-vpn', 's-proxy']) def process_files(file_specs, url_specs, whitelists): regexp = re.compile(r"^.* (ERROR|CRITICAL|TRACE) .*\[.*\-.*\]") logs_with_errors = [] for (name, filename) in file_specs: whitelist = whitelists.get(name, []) with open(filename) as content: if scan_content(name, content, regexp, whitelist): logs_with_errors.append(name) for (name, url) in url_specs: whitelist = whitelists.get(name, []) req = urllib2.Request(url) req.add_header('Accept-Encoding', 'gzip') page = urllib2.urlopen(req) buf = StringIO.StringIO(page.read()) f = gzip.GzipFile(fileobj=buf) if scan_content(name, f.read().splitlines(), regexp, whitelist): logs_with_errors.append(name) return logs_with_errors def scan_content(name, content, regexp, whitelist): had_errors = False print_log_name = True for line in content: if not line.startswith("Stderr:") and regexp.match(line): whitelisted = False for w in whitelist: pat = ".*%s.*%s.*" % (w['module'].replace('.', '\\.'), w['message']) if re.match(pat, line): whitelisted = True break if not whitelisted or dump_all_errors: if print_log_name: print("\nLog File Has Errors: %s" % name) print_log_name = False if not whitelisted: had_errors = True print("*** Not Whitelisted ***"), print(line.rstrip()) return had_errors def collect_url_logs(url): page = urllib2.urlopen(url) content = page.read() logs = re.findall('(screen-[\w-]+\.txt\.gz)', content) return logs def main(opts): if opts.directory and opts.url or not (opts.directory or opts.url): print("Must provide exactly one of -d or -u") exit(1) print("Checking logs...") WHITELIST_FILE = os.path.join( os.path.abspath(os.path.dirname(os.path.dirname(__file__))), "etc", "whitelist.yaml") file_matcher = re.compile(r".*screen-([\w-]+)\.log") files = [] if opts.directory: d = opts.directory for f in os.listdir(d): files.append(os.path.join(d, f)) files_to_process = [] for f in files: m = file_matcher.match(f) if m: files_to_process.append((m.group(1), f)) url_matcher = re.compile(r".*screen-([\w-]+)\.txt\.gz") urls = [] if opts.url: for logfile in collect_url_logs(opts.url): urls.append("%s/%s" % (opts.url, logfile)) urls_to_process = [] for u in urls: m = url_matcher.match(u) if m: urls_to_process.append((m.group(1), u)) whitelists = {} with open(WHITELIST_FILE) as stream: loaded = yaml.safe_load(stream) if loaded: for (name, l) in loaded.iteritems(): for w in l: assert 'module' in w, 'no module in %s' % name assert 'message' in w, 'no message in %s' % name whitelists = loaded logs_with_errors = process_files(files_to_process, urls_to_process, whitelists) if logs_with_errors: print("Logs have errors") if is_grenade: print("Currently not failing grenade runs with errors") return 0 failed = False for log in logs_with_errors: if log not in allowed_dirty: print("Log: %s not allowed to have ERRORS or TRACES" % log) failed = True if failed: return 1 print("ok") return 0 usage = """ Find non-white-listed log errors in log files from a devstack-gate run. Log files will be searched for ERROR or CRITICAL messages. If any error messages do not match any of the whitelist entries contained in etc/whitelist.yaml, those messages will be printed to the console and failure will be returned. A file directory containing logs or a url to the log files of an OpenStack gate job can be provided. The whitelist yaml looks like: log-name: - module: "a.b.c" message: "regexp" - module: "a.b.c" message: "regexp" repeated for each log file with a whitelist. """ parser = argparse.ArgumentParser(description=usage) parser.add_argument('-d', '--directory', help="Directory containing log files") parser.add_argument('-u', '--url', help="url containing logs from an OpenStack gate job") if __name__ == "__main__": try: sys.exit(main(parser.parse_args())) except Exception as e: print("Failure in script: %s" % e) # Don't fail if there is a problem with the script. sys.exit(0) tempest-2014.1.dev4108.gf22b6cc/run_tempest.sh0000775000175000017500000001057312332757070021007 0ustar chuckchuck00000000000000#!/usr/bin/env bash function usage { echo "Usage: $0 [OPTION]..." echo "Run Tempest test suite" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -n, --no-site-packages Isolate the virtualenv from the global Python environment" echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -u, --update Update the virtual environment with any newer package versions" echo " -s, --smoke Only run smoke tests" echo " -t, --serial Run testr serially" echo " -C, --config Config file location" echo " -h, --help Print this usage message" echo " -d, --debug Run tests with testtools instead of testr. This allows you to use PDB" echo " -l, --logging Enable logging" echo " -L, --logging-config Logging config file location. Default is etc/logging.conf" echo " -- [TESTROPTIONS] After the first '--' you can pass arbitrary arguments to testr " } testrargs="" venv=.venv with_venv=tools/with_venv.sh serial=0 always_venv=0 never_venv=0 no_site_packages=0 debug=0 force=0 wrapper="" config_file="" update=0 logging=0 logging_config=etc/logging.conf if ! options=$(getopt -o VNnfusthdC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,serial,help,debug,config:,logging,logging-config: -- "$@") then # parse error usage exit 1 fi eval set -- $options first_uu=yes while [ $# -gt 0 ]; do case "$1" in -h|--help) usage; exit;; -V|--virtual-env) always_venv=1; never_venv=0;; -N|--no-virtual-env) always_venv=0; never_venv=1;; -n|--no-site-packages) no_site_packages=1;; -f|--force) force=1;; -u|--update) update=1;; -d|--debug) debug=1;; -C|--config) config_file=$2; shift;; -s|--smoke) testrargs+="smoke";; -t|--serial) serial=1;; -l|--logging) logging=1;; -L|--logging-config) logging_config=$2; shift;; --) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no ;; *) testrargs+="$testrargs $1";; esac shift done if [ -n "$config_file" ]; then config_file=`readlink -f "$config_file"` export TEMPEST_CONFIG_DIR=`dirname "$config_file"` export TEMPEST_CONFIG=`basename "$config_file"` fi if [ $logging -eq 1 ]; then if [ ! -f "$logging_config" ]; then echo "No such logging config file: $logging_config" exit 1 fi logging_config=`readlink -f "$logging_config"` export TEMPEST_LOG_CONFIG_DIR=`dirname "$logging_config"` export TEMPEST_LOG_CONFIG=`basename "$logging_config"` fi cd `dirname "$0"` if [ $no_site_packages -eq 1 ]; then installvenvopts="--no-site-packages" fi function testr_init { if [ ! -d .testrepository ]; then ${wrapper} testr init fi } function run_tests { testr_init ${wrapper} find . -type f -name "*.pyc" -delete export OS_TEST_PATH=./tempest/test_discover if [ $debug -eq 1 ]; then if [ "$testrargs" = "" ]; then testrargs="discover ./tempest/test_discover" fi ${wrapper} python -m testtools.run $testrargs return $? fi if [ $serial -eq 1 ]; then ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py else ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py fi } if [ $never_venv -eq 0 ] then # Remove the virtual environment if --force used if [ $force -eq 1 ]; then echo "Cleaning virtualenv..." rm -rf ${venv} fi if [ $update -eq 1 ]; then echo "Updating virtualenv..." python tools/install_venv.py $installvenvopts fi if [ -e ${venv} ]; then wrapper="${with_venv}" else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv python tools/install_venv.py $installvenvopts wrapper="${with_venv}" else echo -e "No virtual environment found...create one? (Y/n) \c" read use_ve if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then # Install the virtualenv and run the test suite in it python tools/install_venv.py $installvenvopts wrapper=${with_venv} fi fi fi fi run_tests retval=$? exit $retval tempest-2014.1.dev4108.gf22b6cc/HACKING.rst0000664000175000017500000002202712332757070017676 0ustar chuckchuck00000000000000Tempest Coding Guide ==================== - Step 1: Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ - Step 2: Read on Tempest Specific Commandments ------------------------------ - [T102] Cannot import OpenStack python clients in tempest/api tests - [T104] Scenario tests require a services decorator - [T105] Unit tests cannot use setUpClass - [T106] vim configuration should not be kept in source files. Test Data/Configuration ----------------------- - Assume nothing about existing test data - Tests should be self contained (provide their own data) - Clean up test data at the completion of each test - Use configuration files for values that will vary by environment Exception Handling ------------------ According to the ``The Zen of Python`` the ``Errors should never pass silently.`` Tempest usually runs in special environment (jenkins gate jobs), in every error or failure situation we should provide as much error related information as possible, because we usually do not have the chance to investigate the situation after the issue happened. In every test case the abnormal situations must be very verbosely explained, by the exception and the log. In most cases the very first issue is the most important information. Try to avoid using ``try`` blocks in the test cases, both the ``except`` and ``finally`` block could replace the original exception, when the additional operations leads to another exception. Just letting an exception to propagate, is not bad idea in a test case, at all. Try to avoid using any exception handling construct which can hide the errors origin. If you really need to use a ``try`` block, please ensure the original exception at least logged. When the exception is logged you usually need to ``raise`` the same or a different exception anyway. Use of ``self.addCleanup`` is often a good way to avoid having to catch exceptions and still ensure resources are correctly cleaned up if the test fails part way through. Use the ``self.assert*`` methods provided by the unit test framework the signal failures early. Avoid using the ``self.fail`` alone, it's stack trace will signal the ``self.fail`` line as the origin of the error. Avoid constructing complex boolean expressions for assertion. The ``self.assertTrue`` or ``self.assertFalse`` without a ``msg`` argument, will just tell you the single boolean value, and you will not know anything about the values used in the formula, the ``msg`` argument might be good enough for providing more information. Most other assert method can include more information by default. For example ``self.assertIn`` can include the whole set. Recommended to use testtools matcher for more tricky assertion. `[doc] `_ You can implement your own specific matcher as well. `[doc] `_ If the test case fails you can see the related logs and the information carried by the exception (exception class, backtrack and exception info). This and the service logs are your only guide to find the root cause of flaky issue. Test cases are independent -------------------------- Every ``test_method`` must be callable individually and MUST NOT depends on, any other ``test_method`` or ``test_method`` ordering. Test cases MAY depend on commonly initialized resources/facilities, like credentials management, testresources and so on. These facilities, MUST be able to work even if just one ``test_method`` selected for execution. Service Tagging --------------- Service tagging is used to specify which services are exercised by a particular test method. You specify the services with the tempest.test.services decorator. For example: @services('compute', 'image') Valid service tag names are the same as the list of directories in tempest.api that have tests. For scenario tests having a service tag is required. For the api tests service tags are only needed if the test method makes an api call (either directly or indirectly through another service) that differs from the parent directory name. For example, any test that make an api call to a service other than nova in tempest.api.compute would require a service tag for those services, however they do not need to be tagged as compute. Negative Tests -------------- Newly added negative tests should use the negative test framework. First step is to create an interface description in a json file under `etc/schemas`. These descriptions consists of two important sections for the test (one of those is mandatory): - A resource (part of the URL of the request): Resources needed for a test must be created in `setUpClass` and registered with `set_resource` e.g.: `cls.set_resource("server", server['id'])` - A json schema: defines properties for a request. After that a test class must be added to automatically generate test scenarios out of the given interface description:: load_tests = test.NegativeAutoTest.load_tests class SampeTestNegativeTestJSON(, test.NegativeAutoTest): _interface = 'json' _service = 'compute' _schema_file = Negative tests must be marked with a negative attribute:: @test.attr(type=['negative', 'gate']) def test_get_console_output(self): self.execute(self._schema_file) All negative tests should be added into a separate negative test file. If such a file doesn't exist for the particular resource being tested a new test file should be added. Old XML based negative tests can be kept but should be renamed to `_xml.py`. Test skips because of Known Bugs -------------------------------- If a test is broken because of a bug it is appropriate to skip the test until bug has been fixed. You should use the skip_because decorator so that Tempest's skip tracking tool can watch the bug status. Example:: @skip_because(bug="980688") def test_this_and_that(self): ... Guidelines ---------- - Do not submit changesets with only testcases which are skipped as they will not be merged. - Consistently check the status code of responses in testcases. The earlier a problem is detected the easier it is to debug, especially where there is complicated setup required. Parallel Test Execution ----------------------- Tempest by default runs its tests in parallel this creates the possibility for interesting interactions between tests which can cause unexpected failures. Tenant isolation provides protection from most of the potential race conditions between tests outside the same class. But there are still a few of things to watch out for to try to avoid issues when running your tests in parallel. - Resources outside of a tenant scope still have the potential to conflict. This is a larger concern for the admin tests since most resources and actions that require admin privileges are outside of tenants. - Races between methods in the same class are not a problem because parallelization in tempest is at the test class level, but if there is a json and xml version of the same test class there could still be a race between methods. - The rand_name() function from tempest.common.utils.data_utils should be used anywhere a resource is created with a name. Static naming should be avoided to prevent resource conflicts. - If the execution of a set of tests is required to be serialized then locking can be used to perform this. See AggregatesAdminTest in tempest.api.compute.admin for an example of using locking. Stress Tests in Tempest ----------------------- Any tempest test case can be flagged as a stress test. With this flag it will be automatically discovery and used in the stress test runs. The stress test framework itself is a facility to spawn and control worker processes in order to find race conditions (see ``tempest/stress/`` for more information). Please note that these stress tests can't be used for benchmarking purposes since they don't measure any performance characteristics. Example:: @stresstest(class_setup_per='process') def test_this_and_that(self): ... This will flag the test ``test_this_and_that`` as a stress test. The parameter ``class_setup_per`` gives control when the setUpClass function should be called. Good candidates for stress tests are: - Scenario tests - API tests that have a wide focus Sample Configuration File ------------------------- The sample config file is autogenerated using a script. If any changes are made to the config variables in tempest then the sample config file must be regenerated. This can be done running the script: tools/generate_sample.sh Unit Tests ---------- Unit tests are a separate class of tests in tempest. They verify tempest itself, and thus have a different set of guidelines around them: 1. They can not require anything running externally. All you should need to run the unit tests is the git tree, python and the dependencies installed. This includes running services, a config file, etc. 2. The unit tests cannot use setUpClass, instead fixtures and testresources should be used for shared state between tests. tempest-2014.1.dev4108.gf22b6cc/PKG-INFO0000664000175000017500000001573412332757136017207 0ustar chuckchuck00000000000000Metadata-Version: 1.1 Name: tempest Version: 2014.1.dev4108.gf22b6cc Summary: OpenStack Integration Testing Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Tempest - The OpenStack Integration Test Suite ============================================== This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Design Principles ---------- Tempest Design Principles that we strive to live by. - Tempest should be able to run against any OpenStack cloud, be it a one node devstack install, a 20 node lxc cloud, or a 1000 node kvm cloud. - Tempest should be explicit in testing features. It is easy to auto discover features of a cloud incorrectly, and give people an incorrect assessment of their cloud. Explicit is always better. - Tempest uses OpenStack public interfaces. Tests in Tempest should only touch public interfaces, API calls (native or 3rd party), public CLI or libraries. - Tempest should not touch private or implementation specific interfaces. This means not directly going to the database, not directly hitting the hypervisors, not testing extensions not included in the OpenStack base. If there is some feature of OpenStack that is not verifiable through standard interfaces, this should be considered a possible enhancement. - Tempest strives for complete coverage of the OpenStack API and common scenarios that demonstrate a working cloud. - Tempest drives load in an OpenStack cloud. By including a broad array of API and scenario tests Tempest can be reused in whole or in parts as load generation for an OpenStack cloud. - Tempest should attempt to clean up after itself, whenever possible we should tear down resources when done. - Tempest should be self testing. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. The easiest way to create a configuration file is to copy the sample one in the ``etc/`` directory :: $> cd $TEMPEST_ROOT_DIR $> cp etc/tempest.conf.sample etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running devstack environment, tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your devstack installation. Tempest is not tied to any single test runner, but testr is the most commonly used tool. After setting up your configuration file, you can execute the set of Tempest tests by using ``testr`` :: $> testr run --parallel To run one single test :: $> testr run --parallel tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server Alternatively, you can use the run_tempest.sh script which will create a venv and run the tests or use tox to do the same. Configuration ------------- Detailed configuration of tempest is beyond the scope of this document. The etc/tempest.conf.sample attempts to be a self documenting version of the configuration. The sample config file is auto generated using the script: tools/generate_sample.sh The most important pieces that are needed are the user ids, openstack endpoints, and basic flavors and images needed to run tests. Common Issues ------------- Tempest was originally designed to primarily run against a full OpenStack deployment. Due to that focus, some issues may occur when running Tempest against devstack. Running Tempest, especially in parallel, against a devstack instance may cause requests to be rate limited, which will cause unexpected failures. Given the number of requests Tempest can make against a cluster, rate limiting should be disabled for all test accounts. Additionally, devstack only provides a single image which Nova can use. For the moment, the best solution is to provide the same image uuid for both image_ref and image_ref_alt. Tempest will skip tests as needed if it detects that both images are the same. Unit Tests ---------- Tempest also has a set of unit tests which test the tempest code itself. These tests can be run by specifing the test discovery path:: $> OS_TEST_PATH=./tempest/tests testr run --parallel By setting OS_TEST_PATH to ./tempest/tests it specifies that test discover should only be run on the unit test directory. The default value of OS_TEST_PATH is OS_TEST_PATH=./tempest/test_discover which will only run test discover on the tempest suite. Alternatively, you can use the run_tests.sh script which will create a venv and run the unit tests. There are also the py26, py27, or py33 tox jobs which will run the unit tests with the corresponding version of python. Python 2.6 ---------- Tempest can be run with Python 2.6 however the unit tests and the gate currently only run with Python 2.7, so there are no guarantees about the state of tempest when running with Python 2.6. Additionally, to enable testr to work with tempest using python 2.6 the discover module from the unittest-ext project has to be patched to switch the unittest.TestSuite to use unittest2.TestSuite instead. See:: https://code.google.com/p/unittest-ext/issues/detail?id=79 Platform: UNKNOWN Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 tempest-2014.1.dev4108.gf22b6cc/.mailmap0000664000175000017500000000120212332757070017511 0ustar chuckchuck00000000000000Ravikumar Venkatesan ravikumar-venkatesan Ravikumar Venkatesan ravikumar venkatesan Rohit Karajgi Rohit Karajgi Jay Pipes Jay Pipes Joe Gordon Daryl Walleck dwalleck tempest-2014.1.dev4108.gf22b6cc/openstack-common.conf0000664000175000017500000000035112332757070022220 0ustar chuckchuck00000000000000[DEFAULT] # The list of modules to copy from openstack-common module=config module=install_venv_common module=lockutils module=log module=importlib module=fixture # The base module to hold the copy of openstack.common base=tempest tempest-2014.1.dev4108.gf22b6cc/AUTHORS0000664000175000017500000002560312332757136017156 0ustar chuckchuck00000000000000Aaron Lee Aarti Kriplani Abhijeet Malawade Abhijeet.Jain Adalberto Medeiros Adam Gandelman Adam Gandelman Adam Young Aditi Raveesh Ala Rezmerita Alex Gaynor Alexander Ignatov Alvaro Lopez Garcia Andrea Frittoli Andrey Kurilin Angus Salkeld Anju Tiwari Anju Tiwari Anju5 Ann Kamyshnikova Arata Notsu Armando Migliaccio Ashish Chandra Attila Fazekas Bartosz Górski Ben Nemec Bhuvan Arumugam Bob Ball Boris Pavlovic Brant Knudson Brent Eagles Brian Lamar Brian Waldon Bruce R. Montague Burt Holzman Chang Bo Guo Chang Ye Wang ChenZheng Chmouel Boudjnah Chris Buccella Chris Yeoh Christian Schwede Christophe Sauthier Christopher Yeoh Clark Boylan Clint Byrum Cory Stone Cyril Roelandt Daisuke Morita Dan Prince Dan Smith Dane LeBlanc Daniel Korn Darragh O'Reilly Daryl Walleck Davanum Srinivas David Kranz David Kranz David Ripton Dean Troyer DennyZhang Derek Higgins Dirk Mueller Dolph Mathews Eiichi Aikawa Elena Ezhova Emilien Macchi Eoghan Glynn Eric Windisch Eric Windisch Eugene Nikanorov Fabien Boucher Frederic Lepied Gary Kotton Gavin Brebner Ghanshyam Ghanshyam Mann Giampaolo Lauria Giulio Fidente Gong Zhang Gordon Chung Haiwei Xu He Jie Xu Hengqing Hu Henry Gessau Hoisaleshwara Madan V S Hoisaleshwara Madan V S Hui HX Xiang Ian Wienand Ionuț Arțăriși James E. Blair James E. Blair Jaroslav Henner Jay Pipes Jeff Peeler Jeremy Stanley Jerry Cai Jinhe Fang Joe Gordon Joe H. Rahme John Garbutt John Griffith Jordan Pittier JordanP JordanP JordanP Jorge Chai Joris Roovers Joshua Harlow Juerg Haefliger Julie Pichon Julien Danjou Julien Leloup Justin Shepherd Jérôme Gallard K Jonathan Harker Katherine Elliott Ken'ichi Ohmichi Kui Shi Kurt Taylor Larisa Ustalov Leandro I. Costantino Leo Toyoda Li Ma Lingxian Kong LingxianKong Liu, Zhi Kun Mahesh Panchaksharaiah Malini Kamalambal Marc Koderer Marc Solanas Mark Maglana Mark McClain Mark McClain Martina Kollarova Maru Newby Maru Newby Masayuki Igawa Masayuki Igawa Mate Lakat Matt Riedemann Matthew Treinish Matthew Treinish Mauro S. M. Rodrigues Max Lobur Mehdi Abaakouk Mh Raies Michael Chapman Michael J Fork Michael Still Miguel Lavalle Mikhail S Medvedev Mitsuhiko Yamazaki Monty Taylor Morgan Fainberg Nachi Ueno Nayna Patel Nikhil Manchanda Nikola Dipanov Nikolay Pliashechnikov Oleg Bondarev Pablo Andres Fuente Pavel Sedlák Pranali Deore Prem Karat Pádraig Brady QingXin Meng Qiu Hua Qiao Rami Vaknin Ravikumar Venkatesan Rick Harris Rob Crittenden Rohan Kanade Rohan Kanade Rohan Rhishikesh Kanade Rohit Karajgi Roman Prykhodchenko Rossella Sblendido Russell Sim Ryan Hsu Ryan McNair Ryota Hashimoto Sahid Orentino Ferdjaoui Salvatore Orlando Samuel Merritt Santiago Baldassin Saranya Pandian Sascha Peilicke Sean Dague Sean Dague Sean Dague Sergey Kraynev Sergey Lukjanov Sergey Murashov Sergey Nikitin Shane Wang Soren Hansen Soren Hansen Sreeram Yerrapragada Stephen Gran Steve Baker Steven Dake Steven Hardy Sumanth Nagadavalli Sunil Thaha Swapnil Kulkarni Syed Armani Sylvain Afchain Sylvain Baubeau Tal Kammer Tatyana Leontovich Thomas Spatzier Tiago Mello Tony Yang Tushar Kalra Unmesh Gurjar Valeriy Ponomaryov Vasyl Khomenko Vincent Hou Vincent Untz Vladislav Kuzmin Walter A. Boring IV Wangpan Wayne Vestal Weeks Will Xavier Queralt Xiao Chen Xiao Hanyu Yair Fried Yaroslav Lobankov Yassine Lamgarchal Yong Sheng Gong Yoshihiro Kaneko Yuiko Takada YuikoTakada Zhenguo Niu Zhi Kun Liu Zhi kun Liu ZhiQiang Fan Zhiteng Huang Zhongyue Luo Zhu Zhu afazekas anju Tiwari anju tiwari armando-migliaccio chris fattarsi donald-ngo fujioka yuuichi gavin-brebner-orange gengjh harika-vakadi hi2suresh huangtianhua huangtianhua ivan-zhu izikpenso john-griffith jufeng jun xie kavan-patil lijunjbj liudong llg8212 m.benchchaoui@cloudbau.de meera-belur mouad benchchaoui nayna-patel nithya-ganesan qianlin raiesmh08 raiesmh08 rajalakshmi-ganesan ronak rossella sapan-kona saradpatel saurabh shihanzhang sukhdev tanlin umamohan vponomaryov vrovachev wanghao wanglianmin wingwj zhangyanzi zhhuabj tempest-2014.1.dev4108.gf22b6cc/test-requirements.txt0000664000175000017500000000022312332757070022333 0ustar chuckchuck00000000000000hacking>=0.8.0,<0.9 # needed for doc build docutils==0.9.1 sphinx>=1.1.2,<1.2 python-subunit>=0.0.18 oslosphinx mox>=0.5.3 mock>=1.0 coverage>=3.6 tempest-2014.1.dev4108.gf22b6cc/doc/0000775000175000017500000000000012332757136016645 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/0000775000175000017500000000000012332757136020145 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/HACKING.rst0000777000175000017500000000000012332757070024157 2../../HACKING.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/index.rst0000664000175000017500000000164612332757070022012 0ustar chuckchuck00000000000000.. Tempest documentation master file, created by sphinx-quickstart on Tue May 21 17:43:32 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. ======================= Tempest Testing Project ======================= Contents: .. toctree:: :maxdepth: 2 overview HACKING ------------ Field Guides ------------ Tempest contains tests of many different types, the field guides attempt to explain these in a way that makes it easy to understand where your test contributions should go. .. toctree:: :maxdepth: 1 field_guide/index field_guide/api field_guide/cli field_guide/scenario field_guide/stress field_guide/thirdparty field_guide/unit_tests ------------------ API and test cases ------------------ .. toctree:: :maxdepth: 1 api/modules ================== Indices and tables ================== * :ref:`search` tempest-2014.1.dev4108.gf22b6cc/doc/source/conf.py0000664000175000017500000002145112332757070021444 0ustar chuckchuck00000000000000# -*- coding: utf-8 -*- # # Tempest documentation build configuration file, created by # sphinx-quickstart on Tue May 21 17:43:32 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'oslosphinx' ] todo_include_todos = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Tempest' copyright = u'2013, OpenStack QA Team' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = False # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['tempest.'] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1" html_last_updated_fmt = os.popen(git_cmd).read() # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. html_use_index = False # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Tempestdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Tempest.tex', u'Tempest Documentation', u'OpenStack QA Team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'tempest', u'Tempest Documentation', [u'OpenStack QA Team'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Tempest', u'Tempest Documentation', u'OpenStack QA Team', 'Tempest', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = u'Tempest' epub_author = u'Sean Dague' epub_publisher = u'OpenStack QA Team' epub_copyright = u'2013, OpenStack QA Team' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # A tuple containing the cover image and cover page html template filenames. #epub_cover = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/0000775000175000017500000000000012332757136022405 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/index.rst0000777000175000017500000000000012332757070030247 2../../../tempest/README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/api.rst0000777000175000017500000000000012332757070030462 2../../../tempest/api/README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/stress.rst0000777000175000017500000000000012332757070032006 2../../../tempest/stress/README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/cli.rst0000777000175000017500000000000012332757070030456 2../../../tempest/cli/README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/unit_tests.rst0000777000175000017500000000000012332757070032503 2../../../tempest/tests/README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/thirdparty.rst0000777000175000017500000000000012332757070033524 2../../../tempest/thirdparty/README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/field_guide/scenario.rst0000777000175000017500000000000012332757070032546 2../../../tempest/scenario/README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/doc/source/overview.rst0000777000175000017500000000000012332757070024652 2../../README.rstustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/.coveragerc0000664000175000017500000000012012332757070020207 0ustar chuckchuck00000000000000[run] branch = True source = tempest omit = tempest/tests/*,tempest/openstack/* tempest-2014.1.dev4108.gf22b6cc/ChangeLog0000664000175000017500000030727712332757136017672 0ustar chuckchuck00000000000000CHANGES ======= * refactor out code duplication * Use find_test_caller to put test name in timeout exception details * Verify the response attributes of 'create-aggregate' API * Verify update host Nova V2/V3 APIs attributes * Add unit tests for tempest hacking checks * Add in a concurrency aware subunit filter * Access credential fields as attributes * Enforces the use of Credentials (part2) * Removing unnecessary pass instructions * Refactor _find_caller into a public test finder utility * Add compute service tags to ListSnapshotImagesTest * Verify "update a server" API response attributes * Skip creation of network resources for disabled IPv6 tests * Add noqa support to no_setupclass_for_unit_tests * Revert "add server personality files test" * Updated from global requirements * Add a common class for Nova v2/v3 API tests * Separate common 'start_up' definition from v2 schema * Add unit tests for verifying extensions list * Add unit tests for api_version detection * Add unit tests for _get_unversioned_endpoint * Add cmd entry point for verify_tempest_config * Add support for updating the config file * Enforces the use of Credentials (part1) * Use auth data to fill credentials * Define V3 Credentials * Skip lbaas test test_load_balancer_basic * Fix stress runner not stopping on first fail * Stress runner/driver prints stats after SIGINT * Remove {begin,roll}_detaching volume API tests * Add a lacking message format letter 's' * Verify Set/Get/Delete server meta item attributes * Sahara: preparations for data source tests * ssh_floating verify reboot * Updated from global requirements * Add tests for CINDER services v1 APIs * Verify create/get flavor attributes of Nova APIs * Add a response header validation * Verify more information for member in lbaas api tests * fix test_compute_with_volumes * Remove unused arguments * Adds more testcases to test_telemetry_alarming_api * Account failure to the tearDown instead of setUp * Check for 'Generated' in SSH key comment * Verify 'list-server-metadata' APIs attributes * Verify the attributes of 'set-server-metadata' API * Defines a Credentials class * Verify addrs only against ports with fixed IPs * Verify os-migration API response attributes * Verify attributes through Nova os-quota-sets API * orchestration api add test for volume retain deletion policy * orchestration api add basic volume resource test * orchestration api refactor access to stack outputs * Verify tenant usages API response attributes * Verify certificate API response attributes * Verify delete aggregate attributes of Nova APIs * Remove Nova v3 XML test skip * Delete OrchestrationManager, and its unusual credentials * skip the quota enforcement test for swift * Add parametric tests of Swift object API, part 2 * Verify flavor extra specs attributes of Nova APIs * Fix stress runner signal related issues * ssh instance validation add options for Neutron * Verify list agents attributes of V2/V3 APIs * Added cleanup in some tests * Check detail list Images attributes of Nova V2 API * Check attributes of attach/detach volume Nova APIs * Make large-ops test boot a group of instances 3 times * Verify delete server attributes of Nova API * Missing baremetal NodeState API test * fix dict reference error * Verify delete agent response of V2/V3 APIs * VPNaaS API tests cleanup * skip test_stack_update_add_remove because of race * orchestration convert API test Timeouts to respect build_timeout * Avoid passing empty string as AZ name * List multiple fields tests for networks/ports * move out ironic exception from common import * add quotas tests * Skip more pause tests if pause is not available * Up the default timeout for stack builds * Remove test_create_port_with_no_ip * Validate list_instance_usage_audit_log Nova V2 API * Fix test_verify_created_server_ephemeral_disk * Move exceptions back into one place * Remove the unused _get_unused_flavor_id() * Consider state in firewall create/delete test * Add Sahara client for scenarios tests * fix import for default clients in clients.py * Swift formpost cleanup * Implementing XML client for VPNaaS * Fix import group ordering in test_utils.py * Add load_test mechanism for InputScenarioUtils * Move the test_utils module to scenario test dir * Add cinder api version detection to verify_tempest_config * Fix service list in verify_tempest_config * Fix url parsing for api version check * Inspect listed ports for created port with binding * Verify the response attributes of 'update-aggregate' API * Check get_vnc_console attributes of Nova APIs * Remove test_create_server_response * Updated from global requirements * Verify delete interface response of V2/V3 APIs * remove n-sch from the watch list * Unit Tests for glance_http * Add keystone api version detection to verify_tempest_config * Add check that service tag isn't in path name * Add unit tests for commands * Improve test_load_balancer_basic * Add unit tests for all generators * safe_setup preserve original trace * Rename instance_actions v3 plugin tests to server_actions * Don't output auth tokens with trace output * Verify attributes through Nova os-security-groups API * orchestration API add coverage for stack update API * orchestration API refactor test_list_resources * Verify delete quota response of V2/V3 APIs * Verify "get az list" API response attributes * Add opportunity to directly update headers * Verify the response attributes of 'shutdown/reboot_host' API * Change to use absolute path in load_template() * Stop swift resource leaking even if an error occurs * Verify list_virtual_interfaces attributes of API * Add V2 Nova API os-agents tests * Improve the extra routes test on router * Remove created routers as part of test cleanup * Make heat-slow job run in parallel * Unskip load balancer basic scenario test * Verify the response attributes of 'aggregate-set-metadata' API * Waiting for ACTIVE state in rescue tests * Enable security_groups_basic_ops blocking tests * Network fwaas API test * Volume size could be specified to create volume * orchestration API base class rename clear* functions * Multiple fixes to test_server_basic_ops * Trailing '/' throws error * Add network API test to create/update a port with 2 IP addresses * Adds unit test for negative class decorator * Sahara: preparations for new tests * Missing baremetal driver API test * Server create - JSON schema validation: adminPass is optional * Check attributes of get server password Nova APIs * Check attributes of create/delete sec groups rule * unskip test_list_servers_filtered_by_ip * add trace_requests option to debug section * Remove python25 workaround from glance_http * Move run_ssh class variable into skip decorators * Honor suspend/pause config switches in scenario tests * Verify the response attributes of 'startup_host' API * Remove singleton pattern in base_generator * Enable one flavor tests * Verify detail_list flavor attributes of V2/V3 APIs * Adds Ironic test_baremetal_basic_ops scenario test * Add and use a StackResourceBuildErrorException * Use OS::Nova::Server for NeutronResourcesTestJSON * Move resize_available class variable into skip decorators * Move pause_available class variable into skip decorators * Move suspend_available class variable into skip decorators * Disable other suspend/resume tests if not supported * Stress action for volume attach verify * Expand baremetal port coverage * Verify more information for pools in lbaas tests * Split certificate API test * Check add/remove flavor access APIs attributes * Remove variable part of agent dict to fix AgentManagementTest * orchestration add resource limit API test * orchestration remove unused invalid_template_url * orchestration api tests remove duplicate client assignment * orchestration api tests, docstring cleanups * Verify list extensions attributes of V2/V3 APIs * Verify "get version" API response attributes * Verify the create/delete volume APIs attributes * Add os-migration tests for Nova v3 API * Add os-migration tests for Nova v2 API * Remove unused create_image of Nova v2 API * Check attributes of image meta item Nova APIs * Validate image metadata attributes of Nova APIs * Verify the response status of create delete Image * Enable private flavors tests * Verify hypervisor uptime attributes of Nova API * Check list & search hypervisor attributes of Nova * Validate hypervisors_servers Nova V2/V3 API * Introduce .coveragerc * Verify the response attributes of 'get_aggregate' * Updated from global requirements * Check show_hypervisor attributes of Nova V2/V3 API * fix sahara base class * Verify list_hypervisors_detail Nova V2/V3 API * Stop neutron resource leaking even if an error occurs * Validate get_instance_usage_audit_log Nova V2 API * ceilometer-collector now has errors * Move xml common code into the common dir * Verify "get quotas detail" API response attributes * Deduplicate negative test calls * Check hypervisor statistics attributes of Nova API * Removing unnecessary pieces of code from network client * factor out templates to yaml files * Add list roles api to identity v3 * Update documentation for negative testing * Validate for list flavor attributes of V2/V3 APIs * Stop leaking in images tests * Cinder client does not honor disable_ssl_certificate_validation * Typo in config.py * simplify heat test_limits * Add config fixture support to unit tests * Skip volume snapshot tests if feature is not enabled * Verify attributes through Nova "list security groups" API * Adds more verification in list alarms * Add unit tests for cli.output_parser * Add a new exception for invalid structure * Add unit tests for NegativeRestClient class * Check reserve/unreserve fixed-ips APIs attributes * Fail a test if stack delete failed * Add sahara edp cli commands tests * Cleanup sahara cli tests * Some keystone V3 API tests throw incorrect errors * Verify the response attributes of 'list_aggregates' * Add sahara to list of clients for T102 * Renew token before expiry time * Check create/delete keypair attribute of Nova APIs * Add Keystone role and service test cases * Verify the list volume attributes of Nova APIs * Stop volume leaking * Test current tenant not added to private flavor * Check attach-detach FIP & get FIP pool attributes * Validate list_keypair attribute of Nova V2/V3 APIs * Validate get keypair attributes of Nova V2/V3 API * Add "delete the volume-attached server" tests * Verify list Image attributes through Nova V2 API * Verify Nova create & get Floating IP attributes * Define 'links' as a common parameter type * Move to the python-saharaclient * Remove resize-revert workaround for bug 924371 * don't log cli output on success * add request timing * add _find_caller to the request log * Adds "add_dhcp_agent" to test_dhcp_agent_scheduler * add back empty whitelist * Add Cinder quota negatives * API test for 'create server with scheduler hints' * simplify rest_client logging * Translate xml server tenantId /userId * Skip loadbalancer basic scenario test * Verify quotas attributes through Nova os-quota-sets API * unskip test_integration_1 * Add error handling if testscenarios aren't supported * Add parametric tests of Swift object API, part 3 * Verify "create a server" API response attributes * Make add_remove_fixed_ip tests executable in Nova gate * Fix cinder quota cleanup * Remove usage of internal library function in basic generator * Add server to clean up even in case of errors * fix cinder quota equality * Verify attributes through Nova list flavor-access API * Stop keystone resource leaking even if an error occurs * Stop heat resource leaking even if an error occurs * Make the checks of identity status code strict * Add tests for external network extension * Add validation test in identity v3 test_role * Use HTTP_SUCCESS for checking success status code * Verify "enable a service" API response attributes * Verify Image attributes through Nova V2 GET API * change dirty logs to work off a whitelist * Verify the response attributes of 'show_host_detail' * Verify the response attributes of 'list_hosts' * Validate of Nova list Floating IPs attributes * Validate get limits attributes of Nova V2 API * Networks,Ports: delete with subnet, port with no IP * Add unit tests for configurable network resources * Add slow tag for test_ceilometer_resource_list * Update Oslo config sample generator * Volume boot test uses new block device syntax * Introduce load_tests mechanism for negative tests * Stop test server leaking even if an error happens * Refactor create_ and update_ methods for floating ips * add volume list tests for cinder v2 * Cleanup common.debug * Add unit tests for debug * add server personality files test * add compute quotas test * Validate get fixed-ips attributes of Nova V2 API * Ignore .coverage* files * Add utils.misc unit tests * Enhance test to rescope token using v2 * Enhance rescope token test using v3 * Add test to rescope token using v3 * Add missing client names to T102 hacking check * Add unit tests for wait_for_resource_deletion * Add test to rescope token using v2 * Add unit tests for the tempest.common.utils.file_utils * Add network credential unit testing * Add unit basic unit tests for tenant_isolation * Support endpoint type in CLI tests * Remove test_can_log_into_created_server * Add V2 Nova API os-quota-sets tests * Add cinder tempest cases for encryption-type * Modified test case for nova security group * Add api tests for load balancer's VIPs and health monitors * change teardown check to LOG.error * Add return value of classmethod in network base.py * Move verification of response attributes into service client * Stop volume leaking even if an error is occurred * Add list user groups api to identity v3 * Fix error trace induced by dhcp test actions * Rename Savanna to Sahara * Do not assert router l3 agent association * Wait properly for server deletion in test_volumes_actions * Add tests for binding extended attributes for ports * Object storage tests to use default auth_provider * Fix issue with pep8 gate job * Validate Volume attributes of Nova POST & GET API * Fix V3 image tests according to new image_client * Make imports proper order * Convert fake_config class to use config fixture * Add agents tests for Nova v3 API * Add basic Delete Queue Marconi test * Make test_volume_quotas force tenant isolation * Stop leaking test_update_images * Move network test_quotas to admin directory * Skip Nova API attribute tests if an XML response * Move negative tests for test_images * Verify service attributes through Nova "get services" API * Updated from global requirements * Unset the imageRef when booting from volume * Verify more information in API tests for LBaaS * Add quota_set delete test for Nova v3 API * API test for Get Image Member(s) Schema * V2 API Test to get the VNC console of a Server * Improve readability of test_networks * Adds basic Marconi test * Support disabling suspend/resume for compute api * Support disabling server pausing for compute API * Updated from global requirements * Prepare for enabling H302 rule(services/telemetry, object_storage, etc) * Add unit test for data_utils * fix flake8 errors * Removed teardown method from image test * add token get tests * Remove unused build_url function in data_utils * Add negative tests for endpoints * Increase testing of network connectivity * Fix problem of deleting dhcp port * Allow failing logs with errors on a per-log-file basis * Enable H302 check (tempest/services/volume) * Add shelve_offload test for Nova API * Fixed _error_checker in rest client * Make tempest accounts independent from devstack * cleanup resources in setUpClass if exception raised * Volume backup details API test * Raise orchestration build_timeout to 600 seconds * Add ignore files related to coverage to .gitignore * ServerCfnInitTestJSON wait for stack complete before ssh * Skip nova cli tests with volumes if Cinder unavailable * Fix RemoteClient usage in scenario tests * Enable H302 check (api/compute/{c*,f*,i*,s*,t*}) * Add support for negative tests with admin client * Add multiple negative test generator support * Add Trove (database) Flavor API Tests * Fix test by waiting to lbaas entity delete * Stop volume leaking test_server_rescue_negative * Do not setUpClass or tearDownClass V3 tests if API is disabled * Prepare for enabling H302 rule(services/identity) * Cleanup _interface class variables in compute * Prepare for enabling H302 rule(api/image) * Prepare for enabling H302 rule(services/compute/v3/json) * Separate negative tests for test_images * Prepare for enabling H302 rule(services/compute/json) * Add status code checks for Nova attach_interfaces API * Nova V3 API test for resetNetwork/injectNetworkInfo * Assign floating ip to a server in load balancer scenario * Prepare for enabling H302 rule (api/volume,tempest/*) * Prepare for enabling H302 rule (api/compute/admin) * image(s) schema image metadata API test * Removes gate test annotation in security_groups * Fix invalid syntax in HOT templates * Move ipv6 config option into network-feature-enabled * Cleanup subnet creation in network api tests * Stop running CONF getattr during import * Seperate negative tests for V3 test_server_rescue * Updated from global requirements * Add Cinder tests for quota sets * Add parametric tests of Swift account API * Add "delete the resized server" tests * Remove unused definition in network base.py * Add "Nova V3 API test for add-fixed-IP/remove-fixed-IP" * replace basetring/xrange * Add V3 API Test to get the VNC console of server * Add service/endpoint discover to verify_tempest_config * Add swift discoverable_api support to verify_tempest_config * Make reseller admin role configurable * Split out config option registration * Add unit tests for image waiter * Configure the ec2 zone explicitly * Adds VM connectivity check after advanced VM operations * Add Nova V2 API test for resetNetwork/injectNetworkInfo * Remove unused _parse_array functions * XML endpoint client use correct string for enabled * Cleanup _interface class variables in compute/v3 * unskip test_security_group_rules_negative tests * Use pretty tox with the ``all`` env * Revert "always use sitepackages" * Fix setup_test_v3_user method in identity base.py * Add "V2 API- add-fixed-IP and remove-fixed-IP server action" tests * Separate negative tests for test_server_addresses * Skip test_get_instance_action test for nova v3 * Prepare for enabling H302 rule (thirdparty) * Extend network debug infos on network failures * Add endpoint_type option to Savanna,Ironic and Ceilometer groups * stress/actions/ssh_floating.py n-net compatibility * Test to update neutron security group * Improve tempest auth tests * Fix api_version filter for KeystoneV3AuthProvider * Adds L3 agent test case to test_l3_agent_scheduler.py * Add namespaces to xml in Network client * Adds scenario for hotplug nic in network_basic_ops * NeutronResourcesTestJSON Use the correct resource name for console log * Check server terminations on "delete server" tests * Removal of methods from json/xml network_client files * Move admin "delete a server" tests to test_servers * Separate negative tests for test_instance_actions * Improved tests in tempest.api.image.v2.* * Fix the usage error for config * Clean network scenarios * Test creating router setting tenant_id * Scenario : Start instances using fixed network when possible * Prepare for enabling H302 rule (scenario) * Prepare for enabling H302 rule (common,services,stress) * Prepare for enabling H302 rule (api/compute) * Stress runner friendly logging.conf.sample * Add unit tests to tempest auth file * Add security group deletion to the cleanup util * Implement pluggability for tempest (exceptions) * Separate negative tests for V3 test_quotas * Fix author information * Log ip Information on ssh failures in the minimum scenario * Fix stress runner exit issues * Refactor Managers to a common base class * Preventing overlapping subnets in network scenarios * Fix a typo of debug log in scenario/manager.py * Removed RestClientXML class * Avoid masking an assert failure with a KeyError Exception * Use Python 2.6.x compatible syntax for dict comprehension * add cli.has_manage option * Fix InvalidInstanceID.NotFound handling * Clarify admin_delete_server_of_others test case * EC2 client token is not reuseable for new instance creation * Add tests for Swift in orchestration * Add qcow2 image support * Clean up scenario functions * Add "delete a shelved server" tests * Separate the deleting server tests * fix duplicate api_extensions * Improve ServerCfnInitTestJSON asserts and debugging * Skip EC2VolumesTest if Cinder is not available * Allow IPv6 tests to be disabled * add TRACE level to the items that are being flagged * Separate negative tests for test_projects * Makes some Swift API tests optional * Separate negative tests for test_multiple_create * Separate non-admin AZ tests from admin test files * Remove unsed client variables * Adds cinder backup functional tests * Fix misspellings in tempest * Splits network_basic_ops to fully isolated test cases * Refactor cross_tenant to security_groups_basic_ops * Minor changes to scenario manager * Prepare for enabling H302 (identity,volume,etc.) * Introduce T106 rule for vi modelines * Seperate negative tests for test_server_rescue * Mildly wound the interlopers * Couple of fixes to tempest/auth * Add python-savannaclient to requirements * remove some unused code in nova image tests * fix quotas client in admin/test_quotas.py * Fix test classes name of test_tokens.py * Migrate negative test to a different file * Skip test_list_server_addresses if neutron until bug 1210483 is fixed * Restore heat tests running with admin user, demo tenant * Fix get_versions string parsing * Fix get versions call in verify_nova_api_versions() * Fix containers with expired objects deletion problem * Add quota_set detail test for Nova v3 API * Remove using of deprecated self.headers (part2) * Make endpoint type configurable * unskip test_list_non_public_flavor * Removes vim headers 5th round * Add basic Savanna CLI tests * Refactor create_ and update_ methods for VIPs and health_monitors * Remove using of deprecated self.headers (part1) * Add extra list servers by host test * Prepare for enabling H302 rule (api/object_storage) * port nova v2 images related tests into nova v3 part2 * Neutron Extra DHCP Options API test * Neutron LBaaS Agent Scheduler API test * Add api tests for neutron router * Replace assertEqual(None, *) with assertIsNone in tests * Switch over to oslosphinx * Fix request id log messages * Add a note about python2.6 support to the README * Ensure that bug number is actually a number for skip_because * Test tempest decorators used on integration tests * unskip neutron_meter_label cli tests * cleanUp() removing all test resources as an admin * Rename Openstack to OpenStack * Add CRUD tests for telemetry alarming * clean up security groups tests * port flavors and server_password tests into nova v3 part2 * Define py27 assertFoo methods for py26 * Add test case for snapshot-create with in-use volume * Test updating port's admin_state_up * Separate negative tests for test_list_image_filters * Fix coverage option for run_tests.sh * Adapt documentation for negative testing * Remove tests for Nova V3 API os-simple-tenant-usage * Adds list pool stat test case * Refactor rest-client and identity v2-client * Add some cases about volume-create and volume-rename * Remove tests for Nova V3 API os-instance-usage-audit-log * Fix test_create_list_delete_volume_transfer without tenant isolation * Fix usage of NotImplementedError exceptions in auth classes * Rename images variable in TempestConfigPrivate * Add a serial full tox job to tox.ini * auth.py is too verbose * port flavors and server_password tests into nova v3 part1 * Remove tox locale overrides * Use testtools.matchers.GreaterThan in test_volumes_transfer * Use service catalog_type when getting the heat/glance clients * Fix syntax in test_load_balancer_basics._check_load_balancing * Let the soft reboot really happen in the minimum scenario * enable volume list tests for cinder v2 - part2 * enable volume list tests for cinder v2 - part1 * fix base_url in auth.py * Network API: default to ipv4, add ipv6 tests * Fix volume transfer tests with tenant isolation disabled * Fix images tests with tenant isolation disabled * Add support for special char in volume metadata * Fix client usage in multibackend volume test * Add nova migration-list CLI test * InputScenarioUtils load images if glance available * Skip failing test load balancing test * Test to list ports filtered by router ID * Support building wheels (PEP-427) * network: add metering api operations * Fixed misspellings in Tempest * Add more information to BuildErrorException * Make v2 and v3 identity apis configurable * The rescue tests requires neutron resources * Negative tests: Add result check for resources * Savanna: add API client and tests for plugins * Fix admin tenant credential * Multiversion authentication part2 * Add check_uptodate.sh to run_tests.sh -p * Use isolation credentials for neutron api tests * Add the keyword "Test" to some test class names * Clean/leave OpenStack after a stress test * Add unit tests for negative test framework * Multiversion authentication part1 * Removes leftover self.conf from isolated creds * Add logging config values * make testing neutron api extensions optional * Remove unnecessary volume creation in test_server_rescue * Correctly call client inits from test_multi_backend * Change import of exceptions for keystoneclient * Add log info for tempest SSH connection issues * Negative test autogeneration framework * Add scenario test for load balancer * Fix attach_interfaces tests of Nova v3 * Remove suffix "JSON" from Nova v3 API test classes * Remove suffix "JSON" from Nova v3 API last test class * Add parametric tests of Swift container API * Add skips to the services decorators * Fix services decorator to use object_storage * Fix stress test README.rst * Don't run extensions list if service isn't available * Updated from global requirements * Add test for OS::Neutron::Router Heat resource * Add missing isolated_cred cleanup to savanna tests * Remove last uses of config without global CONF object * Add key -n for sudo utility * Convert all service clients to global CONF object * Convert cli tests to use global CONF object * Convert scenario tests to use global CONF object * stop leaking tempdirs * Enable tenant isolation for the boto tests * Convert thirdparty and stress tests to use global CONF object * Convert volume api tests to use global CONF object * Convert ironic, swift, and heat api tests to use global CONF object * Matches one flavor and one image by default * Remove network resources created in scenario tests * Forbid availability_zone None in aggregate Nova API * Removes vim headers 4th round * Add alias as prefix for flavor_rxtx v3 * remove unused variable * Add bool and integer support to XML parser * Convert network api tests to use global CONF object * Convert image and identity api tests to use global CONF object * Convert compute api tests to global CONF object * test_rescued_vm_add_remove_security_group missing addCleanup * Return body output after given status reached * Skip negative tests of v3 server metadata * Move negative tests for v3 test_server_metadata * Revert "skip test_volume_boot_pattern" * Removed unnecessary lock from a aggregate test * Move common _delete_volume cleanup method in base compute test class * Add tempest test for heat resource OS::Nova::KeyPair * Remove super call in overloaded close() * Separate negative tests for test_templates * tighten up isolated creds create * Fixed up an error message * Remove Nova v3 XML tests completely * Use BaseComputeTest.create_test_server * Run SNAT specific test cases only with ext-gw-mode extension * Specify 'active' status for deleting situations * Fix glance_http HTTPS response issues * Remove unused method basic_auth from rest client * Removes vim headers 3rd round * Use unittest2.TestSuite with py26 * Cleanup exceptions * Increase failure details for network_basic_ops * Update 'Member' with option 'operator_role' in tempest.conf * add tests for force delete snapshot: * Add volume cleanup to test_volume_transfer test * Exclude heartbeat timestamp from agent list checks * Add more descriptive assertion message for test_create_backup * Separate negative tests for Swift * Do not assume volume metadata is identical to POST request * Remove baremetal xml support * Increase ping timeout from 60 to 120 seconds * Skip test_list_servers_by_admin_with_all_tenants test * Create telemetry client * Change status available to active * remove the nova v3 xml tests from the code * remove Nova v3 XML testing * stop validating user IDs on role assignment operations * Avoid using assertGreater * Separate negative tests for test_quotas * Call super class after extension check in vpnaas api test * Remove vim headers 2nd round * Add test for volume list with all-tenants * Fix class inheritance for volume_types tests * Fix attach_interfaces tests * Check if service is available for cli tests * Add simple node group tmpl API test for Savanna * Use automated create and update methods for core network resources * Fix logic for config file env variables * Make --debug flag on run_tests and run_tempest use testtools.run * Fix parameter in auth error message * Sync Patch and PatchObject fixtures from oslo-incubator * Python 2.6 do not has unittest.loader, we import unittest2 instead * skip test_volume_boot_pattern * Remove X-Storage-Stoken from object_storage * Add tests for testing swift bulk middleware * Rewrite RemoteClient.get_boot_time() * text-html type is absent in TXT_ENC * Remove vim headers * Avoid expiration before updating metadata * Fix log_console_output call in test_network_basicops * Add a couple log errors to whitelist.yaml * Move console output log place in test_cross_tenant_connectivity * Only create necessary resources for compute v3 tests * Add unit tests for SSH client functionality * Create only necessary resources for compute v2 and network tests * Make network resource creation in isolation configurable * Fix typo in imagev2 class name : Memeber should be Member * Make BaseV2MemeberImageTest inherits from BaseV2ImageTest * AttachInterfacesTest: use build_timeout on iterface deletion * Fixing typo in test_volumes_get.py * Fix parameter type spelling tempest HOT template * Remove user creation in object service and healthcheck test * Fixing typo "existant". Must be "existent" * Create only necessary networks resources for scenario tests * Create only necessary resources for image, object_storage, volume * Forbid admin_password None in rescue Nova API * Add to whitelist two errors that slipped through * Add more console output log for scenario tests * Remove test_network_quotas_scenario * Fix host_update test case for losing essential parameter * Create security group rule with additional args * Fix parameter of nova v3 flavors API * Add missing whitespace in config description * Add a check for compute api versions to verify_tempest_config * Add neutron extension support to verify_tempest_config * Rework extension verification in verify_tempest_config * Moves negative tests from test_server_metadata * Fix an error when executing run_tests.sh * Use install_venv from oslo to fix no post_process issue * Test server create with ephemeral disk * Remove copyright from empty files * Improve testing of list_extensions for compute * Update README.rst with details about unit tests * Add aggregates scenario test * Tests object_storage extension listing * Fix volume metadata validation of identical to request * Add delete specific status server test * cinder v2 api tests - fix volume client * Input scenario capability for tempest * Add coverage option to run_tests.sh * Fix v3 test_list_servers_negative * Fixes handling of arrays in XML to JSON conversion * trusts API test, avoid creating duplicate user * Neutron Agent Management List Agents Non Admin * Remove Swift container-sync test skipping * Adds test_list_XX_fields cases to list some fields * log console_output when test_network_basic_ops failed * Add a config option for trusts * Fix typo in ssh_floating.json for stress tests * Add a discoverable_apis option for swift * Adds test_show_XX_fields cases to show some fields * Refactor usage of custom_match tests * Fix bug in validating Swift transaction ID * List Nova and Cinder extensions in debug log * Delete a BuildError server * Add list server filter with extra limits * Add a version API test for Nova v3 API * API tests for Ironic * Fix typo in ceilometer and neutron tests * Remove skip_because in swift object expiry test * Moves negative tests from test_instance_actions * port test_server_metadata and test_server_personality into v3 part2 * add keystone group tests * Add heat stack action test case * Add a unit test coverage tox job * Add base class for Telemetry tests * Add python-swift client to the requirements * Add extra list server by status test * fix a minor bug when detecting server image flavor * Wait for the server before validate the rotation result * Add a run_tempest.sh script * Make run_tests.sh for running unit tests * Add basic read-only tests for heat cli * Fix reference config before initialization in cli tests * Skip simple_tenant_usage related tests * Skip flavor_access_add/remove related tests * Add tests for snapshot_metadata * remove eventlet from requirements * always use sitepackages * keystone trusts API test, move delete into tearDown handler * port nova v2 images related tests into nova v3 part1 * sync oslo to current * Catch ssh exception and detach in min scenario * Remove test_auth_token.py * Increase exception log details * add tests for security_group updating * Kill finally, use addCleanUp * Add test for HEAD queries on Swift tempurl MW * Dump all log errors to console * Moves negative tests from test_absolute_limits * port admin/test_servers* into Nova V3 tests part2 * port admin/test_servers* into Nova V3 tests part1 * port instance_usage_audit_log tests into nova v3 part2 * port test_live_block_migration into nova v3 part2 * port test_live_block_migration into nova v3 part1 * port test_quotas into v3 part2 * Add tests for volume_metadata * Add config for Telemetry * port some servers tests into nova v3 part2 * Moves negative tests from test_server_addresses * port test_aggregates and test_hosts into nova v3 part2 * port test_aggregates and test_hosts into nova v3 part1 * Remove unused wait_for function * Use wrapper create_volume() in volume tests * Refactor network client: add create_ and update_ methods * Moves negative tests from test_multiple_create * Separate negative tests for test_image_metadata * port servers negative tests into v3 Part2 * port some flavor tests into nova v3 part2 * Separate negative tests for test_simple_tenant_usage * Make testing neutron agents optional * Fix the example of testr testing * Fix OS_TEST_PATH in .testr.conf * allow hypervisors to be down but still pass * keystone OS-TRUST extension, test with expiry * keystone OS-TRUST extension, test list operations * Add negative tests for routers * Add 2 tests to the Floating IP test case * Add create_floating_ip function * Use create_router_interface and create_port * Serialize plurals correctly in neutron xml client * Add version test for Ceilometer cli * remove last errant file parse warning * remove unneeded __init__ file * rename old config object to TempestConfigPrivate * Remove FloatingIPChecker from network_basic_ops * Fix AutoScalingTest test suite error * moving to global lazy loaded config * cinder v2 api tests - part1 * port test_keypairs into nova v3 part2 * Refactor cross_tenant_connectivity for tenant isolation * Remove unnecessary spaces from messages * Add unit tests for rest_client * Fix AttributeError on BadRequest in scenario tests * scenario/network_basic_ops: reassociate floating-ip * Enable HostsAdminTestXML * Add swift scenario tests * Increase support for isolated tenants in scenario * Add testcases for images * move negative tests out of test_services in nova v3 * Skip extraroute tests if extension is not enabled * Add test_discover module to provide a load_tests hook * scenario cross_tenant_connectivity * attach_interfaces as smoke * SSH connection related cleanups * Separate negative tests for test_fixed_ips * Add negative tests for network * Fix cinder test cases when cinder extensions are in use * Use rand_uuid and remove unused client for clean-up * Fix the help message for run_ssh * Fix three accidentally formatted paragraphs * clean up invalid_multibyte test * provide a valid utf8 multibyte test for nova images * Refactor network client * port certificates tests into nova v3 part2 * port certificates tests into nova v3 part1 * port instance_usage_audit tests into nova v3 part1 * port related volumes tests into nova v3 part2 * port related volumes tests into nova v3 part1 * add some tests for aggregates * add some negative tests for flavor * Add volume extensions tests * Add Savanna client for node group templates * Add Savanna-related configs for testing * Adds ping method to remote client * Separate negative tests for test_availability_zone * Test for the agent management extension API * scenario/network_basic_ops: detach floating-ip * Make negative snapshot tests faster * Move common wait_for_image_status from compute images_client to waiters * Set pipefail for wrapper scripts * actually turn on neutron cli tests * Add whitelist entry for s-proxy 'Timeout talking to memcached' * don't fail on dirty logs with grenade * Run test_service_type_management test only if extension is available * Fix failures when l3_agent_scheduler ext is not available * Add a control point for floating IP assignment * Add new env variable to specify test path * Remove duplicate negative test of flavor_id * port test_keypairs into nova v3 part1 * avoid resource leaks in keypairs tests * remove a spurious wait that could get us into trouble * Only initialize the glance_http if service is enabled * add both v2 and v3 tests for get specified extension * Wait for backup images to be ACTIVE in test_create_backup * Make the wait_for_server_status timeout message a bit more clear * Tighten ERROR regexp in log checker * Don't have tox install pre-release software * Fix the scope to share a server between tests * Add testcases for security groups * Change unstable test which gets console output * Heat: check response fields * Make Heat's non_empty_stack usable without a server * Fix Neutron VPNaaS Test * Remove generic_setup_package() function * Updated from global requirements * Add tests for keystone OS-TRUST v3 API * Separate negative tests for list_floating_ips * Fix the upper values of test_network_quotas * Add the external gateway interface to vpn router * Race condition in ListImageFilters tests * Separate negative tests in flavors/test_flavors * Add config options for enabled extensions * Handle rest client 500 response if non-json body * Fix display_name of volume for test_volumes_list * Test for the update extra route * Modify the name of a negative test class * add admin server tests * Add testcases for volume * Moves negative tests from api/compute/servers/test_virtual_interfaces * Separate negative tests for test_services in Tempest * remove test_service_enable_disable in nova V3 tests * port test_server_rescue into v3 part2 * Add mock to test-requirements.txt * Update tempest hacking regarding unit tests * Remove redundant whitelist for DHCP agent * VPNaas IKE policies tests * Check HTTP response headers of Swift middleware API in detail * Skip autoscaling test until more reliable * Move import to import block again * Fix a minor error in method _get_alternative_flavor * Add tests for testing swift slo middleware * port test_simple_tenant_usage into nova v3 part2 * port some flavor tests into nova v3 part1 * remove the test: test_service_enable_disable * Remove unused allow_tenant_reuse flag * update to hacking 0.8 * Adds paramiko logs to console output * Rip out the coverage extension client from tempest * port test_server_metadata and test_server_personality into v3 part1 * port test_quotas into v3 part1 * Negative tests separate file for floating_ips_actions * port some server tests into nova v3 part1 * Add internal testing for the stress test framework * port test_server_rescue into v3 part1 * add some test for force delete: * Fix rebuild_server() function * Adds "list hosts" test case - Cinder * Adds improvements to the Swift TempURL test * Use correct types for thresholds * Revert "Add test_cases for cinder cli in v2 version" * Start failing logs with errors except neutron * Fix file print logic bug and update whitelist * port test_hypervisor into nova v3 part2 * Moves negative tests from api/compute/keypairs/test_keypairs * Some tests for dhcp agent scheduler * Support Neutron security groups in scenario testing * More stuff in the whitelist * Dump all error messages for neutron * Add missing "interface" argument to AltManager * Use channel_timeout for SSH connection timeout * Add two more tests for Swift staticweb middleware * Add some tests for os_update_readonly_flag * Add test_cases for cinder cli in v2 version * Ensure no dangling resources are left if tests are skipped * port test_simple_tenant_usage into nova v3 part1 * port attach_interfaces and server_address tests into v3 part2 * Add hopefully last batch to the whitelist * Enable a uuid flavor * Improve the UX around sample config generation * Add hard reboot for outputting console log * Adds delete api test to glance * Check HTTP response headers of Swift API in detail * port servers_negative tests into v3 part1 * Add migrate negative tests * Enable the Nova V3 API Tests * Add sample config check to tox pep8 job * Move admin client initialization into stress-openstack * Cleanup using about the data_utils module and functions again * port test_availability_zone into nova v3 part2 * Cleanup using about the data_utils module and functions * Make sure ssh retries on paramiko.SSHException * Add test for Swift formpost middleware * Add test for update/reset_snapshot_status API * Avoid deleting ports assigned to router interfaces * Rename object storage features section * Enhance tests for flavor_extra_spec API * Skip test_create_backup * Disable V3 tests * Forced isolation for the tests in test_list_servers_negative.py * port instance_actions and server_list tests into nova v3 part2 * port instance_actions and server_list tests into nova v3 part1 * Don't use duplicate IP addresses * add some negative tests for security group: * Moves negative tests to test_images_oneserver_negative * add tests for server_password * Update openstack/common/lockutils * port test_hypervisor into nova v3 part1 * Add test cases for volume-transfer * Add some tests for security_group_rules api * Remove unused code in user test * Test for service type management * add role negative tests * Make smoke tests parallel * Add special serial smoke option * Adds negative tests to glance api's * port test_extensions into nova v3 part2 * Reuse a server instance in test_disk_config * Remove unused code in tempest files * Enable all nova v3 tests * Adds tests to cover Swift's crossdomain middleware * Fixing ImageKilledException raising * Add negative resize server action test * Added images support and existing config support * add tests for InstanceUsageAuditLog * port attach_interfaces and server_address tests into v3 part1 * Add shelve/unshelve test of nova APIs * add negative volumes tests * Remove unused code in api/compute/admin/test_quotas.py * Remove skips for bug 1182384 * removes a duplicate volume type test * add tests for certificates * Test image member is enforced * Added some tests for reserve and unreserve volume * Skip all nova v3 tests temporarily * Remove unused run_ssh variable * Add additional documentation for stress tests * port test_availability_zone into nova v3 part1 * port test_services into nova v3 part2 * port test_services into nova v3 part1 * port test_extensions into v3 part1 * Rename to create_test_server in API tests * removing dead docs * remove old README files that are now redundant * move nova v3 delete image tests into glance testing * Adapt scenario readme with real-life aspect * port test_images and test_server_actions into v3 part2 * Configure scenario clients with region * port test_images and test_server_actions into v3 part1 * remove redundant code in fixed_ips_client for xml * glance v2 image sharing tests * Test for l3 agent scheduler API * Add design principles to docs * Change all non-slow scenario tests to smoke * add positive tests for volume * Switch base unit test class to oslo mox fixture * Fix NameError exception * Make test_show_host_detail work with multi host installation * Add tempest unit test to verify the test list * remove skip bug 1233026 * Add missing CLI Neutron tests * add test for create_backup * add debugging for when changes-sinces fails * add tests for show and update of Flavor Extra Spec API extension * Add test case to api/compute/test_quotas * Adds api test to test_images * Sync config file generator from oslo * Fix default values so they work in a devstack run * Fix incorrect config option types * Add api version detection to verify_tempest_config * Add config feature verification script * Stop auto-detecting glance API versions * Reorganize project feature config options * Adds test to cover Swift healthcheck middleware * Stop testing deprecated command nova-manage instance_type * Test for flavor-access-list with private/public flavor * edit inheritence tree api/network/security_groups * Move decision_maker() call into setUpClass * Add base test class for unit tests * Update mailmap for Joe Gordon * Fix bad classname * add some negative tests for force_delete/restore * Replace assertLessEqual - is not in py26 testtools * assertTrue to assertIn * Raise specific exceptions on tearDownClass failure * Preserve the configured log level * Add some test_cases for glance_cli * Fix bad classname * Sync latest module versions from oslo-incubator * Sync fixtures from oslo and use LockFixture * Updated from global requirements * Update to latest pbr * Set max_template_size to heat's default value * Added some test for image tags * Early die if on image gets killed * Revert "Use isolation credentials for neutron api tests" * Add more stuff to the whitelist * RunTimeError on tearDownClass explained * add extend volume tests: * add tests for set_metadata in aggregate * fix DeletableSubnet in api/network/common * Refactor duplicate isolated creds code * api/network/security_groups_negative add testcases * Adds initial ceilometerclient testing code * add negative test cases for create snapshots: * Fix issue with ImagesOneServerJSON test * Use isolation credentials for neutron api tests * Simplify xml/json client selection * Add filenames to skip_tracker.py output * API tests for neutron router gateway * Use lower case true/false in the sample config file * use "" instead of "None" in xml file * Inject "-tempest-" string to rand_name * Forced isolation for the nova quota test * add BaseV2ComputeTest as the base class of nova v2 api tests * Changed the exception name * server status remained in unexpected state * Set tempest version for icehouse * Add a skip for meter-label cli tests * VPNaas vpnservice test cases * Separate negative tests api/network/security_groups * Skip negative compute quota tests that don't work with Neutron * uses skip_because where appropriate instead of regular skip * Fixing ImagesOneServerTestXml issue * add test for updating server's disk_config test * Skip get_server_diagnostics test until bug 1240043 is fixed * add some tests for host and seperate negative ones * Fix to use proper random values * add some tests for hypervisor operation * add volume tests * Reduce vm creations in negative tests * Change six to match global requirements * remove tearDownClass in test_quotas.py * add more test cases to api/network/security_groups * Add a flavor test for getting a specified key value API * Restores passing additional parameters to nosetest * Move negative action tests to right place * Script to filter logs for ERRORs based on whitelist * Rename cli tests from compute to nova * Add section for negative tests to HACKING.rst * Add some test-cases for cinder cli * add token test for token-api * add some negative tests for tenant * Test for server create with IPv6 address only * Accept gzip files in find_stack_traces.py * Add a create_router utility function * Update lockutils from oslo-incubator * Add security group to large_ops test * Use built in cleanup for servers in test_large_ops * Split out unit tests as separate tox jobs * Update to latest tox * glance image v1 member test cleaup * delete the duplicated variable definition * Increase heat's default max_template_size * add some tests for user api * Add CLI tests for Neutron's metering agent * Add some test_cases for compute-cli * skip test_lock_unlock_server * add some negative tests for volume updating: * remove code duplication in tempest/config.py * add volume list tests * Add addtional logging and catch exceptions * Placeholder log check script to be called by devstack-gate * Cleanup existing instances in setup for test_list_servers_negative.py * Fix skip_because decorator * Test for the nova diagnostics API * Use predictable instance/volume names in test_volumes_actions * Cleanup test_list_server_filters setup/teardown * add two negative tests for flavor-access * Remove unused CONF variable and import statement * add a negative test for flavor_extra_specs * introduces skip_because decorator * pass stop_on_error to _has_error_in_logs * Clean up existing instances when not using tenant isolation * Orchestration autoscaling improve status polling * Allow _status_timeout to be used for non-nova polling * TEMPEST_PY26_NOSE_COMPAT in py26 jobs * Handle test_list_servers_by_changes_since without tenant isolation * Add xml support to the floating ip and router * add server delete/unpause tests * add server suspend/resume negative tests * Revert "unskipping bug related to test_stamp_pattern.py" * Update style guide link in HACKING.rst * meta should be metadata in rebuild server * Unskip test_tokens and update expected status to 404 from 401 * Do not check for id in the keystone output * Initial basic setup of openstack and tempest config file * Make smoketests working again with Python 2.6/nose * Add test for admin deleting servers of others * add server reset_state tests * Heat: use first_address instead of first_private_address * Explicity specify network for heat slow tests * _error_in_logs function of driver.py shall check all nodes * Bump oslo.config version * _get_tenant_by_name doesn't return correctly * tempest/config.py parse some sections incorrectly * _create_creds function shouldn't call rand_name twice * Add tempest tests for os-aggregate update * Adds more test to cover Swift tempURL middleware * Added Negative tests for image member * driver.py should log errors when ssh operations fail * fix typo in config.py * add __str__ function to RestClient class * Fix problem with never stopping stress tests * Print the lekaed 'boto' tags on Failure * The debug configuration group is not registered * Update docs config * Add positive tests for os-floating-ip-pools * Handling network resources in tenant isolation * Don't log 'Not Found' ERROR during image cleanup * makes passing the client optional to utilities in scenario/manager.py * Add test for nova API of VM lock/unlock * Fix json version of bootable check * Add locking to test_list_hosts_with_zone * Move LockFixture to tempest.common * Fix typos in tempest/HACKING.rst Edit * README.rst of tempest stress is misleading * add server resizing tests * add negative volume tests * Added test to check list/show extensions-neutron * Adds test_update_all_metadata_field_not_included negative test * Replace OpenStack LLC with OpenStack Foundation * Test that Heat max_template_size is applied * Sync install_venv_common.py from oslo-incubator * Consider lc string for bootable True/False * unskipping bug related to test_stamp_pattern.py * Raise OS_TEST_TIMEOUT for heat slow test * Add tempest tests for os-host/{host-name} api * Test cases for V3 Project Actions * Skip test tokens * Remove claim that scenario tests need 2 services * Adding health monitoring test case * Adding LB member operation test cases * LBaaS client functions and pool testcases * Add update-volume test * Use built-in print() instead of print statement * Add 'Field' to the title of the Field Guides * Update README.rst to use testr instead of nose * fix pep8 errors: E231, E128 * Adds tests covering Swift's container quotas middleware * Add a function for creating a server on a given network * Move _check_tenant_network_connectivity to the end * Add update-snapshot test * Add keystone user-update test * Fix misused assertTrue in unit tests * Fix a test case of 35chars+ * assertEquals is deprecated, use assertEqual * Add "region" config for each service * Add parallel test execution section to HACKING.rst * Sync latest versions of oslo incubator * Fix overlapping subnet creation * test_fixed_ips unsafe xml/json resource sharing * Enable the quota test cases * Adding xml support for load balancer * Network load balancer testing * Fixes some typos in tempest * add required python modules(six,iso8601,fixtures) to requirements.txt * Fixes typos in tempest/api * Add missing import of 'subunit' in test-requirements.txt * Remove wait_for_image_resp_code * Use common create_server method for advanced_ops * VolumesListTest may hide configuration issues * Add tearDownClass to base swift test class * Adding negative test for port * Bulk Subnets and Ports creation * Make the admin role configurable within tempest * Remove Whitebox tests * Remove bin/tempest script * Add service tag section to HACKING.rst * Add hacking check for service tags in scenario * Add services tags to scenario tests * added an api test for security_groups * Adds disk_format parameter to upload_volume method in volumes client * task_state must be consider before many action * Dump basic network info in the test_network_basic_ops * Adds verfication for Bootable Volume * Translate server extension attributes to json * Add service tags to api.volume * Add @services decorator * Remove order dependence from network_basic_ops * Fix import grouping in scenario tests * Fix tempest config usage in test_attach_volume * Fix race condition for test_flavors_extra_specs * Eliminate the impact of "wait_until='BUILD'" * fix race condition between addCleanup and lockutils * Credentials Keystone V3 API Tests * Add a create_server test for flavor authorization * Add device name, ssh password to Tempest config * Fix colon in create volume logging output * Append some operations to boot from volume pattern * Added test case to check floating IP API operations * Switch heat template to use native nova resource * Add tests for Swift's StaticWeb middelware * fix test_flavors_extra_specs failure * Restrict Volume type deletion with volumes assoc * add flavor creation tests * Added missing xml tests of volume * raise assertion error if output is falsy * Add handling for inherited stress attributes * Remove unused LOG variable in scenario tests * Add more detailed message about the volumes missing * Increase default test timeout for timeout fixture * Log request ids from glance and nova * moves addCleanup few lines upper to avoid potential leftovers * Fix logging problem for stress test wrappers * KeyError when tearDownClass called from setUpClass * Add common "create_server_snapshot" method * Fix test_admin_catalog_list * Always log stdout and stderr of CLI commands * Create discovery option for stress tests * emit warning while running flake8 without virtual env * Switch run_tests.sh to run in parallel by default * Add unittest framework + tests for wrapper scripts * Move the network api tests to smoke * Add logging to the python-clients * removes self.fail as suggested by HACKING.rst * Rename heat logical_resource_id to resource_name * Add large-ops option to tox * Updating HACKING.rst * Stress ssh_floating test * Adds tests for Heat * Update requirements from global requirements * uniforms skip messages * Use common create_keypair method for autoscaling * Skip orchestration scenario tests if heat service not available * Switch gating tox jobs to testr parallel * Provide tox entry for running slow heat tests * close http connections * Fixed up a missing space in an error message * refactor - _is_timed_out using instance timeout * Fix ssh timeout issue * Heat autoscaling scenario test * cli: add messages to assertTrue * Cleanup: Add common "ssh-login server" method * Cleanup: Add common "create volume" method * use assertIsNotNone instead of assertNotEqual(*, None) * Skip os-fixed-ips test since neutron has not implemented it * Cleanup: Add common "create security rule" method * Add tenant isolation to the swift tests * Adding network api xml support * Set missing attribute self.server in _create_and_attach utility * Mismatch dictionary key in the process of parsing XML * Remove skip of neutron connectivity check * Fix skip tracker regex for multi-line skips * Add more tests for Swift Account Quota * Fixing format_flavor to handle flavor extra_specs * Cleanup: Add common "create keypair" method * Fixing the rest of the comment spacing issues * per test_method logging * Remove identity race condition * Protected matcher import * Cleanup: Add common "create_server" method * Fixed test for non-public flavor * Add nose to run_tests and tox for python 2.6 * skip test_list_servers_filtered_by_ip_regex on neutron gate * Fix typos in tempest/api/README.rst * Unit tests as stress tests * Cleanup try/except/finally blocks in several tests * setUpClass/tearDownClass full chain * Testcase to create bulk networks in one API call * Added 3 Routers related testcases for Neutron API * Adding extra_specs to flavor format * Add tenant isolation for scenario tests * Switch use of select() to poll() * Remove positive tag * Fix return code for pretty_tox_serial.sh * Fix posargs usage for tox jobs * Add tests for swift container listing filters * Switch to testr serial instead of nose * Rework class inheritance for scenario tests * Change logging in stress test * Added negative tests for server * scenario test involving glance, cinder and nova functionalities * Flag InstanceCfnInitTestJSON as the first slow heat test * Add test for swift ACLs * Adds tests covering Swift's Account Quota middleware * Remove duplicate image tests for tenant authZ * Skip more security group tests until bug 1182384 is fixed * Skip secgroup invalid name/desc tests until bug 1161411 is fixed * Make CLI timeout tests configurable * Added a test for stop and start a server * Add locks for all aggregates tests with hosts * Test cases for Roles V3 Actions * Add network api test cases * Whitebox server expects deleted > 0 * Negative tests added for network * Negative attribute added in server negative test * Skip secgroup rules invalid id tests until bug 1182384 is fixed * Make testr full runs skip tests with 'slow' tag * renames the stress test class to include the Volume keyword * Generate temepst API doc from source * White space after # in tempest/api files * White space after # in the tempest/services * White space after # in thirdparty * Disable logging to the stderr * add image tests v2 * Add argument to stop stress test on first error * Add testing of Neutron per tenant quotas API * Switching to oslo importutils in the stress tests * Make test_neutron_dhcp_agent_list_hosting_net use net name from conf * Sync up the default value for the network_for_ssh option * Sorting services list before asserting in test_get_service_list test * Negative test added for rescuing a paused vm * Add tox job for serial testr * Add eventlet to requirements.txt * Fix fail logic for server of another tenant test * Add locking to test_aggregates * Switch to using Oslo logging * Fix neutron cli tests skip for testr * Use state transition checker wait function in the ec2 image tests * Add wait_for_volume_status in upload_to_image test * Handle a possible volume attachment visibility wait/race issue * Use assertIn and assertNotIn instead of assertTrue/assertFalse * Add stress test to attach volumes to vm's * Negative attribute added in volume negative test * Revert "Add jsonschema to requirements.txt" * Allow to run swift and keystone api tests standalone * skip stamp test until race in nova can be debugged * Sync lockutils and log from oslo * Add environmental variables to test.conf * fix race condition in service list compares * Add posargs to testr-full tox job * Add Neutron CLI tests to tempest * Unskip test_register_get_deregister_ari_image * Add jsonschema to requirements.txt * Simplify whitebox/manager * Added a server-pause test * Negative test for rescuing a non-existent server * Increase ping timeout on scenario testing * Basic starter scenario for testing the dashboard * Fixing man page generation * Use nose skip exception conditionally * add service tests * Add tenant isolation for glance api tests * Move isolated credential code to BaseTestCase * Add isolated tenants for admin compute tests * Add exit codes if run_stress.py detects an error * Move heat_available option to service_available * Add nova config option to service_available group * Add swift config option to service_available group * Add glance to service_available config group * Move neutron_available option to service_available * Ignore the default quota values * Updating HACKING with some test writing recommendations * update hacking to 0.6 * Add global statistic for stress tests * Add scenario test of instance/volume snapshot * add image tests * Add cinder_available config option * Adds tests for tags in boto (EC2 API) * add missing apache2 license headers * fix use of locals() in strings * Skip test that is not relevant to Neutron * Add boto tests for idempotent RunInstances calls * Remove unneeded class filter from .testr.conf * Document E125 as a won't fix * Add unittest like output for testr-full in tox * Use subunit colorizer from nova for run_tests.sh * run_stress.py -h doesn't work without a connection * Remove duplicate flaky test * Remove skip code for test_servers_whitebox as bug had been fixed * Fix stress-tox-job.json action file * Switch to using testr as the test runner for everything non-gating * Rework stress to be more UnitTest like * Add an option to run_stress.py to run tests serially * Fix tox job for stress tests * Change path for "Run one test" example in README.rst * Make tenant_network_mask_bits default setting consistent * Remove version caps from python-fooclient * Cleanup and make HACKING.rst DRYer * Test Tokens - V3 * Fix SSH host to floating IP from fixed IP * Configure a heat security group for testing ssh * Add large_ops scenario test * improve minDisk checking * Add new test for the volume upload in Glance functionality * Add cinder CLI tests to tempest * Fixed test - segmented upload of file * Domain Actions Test Case-V3 * update Quantum to Neutron * Using relative path for personality file in rebuild server test * Sync install_venv_common from oslo * Add tests for server actions * Add exception handling doc * Postgresql is an Optional dependency * Use the same logger class in all test cases * add tests - list hosts tests using admin user * Proposing a change to remove user validation tests * Fixes a test case name * Add a tox job to run stress tests * Orchestration tests to use admin user, demo tenant * Use build_timeout & build_interval from heat config * Create stack in class setup, rather than instance * Implement update_stack client method * Always include credentials for heat stack create * Use print_function * Fix index link in footer bar * add tests - list servers using admin user * Clear keypairs on teardown * Heat test ssh to the server * Skip fixed_ip_details test with Quantum * Skip list_vifs test with Quantum * Revert "Update a test to work with Quantum SecGroup" * Use os.path.join to form the cli command * Skip test that is not relevant to Quantum * CLI doc missing colon for code block * Configurable volume storage_protocol + vendor_name * Add scenario test of instance snapshot and boot * Remove dependency on MySQL-python * Use Python 3.x compatible syntax constructs * Oslo Merge * Remove automated skip decsion from compute * Avoid potential race condition in list_stacks assert * Fix create_security_group_rule to work with python 2.6 * Fix test_volumes_get volumes cleanup * Add a volume create/delete stress test * Ensures testtools 0.9.32 * Actually raise BadRequest if create_subnet fails * Fix XML security group rule client * Loosen constraints on Swift status codes * Add posargs to tox coverage job * skip test_ec2_instance_run.InstanceRunTest.test_integration_1 * Object client adds content-length if PUT is empty * Fix a doc indentation bug * add create_image_from_server to base class for auto cleanup * Implement a new test case for volume cloning functionality * Remove TODO note * actually enable our no tempest/tests check * Raise BadRequest if unable to create subnet * Remove basic_auth strategy * Add negative tests label * Increase to 255 the length of the user name * Remove quota whitebox tests * Tempest Coding Guide * Quantum client should not be conditional * Fix: WhiteBox server leak * Tests for os-hypervisors API extension of Nova * Fixed Typos * Add logic to tox.ini to run all tempest tests * Enhance the validation of the quotas update * Fixes list_snapshots and _with_details methods in snap XML client * ensure no code merges to tempest/tests * Fixed last merge file from "tests" folder to new "api" folder * TestServerAdvancedOps server leaking * Multi-server handling in base.py * Fix a race condition in test_create_delete_image() * Remove duplicate appends to image list * Add keystone client optional arg tests * List servers by changes since with dynamic date * Default to m1.micro for heat test flavor * Add python-cinderclient to requirements.txt * Removed invalid skipped tests * Fixes the multi-backend skip bug and the cleanup steps * Remove executable bit on some files * Makes run_tests.sh exit code match the nosetests exit value * Unskipped object storage test * Added test - conditional object download * identity v3 token * List Domains Test Case-V3 * Rename requires files to standard names * Remove skip mark to test_create_image_when_server_is_rebooting * Removes 'positive' tag from tests * Adding test_server_sequence_suspend_resume * Implement minimum basic scenario * Add ssh check to quantum smoke test * Heat test to launch a heat-cfntools based instance * Merged 2 tests dependent on each other * Fixes bugs in test_s3_ec2_images * Test cases for Policy V3 API-New * Add Flake8 extension for python client import checks * update docs for consistency * Moved swift container cleanup to a class method * Configure logging format flexibly * Update skip_tracker test directory * Update README file * Modify hacking flake8 extension * Limit tests should pass correct image and flavor * Update a test to work with Quantum SecGroup * Set smoke/gate attributes for tests in "compute" * initial seed for tempest doc directory * Add some basic snapshots listing test * Initial heat orchestration tests * Add tests list tenant usage * Remove unnecessary tags/attributes from our tests * rename tests -> api * Smoke attribute implies gate attribute * Moved part of test cases to another class * break out whitebox tests * move boto tests to thirdparty directory - part 1 * add scenario directory * Create Flake8 extension for tempest checks * Code cleanup of object storage tests * Removing redundant, possibly flaky test * Add a sleep with back-off to retries * Add tests list the services to compute * Set smoke/gate attributes for tests in "image" * Set smoke/gate attributes for tests in "identity" * Set smoke/gate attributes for tests in "volume" * A Heat client which does basic stack operations * make test non executable, otherwise nose skips it * Permits a list of values for the "type=" tests attribute * Adding new test for iSCSI live block migration * Clean up tenants created in test_users.py * Adding negative test to check limits of Security Groups and rules * collapse tox.ini test commands for readability * move cli directory into new tree structure * make status_timeout a method * Enhance the validation of the quotas update * only install things in the venv for pep8 * proposal for tempest directory restructure * Do super()'s tearDowns last (bug 1178337) * Put the logic from devstack gate into tox * Remove old stress tests * Simplified stress tests * Enabling assertions disabled for bug #1074901 * Declare the config attribute in a simpler way * Configurable fixed network name * Add Aggregate XML client and tests * Make sure isolated tenants are not left behind * Add setup failure logging in tearDown method * change test_register_http_image to use explicit url * Ignore .testrepository/ * Adds Cinder Multi-Backend Test * Update service test case - V3 * Add a test for creation of volumes' snapshots * Remove reference to dead script configure_tempest.sh * Migrate to pbr from openstack.common.setup * Finish up flake8 conversion * Fixes volumes cleanup in base class * Removed duplicate usage of TempestConfig() * Fix status_timeout incorrectly referencing self * Added 2 user related testcases for Keystone V3API * Fix to enable negative test image_invalid_metadata * Missing image-del func in test_create_delete_image * Clean up servers created in test_multiple_create.py * Add tests list actions on a specified instance * Remove skips in test server rescue * Added tests to list instances by regexp * Adding test_max_image_meta_exceed_limit * Add tests list the availability zone * Make CLI tests python2.6 compatible * Exclude etc/logging.conf from versioning * fix E122 and E126 flake8 issues * attempt to get to flake8/hacking plugins * Add fixed ip client and tests * Fix to create an image with given min_ram value * Add logging configuration * Add aggregate json client and tests * Configure quantum basic ops tests as smoke * Fix to use an API to get the default quota set * raise the first exception in flavors and security_group test * Fix HACKING compliance of test_network_basic_ops * Remove smoke.py and clean up base test classes * make stack traces tool find individual traces * Re-enable detach volume from unrescued VM * Fix docs for admin user config in conf sample * Create tests for multiple server creation * Fix AttachInterfacesTest error * Adds create volume from image test * xenapi:live-block-migration - fix XML client test * Add tests for adding/removing flavor access * Test cases for Endpoints V3 API * RestClient:keystone_auth hides requests and errors * Removes redundant tearDownClass methods * Fix IBM copyright strings * Set version to 2013.2 * Remove skips in quota tests * Fix typo for run_tests.sh -S option * Replace try/except/else with self.assertRaises * Add basic quota tests * Update test_networks.py to v2 of Quantum API * refine _get_isolated_creds * delete servers in setUpClass if ecxeption raised * Reduce chance of name collision for resources * cleanup resource in setUpClass if ecxeption raised * Add missing exceptions import * Remove unused variables * Add glance register image from http service test case * Use the same style checking everywhere * Xml Support for Image Test scripts * Addition of XML support to test_list_servers_negative * In CLI tests set merge_stderr to False by default * Fix SimpleReadOnlyNovaManageTest.test_flavor_list CLI test * Move tempest runtime dependencies to the pip-requires * Remove unused imports * Create and delete flavor as regular user * Make volume attach and detach rescue tests negative * Add basic read only glance cli tests * Add properties to CreateRegisterImagesTest case * enable xml tests test_disk_config * Update hacking.py for @testtools.skip() formatting * Normalize skip bug format * Refactor of test_network_basic_ops -prep new tests * correct the reduplicate tests for list_severs_with_detail({'limit':1}) * Remove skips in volume types tests * Adds XML support to test_live_block_migration.py * Remove skips from bugs marked as fixed * add test for creating a floating IP specifying an non-existent pool * Updating the try/except blocks to assertRaises * Add bug number for skips in CLI tests * Add basic Keystone CLI tests * Add service cleanup handler for test_list_services * clean up trys in test_servers * Replace try/except/else with self.assertRaises * Extend compute-manage cli tests * Make skip_tracker bug keyword detection more robust * Expand CLI test * Tweak quotas in tempest to include new fixed ip quota * Change server create to use tracked create_server * Convert try/expect/else as per new Tempest style * fix the confused issue in server_client about list_server * Fix exception name in test_server_actions * Fix skip formats to trigger skip_tracker * Remove unnecessary asserts from test_images.py * Add api version detection support to glance tests * xml_to_json should not convert xmlns into attribute * Add base classes for image tests * Convert try/expect/else as per new Tempest style * Add a test to list the security group rules * Disable test_rescued_vm_attach_volume * Replace try/except/else with self.assertRaises * add find_stack_traces tool * Remove skip decorator from keypairs tests * Switch to final 1.1.0 oslo.config release * Add duplicate bug detection to skip_tracker.py * fix sever not deleted issue in test_attach_detach_volume * Replace try/except/else with self.assertRaises * Fix rate limit handling and logging * Tests to verify Nova VM Rescue operations * Fix test_flavor_extra_specs * Remove skips for fixed bugs * Add image members tests * Use oslo.config-1.1.0b1 * Have paramico to register the event pipe in time * Remove skip from test_invalid_host_for_migration() * Add glance api v2 testing * RestClient remove wait parameter from the get method * Small fixes around variable usage * Enable XML testing for test_server_addresses * Correct getchildren() usage in list_addresses() * Adding test_security_group_rules_create_with_invalid_port_range * Adding test_delete_the_default_security_group negative test * Move glance image client and tests into v1 dirs * skip ec2 test until it can be debugged * Add tests for nova's os-attach-interfaces extension * Add quantum_available config option * Update live migration test to use new syntax for create_server * enable test_servers_negative * Fix endpoint usage for glance_http in image client * Sync latest setup.py from oslo * Add negative test in test_quotas.py * Update defaults for s3 materials paths/names * Catching new exception while disassociating a disassociated floating ip * More assertions for test_integration_1 * Add basic image filtering tests * add the version requirement for testtools * Standardises expected exception layout * convert to resource tracked create_server * create_server cleanup * test_server_metadata.py - BP add-xml-support * add database drivers for whitebox testing * Sync latest install_venv_common.py * Update stress tests to properly use tempest.config * Prepare base test class for CLI tests * Implements test_update_all_metadata_field_error * Catching new exception while disassociating a disassociated floating ip * Add tests for server metadata * Move the console tests to the other server actions tests * Simplify xml-json inheritance in identity * test_live_block_migration cleanup * Implement assertRaises assertions on all tests * Small server action code compression * Handle XML body of server's virtual interfaces correctly * tempest.tests.boto merge to tempest.testboto * Cleanup of identity/admin/test_users.py * Handle error in test_create_get_delete_service * Expand read only cli compute test * Add negative test for get server in test_servers_negative.py * Add negative test for set server metadata * Clean up logging from glance_http.py * Have all test case to use a single base class * Simplify xml-json inheritance in compute * update identity to handle new table attributes * Fix update option for run_tests.sh * Compare ipv6 only with canonized form * Adding list_virtual_interfaces method to the servers_client * Stress improts * T4xx fixes * Add object based wait capability to boto tests * Fix compute tests init * Add an update option to run_tests.sh * Modify roles tests to deal with a default role * Imports in alphabetic order * Remove skipped test for bug 1061738 * By default the features are not skipped * Configurable Tempest config file location * First commit for python client test suite * Fix init of test_volume_type_extra_specs_list * Sync in latest version of oslo * Remove duplicate calls to clear_servers * Simplify volume test classes inheritance * Add missing import to the image_client * Remove unnecessary client alias in console tests * Correction in quota_client's condition logic * Fix 'if' in the clear_isolated_creds * json name usage * Update HACKING file * Fixes test_resize_server_(confirm|revert) methods * Add an images client * Break out RestClient error checking * Fixes around variable usage * Remove unused imports * Add negative test for create server * Change quota tests to use assertEqual * Remove duplicated wait * Convert to use tempest attr implementation 2/2 * Convert to use tempest attr implementation 1/2 * Fix MismatchError error when checking flavor value * Implements test_(create|update)_metadata_key_error * Implements test_flavors.test_invalid_min(Ram|Disk)_filter * Enable test_absolute_limits.test_absLimits_get * Add generic nose/testtools attr decorator * Add support for testrepository * Implements test_flavors.test_invalid_is_public_string * Change test_get_default_quotas to use assertEqual * Sync latest install_venv_common from oslo * Implements test_flavors.test_is_public_string_variations * Removes use of nose.tools.raises * Small Bug fixes * Verbose logging on error * Remove variable assignment that appears twice * Add a volume from snapshot test case * Make isolated volume tests have unique tenant * clean the unittest2 * Fixes "not in" usage * Fixes duplicate "-s" option in run_tests.sh * Use real capabilites for all volume type instead of fake ones * Move out http response checking from the request * Adds unitest2 and keyring to pip-requires * Fix boto initialization * Fix volume XML tests * Proposed EC2 OpenStack extension * Remove use of detailed-errors nose plugin * Removes assertGreaterEqual for Python 2.6 backward compat * Enable boto keyapir test * Testcase for keystone - List services * bug 1110343 Fix missing config.network.username * Derivate most of the RestClient's exception from the failureException * Update TEMPEST_README.txt * Move the singleton to a common location * Addition of XML support to test_quotas.py * Use install_venv_common.py from oslo * Fix Py2.6 dict comprehension SyntaxError * Handling rate-limit for JSON request- rest_client * Use testtools as the base testcase class * Add whitebox section * Add NotImplementedError to the abstract method * Remove not used configuration variables * Fixes whitebox testing for deleted type change * Credentials Configuration changes * update whitebox testing for deleted type change * Remove few unnecessary backslashes in ObjectTest * Ensure package-wide test init is done with testr * Flavor min memory tests * Test to check container synchronization * Fixes test name typo * TestCase to check set/get/unset flavor extraspecs * Add back missing return in ObjectClient * bug 1101184 add new test: verify new n/w visible * Fix flavors tests so they can be run in parallel * Test to upload object in segments and download it * Fix PEP8 compliance problems * Refactor identity * change ipv6 format to pass on postgresql * Revert "Split up XML and JSON testing." * Split up XML and JSON testing * Test to check temp url support * Fix install_venv-get_distro failure on Fedora * Only create 1 server for server actions tests * Allows identity endpoint to be specified as URI * Add skips for tests when dependency not present * Adds setting to disable SSL cert validation * Removes paramiko dependency from test-requires * Object write/read ACL and few security testcases * Fix test_authentication_with_invalid_tenant * Fix test_authentication_when_tenant_is_disabled * Add CentOS-specific OpenSSL package installation * Remove skips that are no longer necessary * boto: instance teardown wait until instance is gone * Don't ignore exceptions * ensure setup_test_user has been called before using test_user * Convert these tests of ServerBasicOps into one test * make skip_tracker directly executable * Refactor compute image tests * Use '-m' for tempest_coverage.py in tox.ini * Remove skip from Server Filters Tests * Fix list images xml deserialization * Add missing methods to xml admin_client * ensure we wait for server deletes * Disable test_run_terminate_instance * Use real capabilites for volume type instead of fake ones * Add wait for resource deletion on volume teardown * Update gitignore because of oslo setup.py * Modified Server Actions Create Image test * Fix tenant leaking in test_tenants.py * Adds 3 additional tests to test_flavor.py script * Add tools/tempest_coverage.py script * Fix parsing of addresses. lp#1074039 * Fix T401 and T402 errors * tempest error codes should start with T * Fix boto lib config * ensure isolated test cases run with an isolated tenant * add hacking.py rule to prevent docstrings * fix file injection test to not assume /etc is present * convert docstrings to comments * convert docstrings to comments * convert docstrings to comments * convert docstrings to comments * Fixes PEP8 error E121 * Fix venv for ./run_tests.sh -p * Use oslo cfg module for tempest config.py * Initial Oslo sync for Tempest * Add python-quantumclient to pip-requires * add run_tests.sh option to not capture stdout * Addition of XML support to test_console_output.py * Logic in rest_client incorrect "resp.status=413" * Tests to check few object actions anonymously * Remove unused imports * Add error handling to smoke test cleanup * Do not limit the max versions in the requirements * Negative Cinder tests for Volume Types,extra specs * Fix sample conf for compatability with devstack * Specify region by name * Fix use of venv in Tempest * Test Case to check "swift object expiry" * RestClient: Don't hard code volume service name * Add smoke tests for quantum * Add admin credential config for network client * Fix smoke tests to delete resources synchronously * Test to GET public-readable container's object * ensure servers are deleted between tests * add create_server_with_extras * Check images by ids, not by count. lp#1088515 * Add num_retries configuration option * Tempest should ignore SSL certificate validation * Adds paramiko to pip-requires * Ensure we check for the right body * Add ability to skip disk_config tests regardless of extension status * Improve pep8 checks to be similar to those in nova * Fix pep8 failures in tests for Volume Types and extra specs * fix formatting errors to help debugging * Fix typo that causes NameError: global name 'exception' exception * Enable EC2 Create volume from snapshot * Enabling the tests of floating ip script test_floating_ips_actions.py that were skipped due to bug #957706 * Fix pep8 violations in stress tests * Assign TODO to committer * Fix use of except in tempest * Fix import order to comply with import ordering rules * Reenable security group related test case * Remove tempest.conf.tpl * Adds Cinder tests for Volume Types and extra specs * early failures would prevent cleanup * Make parameter list generation consistent using urlencode() * Check for the canonical form as well, either is valid * Skip tests broken by nova b u g 1086980 * Start making setup.py similar to other OpenStack Projects * Empty Body testing according to the RFC2616 * Remove unused configuration variables * Test Case to check "copy object across container" * Started consolidation of disk config tests * Fix and simplify arbitrary_string. lp#1085048 * Add swift object versioning test case * Simplify parse_image_id * Fixed potential unbound varialble errors on test failures * Don't try to cleanup volume that doesn't exist * Fix issue with 404 logs on wait for delete * Fix pep8 failures in test_ec2_security_groups.py * Don't log stack trace on S3/EC2 client errors * test_absolute_limits.py to check limits response * Adds a Quotas client for Nova * Spelling: executng=>executing * fix some typo * Add start of the EC2/S3 API testing to tempest * Adds JSON client for servers admin API * fix for Bug1078481 * use deleted=False instead of deleted=0 in queries * be specific about metadata too long error * make it possible to run only one test in tempest * exclude venv directories from local pep8 * Make assertion failures more informative * Added Swift tests: * account: delete account metadata * container: retrieve/delete container metadata * object: retrieve/copy(2 ways) object Syntax bug fix in container_client.py: return resp. body => return resp, body Fixed passing headers parameter in head method in rest_client.py: return self.request('HEAD', url, headers) Removed unused imports * Remove unnecessary test. Fixes bug 1072841 * Fix SyntaxError: invalid syntax - comma * Remove kong. Fixes bug 1052511 * General cleanup/organization of compute tests * flavors with disk sizes of 10 10 20 30 would fail unexpectedly before since flavor[1]['disk'] == flavor[0]['disk] * Initial add of Swift tests * Fix 'message' is not defined errors * Handle ImportError's when quantumclient is missing * Added missing import for SkipTest in test_authorization * Tempest tests to cover live-block-migration * Refactor list servers negative test and fix remaining gate bugs * Put skip at top level * Add XML support for test_security_groups.py and test_security_group_rules.py * Clean up pep8 E125 violations * Clean up pep8 E123 and E124 violations * Clean up pep8 E127 violations * Clean up pep8 E128 violations * Clean up pep8 E711 violations * Passing endpoint makes authenticate lazy so getting catalog fails * Add XML support for test cases under identity admin * Clean up pep8 E502 violations * Add XML support to the cinder client * make the rand_name value shorter * Update test_associate_already_associated_floating_ip * Add support to XML in images_client and its tests * Add XML support for test_list_floating_ips.py * Adds client API and tests for volume attachments * Fix test_rebuild_nonexistent_server * Refactor status_timeout() methods in tempest.test * Add XML support to the volumes tests * Add XML support to the volumes client * Add lxml to pip-requires * Add .tox to .gitignore * Replace glance.client with python-glanceclient * Fix ssh.Client retval and deadlock danger LP#1038561 * Fix test_create_server_with_unauthorized_image * Re-enable 3 flavor tests * Fix bug 1005397 * Fix undefined name 'nose' * Added missing imports in tempest.tests.compute.base * Fix forgotten import of `exceptions` * Provide more default clients for smoke tests * Comment out flakey failing tests * Adds Cinder client * Ensure token refresh. Fixes bug 1044316 * Add *.egg-info to .gitignore * Add XML support for extensions_client * Add XML support for flavors_client * Add XML support for test_attach_volume * Add XML support for test_security_groups.py * Prevent stale isolated tenants from blocking test runs * Add xml support to keypairs_client and its tests * Resolves lp#1042541 * Add XML support to the server personality test * Add XML support to the limits client * Fix XML formatting for create server personality * Add XML support for test_create_server.py * Add XML support for test_server_actions.py * Add XML support for test_list_server_filters.py * Add XML support for test_servers.py * Add XML support for test_images.py * Add an xml/servers_client.py implementation * Attempt to clean up any servers we left behind after a test * Add a RestClient variant that sends and expects XML * Skip tests that are causing tempest gate to fail * Fixes LP Bug# 930482 - Test for security -tenanid by pass * Skip whitebox tests until they are fixed * Resolves lp#1033757 * Match name of test class to filename. Fixes bug 1006193 * Addresses lp#1004971 * Addition of base Smoke and Whitebox tests * Fixes bug 902402- Integration Testcases for Keystone - users, Roles, and tenants * Fixes Bug 1031639: admin_client.py- 'Assign and Remove role to user' points to a different URI * Tolerate set_admin_password not implemented * Fix for bug 1025552- Modifies test_servers_negative.py script * Fix Bug1029936 :SKIP TEST removal and change of Bug ID * Fix Bug1029334 :Skip Test removed from test_volumes_negative * Fix for Bug 1029792. Added Documentation Strings to test cases in test_console_output.py * fix for Bug 1029015.Added single quotes to remove unnecessary space in msg * Disable ConfigParser interpolation (lp#1025993) * Fix checks in server listing only lok for an ID * Remove skip for bug #984762 * Remove skips for resolved bugs and fixed some coding errors in tests * Fix logic on alt user tenant/password check * Fix an unbound variable in setup_package * Fix NameError in list image filters test * Adds a script for tracking bug skips in tempest * Optimized and reduced the scope of smoke tests * Add skip for disabled user test until associated bug is fixed * Fixes bug #1016042 - New tests for security groups * Add tests for volume attach and detach * Remove duplicate line cls.floating_ips_client = os.floating_ips_client from tempest/tests/compute/base.py * Refactor Tempest to be parallel-test friendly * Add BaseComputeTest.wait_for and use it to fix bug 1017932 * Skip slow/buggy soft reboot test until bug 1014647 is dealt with * Comment out broken test involving soft reboot * Step 1 of refactoring compute tests * Wait for resource deletion after 202 response. Fixes bug 1007447 * Fix LP #1006198 - Network tests should be skipped if no Quantum * Fixes lp:1003476. Adds negative tests for images * Setup Nose multiprocessor config for: * Fixes bug 1006405-Additional test cases to be added to test_volumes_negative.py * Wait for server to be deleted before reboot/rebuild. Fixes bug 1006586 * Fixes bug 992088- Testcases for Console Output and one test case to test_authorization.py * Deleted flavors can be viewed ... but not listed * Adds admin tests for roles and roleRef API * First cut of Network client and positive tests * Fixes bug 992721- Metadata testcases for authorization testcases * Fixes bug 972130- Testcases to CREATE, GET, DELETE, FILTER volumes * Added an AddImageException to exceptions.py and modified images_client to use this exception rather than BuildErrorException * Fix floating ip tests by adding missing an import * Fixes Bug 1004138 - Fix for Bug 992275 Breaks on Python < 2.7 * Added tests for servers API as per bug/992299 * Fixes Bug 992167-Some new tests to be added to test_security_groups.py and test_authorization.py * Fixes Bug 992215-Some new tests to be added to test_images.py and test_authorization.py * Fixes lp:1002135. Minor re-factor to rest client * Fixes bug/999647. Adds negative tests for LIST and GET servers API * Adds instance_utils library and initial SSH tests * Fixes bug 992275-Some new tests to test_floating_ips_actions.py * Fixes bug 972673-Incorrect named parameters in BuildErrorException in test_volumes_list * Fixes bug 992149-Some new tests to test_keypairs.py and test_authorization.py * Fix LP #992228 - Test rebuild/reboot of deleted server * Adds negative tests for Identity Tenants API * Fixes bug/902405. Adds users tests and methods to admin client * Add resources for floating_ip, keypair, volume. Add floating_ip test * Skip blank role name test until resolved upsteam * Minor fixes and docstrings updates * Test cases for keystone tenant operations * Fixes LP #992640 - Volumes sometimes not cleaned * Moved parse_image_id() to data_utils * Fixed the LP bug 993754. Ensure that the server created in the test is destroyed in finally: block of the test * Fixed the LP bug 993739 * Adds key_name parameter in create_server * fix for bug 992877 * Ensures that floating IP created in test is destroyed in a finally: block * Fixed LP bug 991806. Ensures that floating IP created in test is destroyed in a finally: block * Adds an identity admin client and API tests for keystone roles * Fixes LP #992096 - Add configure_via_auth=False * Addresses lp#948243 - Tempest handles misconfig better * Remind user about log_level * fixed the bug 983856. Pep8 complient made * Added keypairs negative tests, removed unused client objects * bug 985867: remove conf_from_devstack in favor of devstack.git/tools/configure_tempest.sh * Clients subclass the RestClient to allow attributes to be overrided by each client and allow better code reuse * Do not assume network names * Don't pass None for any values in post body * Allow skip of disk_config tests that require resize * Don't pass disk config as None. Fix for bug 980119 * Add License to Tempest * Use `username' in ImagesConfig * Fixes LP 973338 - Add custom alt and admin manager * Adds config file template (for use in gate script) * Convert to UNIX line endings * Remove obsolete config file * Enabling flavor marker tests * Fixes lp##971527 * Properly handle skipping if no alt user * Fix and simplify reboot stress test. Add new env stuff * Refactor configuration setup and document config * Adds basic tests for disk config extension * Fixes bug 960864- Testcases for the action list Volumes and list Volumes with Detail * Avoid new bug 963248 * Fix unbound local variable 'password error * Generalize configuration for controller access * Fixes lp#960647 * Fixes bug 902374-Negative tests for Volumes * Addresses lp#940832 * Remove glance dependency. Fixes bug 944410 * Fixes bug 938953 parsing of image id * Fixes LP Bug# 955349 - No init file in compute tests * Fixes LP Bug#953450 - Remove vestigial ssh_timeout * Initial checkin of Stress Test for nova * Lowercase boolean configs before comparison * Addresses lp#942382 - refactor configuration for clarity * Intermediate improvement of Tempest quickstart * Narrow race in wait_for_server_status() * Redrive rate limited API calls * test_rebuild_server tolerant of imageRef as URI * Fixes lp#945419 - use_ssl value is ignored * Fixes lp#945803 * Remove trailing whitespaces in regular file * Pass credentials to glance client * Fixes bug 902352 – New tests for security groups * Addresses lp#902371 * Fixed Bug#943092 * Fixes lp#940532 * Resolves lp#941705 * Addresses lp#933845 * Changed config to use catalog type instead of catalog name * Removed expected failure from negative access IP tests * Fix hardcoding of status bug * Skip test that is failing due to nova bug 940500 * Fix for bug 931712. Make keypair test work * Fixes lp#932320 * Made catalog name configurable * Make floating ip test work. Fixes bug 929765 * Fix for critical part of bug 929765 * Fixes LP#921409 * Adds /servers filter tests * Re-ordered resource building in fixtures to improve execution time * Fixes LP Bug#903977 - Boundary tests for list servers * Fixes LP#922784 * Removed duplicate test * Re-ordered resource allocation * Addresses LP#917976 * Adds basic logging when exceptions occur * Fixes LP#902358 - Test case for Floating IPs * Fixes LP#903978 - Write testcases for test_server_actions (boundary) * Added flavor filter tests: lp899979, lp899980, lp899982 * Modified flavor service to return results in line with other services * Added bug flag to tests that are failing due to known issues * Fixes LP #920782 - Malformed request URL * Fixes LP#920812 - KeyError: 'overLimit' on 413 return * KeyPairsClient: Configure client to query nova service from Keystone catalog * Added Keypair extension (os-keypairs) client and tests LP#900139 * Adds Images API tests * Don't set multiple images if image_ref_alt is the same as image_ref. Fix typo in skip_unless_addr * Fixes LP Bug#903969 - Image metadata boundary tests * Rework exceptions in Tempest * import error on security groups clientfix 917867 * incorporated Jay's suggestion bug915695 security groups client - to list * Fix for bug917490 * Adds a wait for the image to become active before deleting it * Removed an invalid test Added nonexistant tests for list images operations * Minor style related change in strings Changed indentation to 4 space width Added nonexistant tests for server metadata operations * LP Bug#914770 - NameError in test_images * Patch4 corrected typo - for server. not changing try except block Patch3 removed wait time Patch 2. Incorporated Jay's review comments fix for bug903970 create image from deleted server. Verified Pep8 formats. Verified that the test run fine . No image created * Fixes LP Bug #912596 - image_ref_alt not found * Added test_create_server_metadata_key_too_long * Create a Tempest conf from a Devstack env * RestClient to target specific services in Keystone catalog * Added filter tests to list images tests, addresses lp bug 900088 * Fixed issue with white space after pep8 review Code review changes for Fixes for lp:903989 Change-Id: Ic345f0b30f24764a6f933684577323042fdeb8aa * Negative test for Flavor - testcase bug 903967. Test run successfully fixed Pep8 issues. Ran Pep8, and it is fine now. Change-Id: I23f04adcbffef4ec67a996e406aec544fa2deb5b * Code review changes for Bugfix for lp904407, /extensions tests * Fixing revert/confirm resize tests * Fixes lp:903466 * General test cleanup * Fixes Bug lp:900910 * Fixed bug 902058 (review comments fixed) * PEP8 fixes * Removed storm references from README.rst * Daryl's latest renaming commit needed some fixes * Changes the namespace from storm to tempest, as well as adding addition tests and improvements * Changed namespace from storm to tempest * Added absolute limits service and server personality file tests * Optimized run time for image metadata tests * Added additional assertions for create server and rebuild server tests * Removed any SSH verification until further decisions are made * Fixes LP Bug#899383 - Cleanup config file search * * Added build_url() utility that returns an endpoint URL based on config parameters * Updated storm.conf * Added more properties to Nova config object * Fixed pep8 and the 'set' typo that came from a vi editor 'set list' fumble * Removed unnecessary 'self' reference * Update .gitreview with new project name * Added image metadata tests, fixed minor bug in servers service with metadata * Fix numeric header values for kernel_id and ramdisk_id * Added server metadata and image tests. Also added a README for storm.conf * Added negative tests for servers * Assert we receive a scoped token & the correct user * Removed unused imports; whitespace normalization * Documented availability of 'auth' tag * Added server details tests. Also re-added several files that somehow missed the initial commit * Fix rabbitmq login * Initial import of tests from the Zodiac project. On suggestion from Westmaas, imported tests under the nova directory (final naming TBD) to more quickly get them imported. To run these tests, execute 'nosetests nova/tests'. I've also only submitted the most stable of the tests. More to come * Tests were not passing for test_servers in Kong tests. Kong was expecting too much information back from the POST * Cleaning up kong.tests.test_server_actions * Adding missing kong.common.utils module * Removing link doctoring in test_images * Adding identity api v2.0 tests * Updating images tests * Further optimize kong.tests.test_servers * Removing unnecessary printing of config file * Removed non-testing suggestions * Stop kong/run_tests.sh from building venv each run * Relaxing FlavorsTest entity checking * Adding paramiko and unittest2 to pip-requires * Updating sample config with required values * The servers test deal with the new uuid params * Add .gitreview config file for gerrit * test supporting API v1.1 * Making run_test.sh python version and directory agnostic * Adding generic run_tests.sh * Add rfc.sh to help with gerrit workflow * Consolidate configuration some more * Make ServersTest.setUpClass just setUp to make sure we have read the config * Annotate old stacktester tests so that they get run by run_tests.sh --whatever * Output request to create server call for easier debugging * Output response from create server call for easier debugging * Make the use of a ramdisk optional for tests * Pull in changes from stacktester * Skip Rabbit tests if pika is not available * Initialise openstack.Manager() with config from self.nova * Make all tests inherit from the same base class * Import all the stacktester stuff as-is (s/stacktester/kong/, though) * Add __init__.py * Move everything under the kong/ namespace * PEP-8 compliance * Remove a bunch of unused imports * No need for xmlrpclib.Server * This is not exactly Kong * Add sample instructions for fetching images. Make sample config match these instructions * cleanup of self.asserts * cleanup self.asserts * moved sample_vm directory under include dir. Added image/kernel/initrd declaration to config file. Updated glance tests to reference config variables for image/kernel/initrd * Successfully deleting a server returns a 202 now instead of a 204 * Modified the logic to determine which ip to ping during build_check routine * Initial Release * Add README tempest-2014.1.dev4108.gf22b6cc/etc/0000775000175000017500000000000012332757136016653 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/etc/logging.conf.sample0000664000175000017500000000124112332757070022423 0ustar chuckchuck00000000000000[loggers] keys=root,tempest_stress [handlers] keys=file,devel,syslog [formatters] keys=simple,tests [logger_root] level=DEBUG handlers=file [logger_tempest_stress] level=DEBUG handlers=file,devel qualname=tempest.stress [handler_file] class=FileHandler level=DEBUG args=('tempest.log', 'w+') formatter=tests [handler_syslog] class=handlers.SysLogHandler level=ERROR args = ('/dev/log', handlers.SysLogHandler.LOG_USER) [handler_devel] class=StreamHandler level=DEBUG args=(sys.stdout,) formatter=simple [formatter_tests] class = tempest.openstack.common.log.ContextFormatter [formatter_simple] format=%(asctime)s.%(msecs)03d %(process)d %(levelname)s: %(message)s tempest-2014.1.dev4108.gf22b6cc/etc/tempest.conf.sample0000664000175000017500000006133412332757070022467 0ustar chuckchuck00000000000000[DEFAULT] # # Options defined in tempest.openstack.common.lockutils # # Whether to disable inter-process locks (boolean value) #disable_process_locking=false # Directory to use for lock files. (string value) #lock_path= # # Options defined in tempest.openstack.common.log # # Print debugging output (set logging level to DEBUG instead # of default WARNING level). (boolean value) #debug=false # Print more verbose output (set logging level to INFO instead # of default WARNING level). (boolean value) #verbose=false # Log output to standard error (boolean value) #use_stderr=true # format string to use for log messages with context (string # value) #logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s # format string to use for log messages without context # (string value) #logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s # data to append to log format when level is DEBUG (string # value) #logging_debug_format_suffix=%(funcName)s %(pathname)s:%(lineno)d # prefix each line of exception output with this format # (string value) #logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s # list of logger=LEVEL pairs (list value) #default_log_levels=amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,iso8601=WARN # publish error events (boolean value) #publish_errors=false # make deprecations fatal (boolean value) #fatal_deprecations=false # If an instance is passed with the log message, format it # like this (string value) #instance_format="[instance: %(uuid)s] " # If an instance UUID is passed with the log message, format # it like this (string value) #instance_uuid_format="[instance: %(uuid)s] " # The name of logging configuration file. It does not disable # existing loggers, but just appends specified logging # configuration to any other existing logging options. Please # see the Python logging module documentation for details on # logging configuration files. (string value) # Deprecated group/name - [DEFAULT]/log_config #log_config_append= # DEPRECATED. A logging.Formatter log message format string # which may use any of the available logging.LogRecord # attributes. This option is deprecated. Please use # logging_context_format_string and # logging_default_format_string instead. (string value) #log_format= # Format string for %%(asctime)s in log records. Default: # %(default)s (string value) #log_date_format=%Y-%m-%d %H:%M:%S # (Optional) Name of log file to output to. If no default is # set, logging will go to stdout. (string value) # Deprecated group/name - [DEFAULT]/logfile #log_file= # (Optional) The base directory used for relative --log-file # paths (string value) # Deprecated group/name - [DEFAULT]/logdir #log_dir= # Use syslog for logging. (boolean value) #use_syslog=false # syslog facility to receive log lines (string value) #syslog_log_facility=LOG_USER [baremetal] # # Options defined in tempest.config # # Catalog type of the baremetal provisioning service (string # value) #catalog_type=baremetal # Whether the Ironic nova-compute driver is enabled (boolean # value) #driver_enabled=false # The endpoint type to use for the baremetal provisioning # service (string value) #endpoint_type=publicURL # Timeout for Ironic node to completely provision (integer # value) #active_timeout=300 # Timeout for association of Nova instance and Ironic node # (integer value) #association_timeout=10 # Timeout for Ironic power transitions. (integer value) #power_timeout=20 # Timeout for unprovisioning an Ironic node. (integer value) #unprovision_timeout=20 [boto] # # Options defined in tempest.config # # EC2 URL (string value) #ec2_url=http://localhost:8773/services/Cloud # S3 URL (string value) #s3_url=http://localhost:8080 # AWS Secret Key (string value) #aws_secret= # AWS Access Key (string value) #aws_access= # AWS Zone for EC2 tests (string value) #aws_zone=nova # S3 Materials Path (string value) #s3_materials_path=/opt/stack/devstack/files/images/s3-materials/cirros-0.3.0 # ARI Ramdisk Image manifest (string value) #ari_manifest=cirros-0.3.0-x86_64-initrd.manifest.xml # AMI Machine Image manifest (string value) #ami_manifest=cirros-0.3.0-x86_64-blank.img.manifest.xml # AKI Kernel Image manifest (string value) #aki_manifest=cirros-0.3.0-x86_64-vmlinuz.manifest.xml # Instance type (string value) #instance_type=m1.tiny # boto Http socket timeout (integer value) #http_socket_timeout=3 # boto num_retries on error (integer value) #num_retries=1 # Status Change Timeout (integer value) #build_timeout=60 # Status Change Test Interval (integer value) #build_interval=1 [cli] # # Options defined in tempest.config # # enable cli tests (boolean value) #enabled=true # directory where python client binaries are located (string # value) #cli_dir=/usr/local/bin # Whether the tempest run location has access to the *-manage # commands. In a pure blackbox environment it will not. # (boolean value) #has_manage=true # Number of seconds to wait on a CLI timeout (integer value) #timeout=15 [compute] # # Options defined in tempest.config # # Allows test cases to create/destroy tenants and users. This # option enables isolated test cases and better parallel # execution, but also requires that OpenStack Identity API # admin credentials are known. (boolean value) #allow_tenant_isolation=false # Valid primary image reference to be used in tests. (string # value) #image_ref={$IMAGE_ID} # Valid secondary image reference to be used in tests. (string # value) #image_ref_alt={$IMAGE_ID_ALT} # Valid primary flavor to use in tests. (string value) #flavor_ref=1 # Valid secondary flavor to be used in tests. (string value) #flavor_ref_alt=2 # User name used to authenticate to an instance. (string # value) #image_ssh_user=root # Password used to authenticate to an instance. (string value) #image_ssh_password=password # User name used to authenticate to an instance using the # alternate image. (string value) #image_alt_ssh_user=root # Password used to authenticate to an instance using the # alternate image. (string value) #image_alt_ssh_password=password # Time in seconds between build status checks. (integer value) #build_interval=10 # Timeout in seconds to wait for an instance to build. # (integer value) #build_timeout=300 # Should the tests ssh to instances? (boolean value) #run_ssh=false # Auth method used for authenticate to the instance. Valid # choices are: keypair, configured, adminpass. keypair: start # the servers with an ssh keypair. configured: use the # configured user and password. adminpass: use the injected # adminPass. disabled: avoid using ssh when it is an option. # (string value) #ssh_auth_method=keypair # How to connect to the instance? fixed: using the first ip # belongs the fixed network floating: creating and using a # floating ip (string value) #ssh_connect_method=fixed # User name used to authenticate to an instance. (string # value) #ssh_user=root # Timeout in seconds to wait for ping to succeed. (integer # value) #ping_timeout=120 # Timeout in seconds to wait for authentication to succeed. # (integer value) #ssh_timeout=300 # Additional wait time for clean state, when there is no OS- # EXT-STS extension available (integer value) #ready_wait=0 # Timeout in seconds to wait for output from ssh channel. # (integer value) #ssh_channel_timeout=60 # Visible fixed network name (string value) #fixed_network_name=private # Network used for SSH connections. (string value) #network_for_ssh=public # IP version used for SSH connections. (integer value) #ip_version_for_ssh=4 # Does SSH use Floating IPs? (boolean value) #use_floatingip_for_ssh=true # Catalog type of the Compute service. (string value) #catalog_type=compute # The compute region name to use. If empty, the value of # identity.region is used instead. If no such region is found # in the service catalog, the first found one is used. (string # value) #region= # The endpoint type to use for the compute service. (string # value) #endpoint_type=publicURL # Catalog type of the Compute v3 service. (string value) #catalog_v3_type=computev3 # Path to a private key file for SSH access to remote hosts # (string value) #path_to_private_key= # Expected device name when a volume is attached to an # instance (string value) #volume_device_name=vdb # Time in seconds before a shelved instance is eligible for # removing from a host. -1 never offload, 0 offload when # shelved. This time should be the same as the time of # nova.conf, and some tests will run for as long as the time. # (integer value) #shelved_offload_time=0 # Allows test cases to create/destroy tenants and users. This # option enables isolated test cases and better parallel # execution, but also requires that OpenStack Identity API # admin credentials are known. (boolean value) #allow_tenant_isolation=false [compute-admin] # # Options defined in tempest.config # # Administrative Username to use for Nova API requests. # (string value) #username= # Administrative Tenant name to use for Nova API requests. # (string value) #tenant_name= # API key to use when authenticating as admin. (string value) #password= # Domain name for authentication as admin (Keystone V3).The # same domain applies to user and project (string value) #domain_name= [compute-feature-enabled] # # Options defined in tempest.config # # If false, skip all nova v3 tests. (boolean value) #api_v3=true # If false, skip disk config tests (boolean value) #disk_config=true # A list of enabled compute extensions with a special entry # all which indicates every extension is enabled (list value) #api_extensions=all # A list of enabled v3 extensions with a special entry all # which indicates every extension is enabled (list value) #api_v3_extensions=all # Does the test environment support changing the admin # password? (boolean value) #change_password=false # Does the test environment support resizing? (boolean value) #resize=false # Does the test environment support pausing? (boolean value) #pause=true # Does the test environment support suspend/resume? (boolean # value) #suspend=true # Does the test environment support live migration available? # (boolean value) #live_migration=false # Does the test environment use block devices for live # migration (boolean value) #block_migration_for_live_migration=false # Does the test environment block migration support cinder # iSCSI volumes (boolean value) #block_migrate_cinder_iscsi=false # Enable VNC console. This configuration value should be same # as [nova.vnc]->vnc_enabled in nova.conf (boolean value) #vnc_console=false [dashboard] # # Options defined in tempest.config # # Where the dashboard can be found (string value) #dashboard_url=http://localhost/ # Login page for the dashboard (string value) #login_url=http://localhost/auth/login/ [data_processing] # # Options defined in tempest.config # # Catalog type of the data processing service. (string value) #catalog_type=data_processing # The endpoint type to use for the data processing service. # (string value) #endpoint_type=publicURL [database] # # Options defined in tempest.config # # Catalog type of the Database service. (string value) #catalog_type=database # Valid primary flavor to use in database tests. (string # value) #db_flavor_ref=1 [debug] # # Options defined in tempest.config # # Enable diagnostic commands (boolean value) #enable=true # A regex to determine which requests should be traced. This # is a regex to match the caller for rest client requests to # be able to selectively trace calls out of specific classes # and methods. It largely exists for test development, and is # not expected to be used in a real deploy of tempest. This # will be matched against the discovered ClassName:method in # the test environment. Expected values for this field are: # * ClassName:test_method_name - traces one test_method * # ClassName:setUp(Class) - traces specific setup functions * # ClassName:tearDown(Class) - traces specific teardown # functions * ClassName:_run_cleanups - traces the cleanup # functions If nothing is specified, this feature is not # enabled. To trace everything specify .* as the regex. # (string value) #trace_requests= [identity] # # Options defined in tempest.config # # Catalog type of the Identity service. (string value) #catalog_type=identity # Set to True if using self-signed SSL certificates. (boolean # value) #disable_ssl_certificate_validation=false # Full URI of the OpenStack Identity API (Keystone), v2 # (string value) #uri= # Full URI of the OpenStack Identity API (Keystone), v3 # (string value) #uri_v3= # Identity API version to be used for authentication for API # tests. (string value) #auth_version=v2 # The identity region name to use. Also used as the other # services' region name unless they are set explicitly. If no # such region is found in the service catalog, the first found # one is used. (string value) #region=RegionOne # The endpoint type to use for the identity service. (string # value) #endpoint_type=publicURL # Username to use for Nova API requests. (string value) #username= # Tenant name to use for Nova API requests. (string value) #tenant_name= # Role required to administrate keystone. (string value) #admin_role=admin # API key to use when authenticating. (string value) #password= # Domain name for authentication (Keystone V3).The same domain # applies to user and project (string value) #domain_name= # Username of alternate user to use for Nova API requests. # (string value) #alt_username= # Alternate user's Tenant name to use for Nova API requests. # (string value) #alt_tenant_name= # API key to use when authenticating as alternate user. # (string value) #alt_password= # Alternate domain name for authentication (Keystone V3).The # same domain applies to user and project (string value) #alt_domain_name= # Administrative Username to use for Keystone API requests. # (string value) #admin_username= # Administrative Tenant name to use for Keystone API requests. # (string value) #admin_tenant_name= # API key to use when authenticating as admin. (string value) #admin_password= # Admin domain name for authentication (Keystone V3).The same # domain applies to user and project (string value) #admin_domain_name= [identity-feature-enabled] # # Options defined in tempest.config # # Does the identity service have delegation and impersonation # enabled (boolean value) #trust=true # Is the v2 identity API enabled (boolean value) #api_v2=true # Is the v3 identity API enabled (boolean value) #api_v3=true [image] # # Options defined in tempest.config # # Catalog type of the Image service. (string value) #catalog_type=image # The image region name to use. If empty, the value of # identity.region is used instead. If no such region is found # in the service catalog, the first found one is used. (string # value) #region= # The endpoint type to use for the image service. (string # value) #endpoint_type=publicURL # http accessible image (string value) #http_image=http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz [image-feature-enabled] # # Options defined in tempest.config # # Is the v2 image API enabled (boolean value) #api_v2=true # Is the v1 image API enabled (boolean value) #api_v1=true [input-scenario] # # Options defined in tempest.config # # Matching images become parameters for scenario tests (string # value) #image_regex=^cirros-0.3.1-x86_64-uec$ # Matching flavors become parameters for scenario tests # (string value) #flavor_regex=^m1.nano$ # SSH verification in tests is skippedfor matching images # (string value) #non_ssh_image_regex=^.*[Ww]in.*$ # List of user mapped to regex to matching image names. # (string value) #ssh_user_regex=[["^.*[Cc]irros.*$", "root"]] [negative] # # Options defined in tempest.config # # Test generator class for all negative tests (string value) #test_generator=tempest.common.generator.negative_generator.NegativeTestGenerator [network] # # Options defined in tempest.config # # Catalog type of the Neutron service. (string value) #catalog_type=network # The network region name to use. If empty, the value of # identity.region is used instead. If no such region is found # in the service catalog, the first found one is used. (string # value) #region= # The endpoint type to use for the network service. (string # value) #endpoint_type=publicURL # The cidr block to allocate tenant ipv4 subnets from (string # value) #tenant_network_cidr=10.100.0.0/16 # The mask bits for tenant ipv4 subnets (integer value) #tenant_network_mask_bits=28 # The cidr block to allocate tenant ipv6 subnets from (string # value) #tenant_network_v6_cidr=2003::/64 # The mask bits for tenant ipv6 subnets (integer value) #tenant_network_v6_mask_bits=96 # Whether tenant network connectivity should be evaluated # directly (boolean value) #tenant_networks_reachable=false # Id of the public network that provides external connectivity # (string value) #public_network_id= # Id of the public router that provides external connectivity # (string value) #public_router_id= # Timeout in seconds to wait for network operation to # complete. (integer value) #build_timeout=300 # Time in seconds between network operation status checks. # (integer value) #build_interval=10 [network-feature-enabled] # # Options defined in tempest.config # # Allow the execution of IPv6 tests (boolean value) #ipv6=true # A list of enabled network extensions with a special entry # all which indicates every extension is enabled (list value) #api_extensions=all [object-storage] # # Options defined in tempest.config # # Catalog type of the Object-Storage service. (string value) #catalog_type=object-store # The object-storage region name to use. If empty, the value # of identity.region is used instead. If no such region is # found in the service catalog, the first found one is used. # (string value) #region= # The endpoint type to use for the object-store service. # (string value) #endpoint_type=publicURL # Number of seconds to time on waiting for a container to # container synchronization complete. (integer value) #container_sync_timeout=120 # Number of seconds to wait while looping to check the status # of a container to container synchronization (integer value) #container_sync_interval=5 # Role to add to users created for swift tests to enable # creating containers (string value) #operator_role=Member # User role that has reseller admin (string value) #reseller_admin_role=ResellerAdmin [object-storage-feature-enabled] # # Options defined in tempest.config # # A list of the enabled optional discoverable apis. A single # entry, all, indicates that all of these features are # expected to be enabled (list value) #discoverable_apis=all [orchestration] # # Options defined in tempest.config # # Catalog type of the Orchestration service. (string value) #catalog_type=orchestration # The orchestration region name to use. If empty, the value of # identity.region is used instead. If no such region is found # in the service catalog, the first found one is used. (string # value) #region= # The endpoint type to use for the orchestration service. # (string value) #endpoint_type=publicURL # Time in seconds between build status checks. (integer value) #build_interval=1 # Timeout in seconds to wait for a stack to build. (integer # value) #build_timeout=1200 # Instance type for tests. Needs to be big enough for a full # OS plus the test workload (string value) #instance_type=m1.micro # Name of heat-cfntools enabled image to use when launching # test instances. (string value) #image_ref= # Name of existing keypair to launch servers with. (string # value) #keypair_name= # Value must match heat configuration of the same name. # (integer value) #max_template_size=524288 # Value must match heat configuration of the same name. # (integer value) #max_resources_per_stack=1000 [queuing] # # Options defined in tempest.config # # Catalog type of the Queuing service. (string value) #catalog_type=queuing [scenario] # # Options defined in tempest.config # # Directory containing image files (string value) #img_dir=/opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec # QCOW2 image file name (string value) #qcow2_img_file=cirros-0.3.1-x86_64-disk.img # AMI image file name (string value) #ami_img_file=cirros-0.3.1-x86_64-blank.img # ARI image file name (string value) #ari_img_file=cirros-0.3.1-x86_64-initrd # AKI image file name (string value) #aki_img_file=cirros-0.3.1-x86_64-vmlinuz # ssh username for the image file (string value) #ssh_user=cirros # specifies how many resources to request at once. Used for # large operations testing. (integer value) #large_ops_number=0 [service_available] # # Options defined in tempest.config # # Whether or not cinder is expected to be available (boolean # value) #cinder=true # Whether or not neutron is expected to be available (boolean # value) #neutron=false # Whether or not glance is expected to be available (boolean # value) #glance=true # Whether or not swift is expected to be available (boolean # value) #swift=true # Whether or not nova is expected to be available (boolean # value) #nova=true # Whether or not Heat is expected to be available (boolean # value) #heat=false # Whether or not Ceilometer is expected to be available # (boolean value) #ceilometer=true # Whether or not Horizon is expected to be available (boolean # value) #horizon=true # Whether or not Sahara is expected to be available (boolean # value) #sahara=false # Whether or not Ironic is expected to be available (boolean # value) #ironic=false # Whether or not Trove is expected to be available (boolean # value) #trove=false # Whether or not Marconi is expected to be available (boolean # value) #marconi=false [stress] # # Options defined in tempest.config # # Directory containing log files on the compute nodes (string # value) #nova_logdir= # Maximum number of instances to create during test. (integer # value) #max_instances=16 # Controller host. (string value) #controller= # Controller host. (string value) #target_controller= # ssh user. (string value) #target_ssh_user= # Path to private key. (string value) #target_private_key_path= # regexp for list of log files. (string value) #target_logfiles= # time (in seconds) between log file error checks. (integer # value) #log_check_interval=60 # The number of threads created while stress test. (integer # value) #default_thread_number_per_action=4 # Prevent the cleaning (tearDownClass()) between each stress # test run if an exception occurs during this run. (boolean # value) #leave_dirty_stack=false # Allows a full cleaning process after a stress test. Caution # : this cleanup will remove every objects of every tenant. # (boolean value) #full_clean_stack=false [telemetry] # # Options defined in tempest.config # # Catalog type of the Telemetry service. (string value) #catalog_type=metering # The endpoint type to use for the telemetry service. (string # value) #endpoint_type=publicURL [volume] # # Options defined in tempest.config # # Time in seconds between volume availability checks. (integer # value) #build_interval=10 # Timeout in seconds to wait for a volume to becomeavailable. # (integer value) #build_timeout=300 # Catalog type of the Volume Service (string value) #catalog_type=volume # The volume region name to use. If empty, the value of # identity.region is used instead. If no such region is found # in the service catalog, the first found one is used. (string # value) #region= # The endpoint type to use for the volume service. (string # value) #endpoint_type=publicURL # Name of the backend1 (must be declared in cinder.conf) # (string value) #backend1_name=BACKEND_1 # Name of the backend2 (must be declared in cinder.conf) # (string value) #backend2_name=BACKEND_2 # Backend protocol to target when creating volume types # (string value) #storage_protocol=iSCSI # Backend vendor to target when creating volume types (string # value) #vendor_name=Open Source # Disk format to use when copying a volume to image (string # value) #disk_format=raw # Default size in GB for volumes created by volumes tests # (integer value) #volume_size=1 [volume-feature-enabled] # # Options defined in tempest.config # # Runs Cinder multi-backend test (requires 2 backends) # (boolean value) #multi_backend=false # Runs Cinder volumes backup test (boolean value) #backup=true # Runs Cinder volume snapshot test (boolean value) #snapshot=true # A list of enabled volume extensions with a special entry all # which indicates every extension is enabled (list value) #api_extensions=all # Is the v1 volume API enabled (boolean value) #api_v1=true # Is the v2 volume API enabled (boolean value) #api_v2=true tempest-2014.1.dev4108.gf22b6cc/etc/schemas/0000775000175000017500000000000012332757136020276 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/0000775000175000017500000000000012332757136021752 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/servers/0000775000175000017500000000000012332757136023443 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/servers/get_console_output.json0000664000175000017500000000110412332757070030250 0ustar chuckchuck00000000000000{ "name": "get-console-output", "http-method": "POST", "url": "servers/%s/action", "resources": [ {"name":"server", "expected_result": 404} ], "json-schema": { "type": "object", "properties": { "os-getConsoleOutput": { "type": "object", "properties": { "length": { "type": ["integer", "string"], "minimum": 0 } } } }, "additionalProperties": false } } tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/flavors/0000775000175000017500000000000012332757136023426 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/flavors/flavor_details_v3.json0000664000175000017500000000016512332757070027726 0ustar chuckchuck00000000000000{ "name": "get-flavor-details", "http-method": "GET", "url": "flavors/%s", "resources": ["flavor"] } tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/flavors/flavors_list_v3.json0000664000175000017500000000110412332757070027431 0ustar chuckchuck00000000000000{ "name": "list-flavors-with-detail", "http-method": "GET", "url": "flavors/detail", "json-schema": { "type": "object", "properties": { "min_ram": { "type": "integer", "results": { "gen_none": 400, "gen_string": 400 } }, "min_disk": { "type": "integer", "results": { "gen_none": 400, "gen_string": 400 } } } } } tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/flavors/flavors_list.json0000664000175000017500000000110212332757070027017 0ustar chuckchuck00000000000000{ "name": "list-flavors-with-detail", "http-method": "GET", "url": "flavors/detail", "json-schema": { "type": "object", "properties": { "minRam": { "type": "integer", "results": { "gen_none": 400, "gen_string": 400 } }, "minDisk": { "type": "integer", "results": { "gen_none": 400, "gen_string": 400 } } } } } tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/flavors/flavor_details.json0000664000175000017500000000024512332757070027315 0ustar chuckchuck00000000000000{ "name": "get-flavor-details", "http-method": "GET", "url": "flavors/%s", "resources": [ {"name": "flavor", "expected_result": 404} ] } tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/admin/0000775000175000017500000000000012332757136023042 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/etc/schemas/compute/admin/flavor_create.json0000664000175000017500000000114712332757070026551 0ustar chuckchuck00000000000000{ "name": "flavor-create", "http-method": "POST", "admin_client": true, "url": "flavors", "default_result_code": 400, "json-schema": { "type": "object", "properties": { "name": { "type": "string"}, "ram": { "type": "integer", "minimum": 1}, "vcpus": { "type": "integer", "minimum": 1}, "disk": { "type": "integer"}, "id": { "type": "integer"}, "swap": { "type": "integer"}, "rxtx_factor": { "type": "integer"}, "OS-FLV-EXT-DATA:ephemeral": { "type": "integer"} } } } tempest-2014.1.dev4108.gf22b6cc/etc/whitelist.yaml0000664000175000017500000000000012332757070021536 0ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tox.ini0000664000175000017500000000635712332757070017423 0ustar chuckchuck00000000000000[tox] envlist = pep8 minversion = 1.6 skipsdist = True [testenv] setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./tempest/test_discover usedevelop = True install_command = pip install -U {opts} {packages} [testenv:py26] setenv = OS_TEST_PATH=./tempest/tests commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}' [testenv:py33] setenv = OS_TEST_PATH=./tempest/tests commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}' [testenv:py27] setenv = OS_TEST_PATH=./tempest/tests commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}' [testenv:cover] setenv = OS_TEST_PATH=./tempest/tests commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}' [testenv:all] sitepackages = True setenv = VIRTUAL_ENV={envdir} commands = bash tools/pretty_tox.sh '{posargs}' [testenv:full] sitepackages = True # The regex below is used to select which tests to run and exclude the slow tag: # See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610 commands = bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}' [testenv:full-serial] # The regex below is used to select which tests to run and exclude the slow tag: # See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610 commands = bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}' [testenv:testr-full] sitepackages = True commands = bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}' [testenv:heat-slow] sitepackages = True setenv = OS_TEST_TIMEOUT=1200 # The regex below is used to select heat api/scenario tests tagged as slow. commands = bash tools/pretty_tox.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}' [testenv:large-ops] sitepackages = True commands = python setup.py testr --slowest --testr-args='tempest.scenario.test_large_ops {posargs}' [testenv:smoke] sitepackages = True commands = bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}' [testenv:smoke-serial] sitepackages = True # This is still serial because neutron doesn't work with parallel. See: # https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke # job would fail if we moved it to parallel. commands = bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}' [testenv:stress] sitepackages = True commands = python -m tempest/stress/run_stress -a -d 3600 -S [testenv:venv] commands = {posargs} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt [testenv:pep8] setenv = MODULEPATH=tempest.common.generate_sample_tempest commands = flake8 {posargs} {toxinidir}/tools/config/check_uptodate.sh deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt [hacking] local-check-factory = tempest.hacking.checks.factory [flake8] # E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved. For further detail see https://review.openstack.org/#/c/36788/ ignore = E125,H302,H404 show-source = True exclude = .git,.venv,.tox,dist,doc,openstack,*egg tempest-2014.1.dev4108.gf22b6cc/requirements.txt0000664000175000017500000000102312332757070021355 0ustar chuckchuck00000000000000pbr>=0.6,!=0.7,<1.0 anyjson>=0.3.3 httplib2>=0.7.5 jsonschema>=2.0.0,<3.0.0 testtools>=0.9.34 lxml>=2.3 boto>=2.12.0,!=2.13.0 paramiko>=1.9.0 netaddr>=0.7.6 python-glanceclient>=0.9.0 python-keystoneclient>=0.8.0 python-novaclient>=2.17.0 python-neutronclient>=2.3.4,<3 python-cinderclient>=1.0.6 python-heatclient>=0.2.3 python-ironicclient python-saharaclient>=0.6.0 python-swiftclient>=1.6 testresources>=0.2.4 keyring>=2.1 testrepository>=0.0.18 oslo.config>=1.2.0 six>=1.6.0 iso8601>=0.1.9 fixtures>=0.3.14 testscenarios>=0.4 tempest-2014.1.dev4108.gf22b6cc/README.rst0000664000175000017500000001243112332757070017565 0ustar chuckchuck00000000000000Tempest - The OpenStack Integration Test Suite ============================================== This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Design Principles ---------- Tempest Design Principles that we strive to live by. - Tempest should be able to run against any OpenStack cloud, be it a one node devstack install, a 20 node lxc cloud, or a 1000 node kvm cloud. - Tempest should be explicit in testing features. It is easy to auto discover features of a cloud incorrectly, and give people an incorrect assessment of their cloud. Explicit is always better. - Tempest uses OpenStack public interfaces. Tests in Tempest should only touch public interfaces, API calls (native or 3rd party), public CLI or libraries. - Tempest should not touch private or implementation specific interfaces. This means not directly going to the database, not directly hitting the hypervisors, not testing extensions not included in the OpenStack base. If there is some feature of OpenStack that is not verifiable through standard interfaces, this should be considered a possible enhancement. - Tempest strives for complete coverage of the OpenStack API and common scenarios that demonstrate a working cloud. - Tempest drives load in an OpenStack cloud. By including a broad array of API and scenario tests Tempest can be reused in whole or in parts as load generation for an OpenStack cloud. - Tempest should attempt to clean up after itself, whenever possible we should tear down resources when done. - Tempest should be self testing. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. The easiest way to create a configuration file is to copy the sample one in the ``etc/`` directory :: $> cd $TEMPEST_ROOT_DIR $> cp etc/tempest.conf.sample etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running devstack environment, tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your devstack installation. Tempest is not tied to any single test runner, but testr is the most commonly used tool. After setting up your configuration file, you can execute the set of Tempest tests by using ``testr`` :: $> testr run --parallel To run one single test :: $> testr run --parallel tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server Alternatively, you can use the run_tempest.sh script which will create a venv and run the tests or use tox to do the same. Configuration ------------- Detailed configuration of tempest is beyond the scope of this document. The etc/tempest.conf.sample attempts to be a self documenting version of the configuration. The sample config file is auto generated using the script: tools/generate_sample.sh The most important pieces that are needed are the user ids, openstack endpoints, and basic flavors and images needed to run tests. Common Issues ------------- Tempest was originally designed to primarily run against a full OpenStack deployment. Due to that focus, some issues may occur when running Tempest against devstack. Running Tempest, especially in parallel, against a devstack instance may cause requests to be rate limited, which will cause unexpected failures. Given the number of requests Tempest can make against a cluster, rate limiting should be disabled for all test accounts. Additionally, devstack only provides a single image which Nova can use. For the moment, the best solution is to provide the same image uuid for both image_ref and image_ref_alt. Tempest will skip tests as needed if it detects that both images are the same. Unit Tests ---------- Tempest also has a set of unit tests which test the tempest code itself. These tests can be run by specifing the test discovery path:: $> OS_TEST_PATH=./tempest/tests testr run --parallel By setting OS_TEST_PATH to ./tempest/tests it specifies that test discover should only be run on the unit test directory. The default value of OS_TEST_PATH is OS_TEST_PATH=./tempest/test_discover which will only run test discover on the tempest suite. Alternatively, you can use the run_tests.sh script which will create a venv and run the unit tests. There are also the py26, py27, or py33 tox jobs which will run the unit tests with the corresponding version of python. Python 2.6 ---------- Tempest can be run with Python 2.6 however the unit tests and the gate currently only run with Python 2.7, so there are no guarantees about the state of tempest when running with Python 2.6. Additionally, to enable testr to work with tempest using python 2.6 the discover module from the unittest-ext project has to be patched to switch the unittest.TestSuite to use unittest2.TestSuite instead. See:: https://code.google.com/p/unittest-ext/issues/detail?id=79 tempest-2014.1.dev4108.gf22b6cc/.testr.conf0000664000175000017500000000071012332757070020161 0ustar chuckchuck00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \ OS_TEST_LOCK_PATH=${OS_TEST_LOCK_PATH:-${TMPDIR:-'/tmp'}} \ ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./tempest/test_discover} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list group_regex=([^\.]*\.)* tempest-2014.1.dev4108.gf22b6cc/setup.py0000775000175000017500000000202512332757070017611 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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 FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr'], pbr=True) tempest-2014.1.dev4108.gf22b6cc/run_tests.sh0000775000175000017500000001013512332757070020462 0ustar chuckchuck00000000000000#!/usr/bin/env bash function usage { echo "Usage: $0 [OPTION]..." echo "Run Tempest unit tests" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -n, --no-site-packages Isolate the virtualenv from the global Python environment" echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -u, --update Update the virtual environment with any newer package versions" echo " -t, --serial Run testr serially" echo " -p, --pep8 Just run pep8" echo " -c, --coverage Generate coverage report" echo " -h, --help Print this usage message" echo " -d, --debug Run tests with testtools instead of testr. This allows you to use PDB" echo " -- [TESTROPTIONS] After the first '--' you can pass arbitrary arguments to testr " } testrargs="" just_pep8=0 venv=.venv with_venv=tools/with_venv.sh serial=0 always_venv=0 never_venv=0 no_site_packages=0 debug=0 force=0 coverage=0 wrapper="" config_file="" update=0 if ! options=$(getopt -o VNnfuctphd -l virtual-env,no-virtual-env,no-site-packages,force,update,serial,coverage,pep8,help,debug -- "$@") then # parse error usage exit 1 fi eval set -- $options first_uu=yes while [ $# -gt 0 ]; do case "$1" in -h|--help) usage; exit;; -V|--virtual-env) always_venv=1; never_venv=0;; -N|--no-virtual-env) always_venv=0; never_venv=1;; -n|--no-site-packages) no_site_packages=1;; -f|--force) force=1;; -u|--update) update=1;; -d|--debug) debug=1;; -p|--pep8) let just_pep8=1;; -c|--coverage) coverage=1;; -t|--serial) serial=1;; --) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no ;; *) testrargs="$testrargs $1";; esac shift done cd `dirname "$0"` if [ $no_site_packages -eq 1 ]; then installvenvopts="--no-site-packages" fi function testr_init { if [ ! -d .testrepository ]; then ${wrapper} testr init fi } function run_tests { testr_init ${wrapper} find . -type f -name "*.pyc" -delete export OS_TEST_PATH=./tempest/tests if [ $debug -eq 1 ]; then if [ "$testrargs" = "" ]; then testrargs="discover ./tempest/tests" fi ${wrapper} python -m testtools.run $testrargs return $? fi if [ $coverage -eq 1 ]; then ${wrapper} python setup.py test --coverage return $? fi if [ $serial -eq 1 ]; then ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py else ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py fi } function run_pep8 { echo "Running flake8 ..." if [ $never_venv -eq 1 ]; then echo "**WARNING**:" >&2 echo "Running flake8 without virtual env may miss OpenStack HACKING detection" >&2 fi ${wrapper} flake8 export MODULEPATH=tempest.common.generate_sample_tempest ${wrapper} tools/config/check_uptodate.sh } if [ $never_venv -eq 0 ] then # Remove the virtual environment if --force used if [ $force -eq 1 ]; then echo "Cleaning virtualenv..." rm -rf ${venv} fi if [ $update -eq 1 ]; then echo "Updating virtualenv..." python tools/install_venv.py $installvenvopts fi if [ -e ${venv} ]; then wrapper="${with_venv}" else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv python tools/install_venv.py $installvenvopts wrapper="${with_venv}" else echo -e "No virtual environment found...create one? (Y/n) \c" read use_ve if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then # Install the virtualenv and run the test suite in it python tools/install_venv.py $installvenvopts wrapper=${with_venv} fi fi fi fi if [ $just_pep8 -eq 1 ]; then run_pep8 exit fi run_tests retval=$? if [ -z "$testrargs" ]; then run_pep8 fi exit $retval tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/0000775000175000017500000000000012332757136021253 5ustar chuckchuck00000000000000tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/PKG-INFO0000664000175000017500000001573412332757136022362 0ustar chuckchuck00000000000000Metadata-Version: 1.1 Name: tempest Version: 2014.1.dev4108.gf22b6cc Summary: OpenStack Integration Testing Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Tempest - The OpenStack Integration Test Suite ============================================== This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Design Principles ---------- Tempest Design Principles that we strive to live by. - Tempest should be able to run against any OpenStack cloud, be it a one node devstack install, a 20 node lxc cloud, or a 1000 node kvm cloud. - Tempest should be explicit in testing features. It is easy to auto discover features of a cloud incorrectly, and give people an incorrect assessment of their cloud. Explicit is always better. - Tempest uses OpenStack public interfaces. Tests in Tempest should only touch public interfaces, API calls (native or 3rd party), public CLI or libraries. - Tempest should not touch private or implementation specific interfaces. This means not directly going to the database, not directly hitting the hypervisors, not testing extensions not included in the OpenStack base. If there is some feature of OpenStack that is not verifiable through standard interfaces, this should be considered a possible enhancement. - Tempest strives for complete coverage of the OpenStack API and common scenarios that demonstrate a working cloud. - Tempest drives load in an OpenStack cloud. By including a broad array of API and scenario tests Tempest can be reused in whole or in parts as load generation for an OpenStack cloud. - Tempest should attempt to clean up after itself, whenever possible we should tear down resources when done. - Tempest should be self testing. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. The easiest way to create a configuration file is to copy the sample one in the ``etc/`` directory :: $> cd $TEMPEST_ROOT_DIR $> cp etc/tempest.conf.sample etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running devstack environment, tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your devstack installation. Tempest is not tied to any single test runner, but testr is the most commonly used tool. After setting up your configuration file, you can execute the set of Tempest tests by using ``testr`` :: $> testr run --parallel To run one single test :: $> testr run --parallel tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server Alternatively, you can use the run_tempest.sh script which will create a venv and run the tests or use tox to do the same. Configuration ------------- Detailed configuration of tempest is beyond the scope of this document. The etc/tempest.conf.sample attempts to be a self documenting version of the configuration. The sample config file is auto generated using the script: tools/generate_sample.sh The most important pieces that are needed are the user ids, openstack endpoints, and basic flavors and images needed to run tests. Common Issues ------------- Tempest was originally designed to primarily run against a full OpenStack deployment. Due to that focus, some issues may occur when running Tempest against devstack. Running Tempest, especially in parallel, against a devstack instance may cause requests to be rate limited, which will cause unexpected failures. Given the number of requests Tempest can make against a cluster, rate limiting should be disabled for all test accounts. Additionally, devstack only provides a single image which Nova can use. For the moment, the best solution is to provide the same image uuid for both image_ref and image_ref_alt. Tempest will skip tests as needed if it detects that both images are the same. Unit Tests ---------- Tempest also has a set of unit tests which test the tempest code itself. These tests can be run by specifing the test discovery path:: $> OS_TEST_PATH=./tempest/tests testr run --parallel By setting OS_TEST_PATH to ./tempest/tests it specifies that test discover should only be run on the unit test directory. The default value of OS_TEST_PATH is OS_TEST_PATH=./tempest/test_discover which will only run test discover on the tempest suite. Alternatively, you can use the run_tests.sh script which will create a venv and run the unit tests. There are also the py26, py27, or py33 tox jobs which will run the unit tests with the corresponding version of python. Python 2.6 ---------- Tempest can be run with Python 2.6 however the unit tests and the gate currently only run with Python 2.7, so there are no guarantees about the state of tempest when running with Python 2.6. Additionally, to enable testr to work with tempest using python 2.6 the discover module from the unittest-ext project has to be patched to switch the unittest.TestSuite to use unittest2.TestSuite instead. See:: https://code.google.com/p/unittest-ext/issues/detail?id=79 Platform: UNKNOWN Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/not-zip-safe0000664000175000017500000000000112332757136023501 0ustar chuckchuck00000000000000 tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/requires.txt0000664000175000017500000000102212332757136023646 0ustar chuckchuck00000000000000pbr>=0.6,!=0.7,<1.0 anyjson>=0.3.3 httplib2>=0.7.5 jsonschema>=2.0.0,<3.0.0 testtools>=0.9.34 lxml>=2.3 boto>=2.12.0,!=2.13.0 paramiko>=1.9.0 netaddr>=0.7.6 python-glanceclient>=0.9.0 python-keystoneclient>=0.8.0 python-novaclient>=2.17.0 python-neutronclient>=2.3.4,<3 python-cinderclient>=1.0.6 python-heatclient>=0.2.3 python-ironicclient python-saharaclient>=0.6.0 python-swiftclient>=1.6 testresources>=0.2.4 keyring>=2.1 testrepository>=0.0.18 oslo.config>=1.2.0 six>=1.6.0 iso8601>=0.1.9 fixtures>=0.3.14 testscenarios>=0.4tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/top_level.txt0000664000175000017500000000001012332757136023774 0ustar chuckchuck00000000000000tempest tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/dependency_links.txt0000664000175000017500000000000112332757136025321 0ustar chuckchuck00000000000000 tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/entry_points.txt0000664000175000017500000000012212332757136024544 0ustar chuckchuck00000000000000[console_scripts] verify-tempest-config = tempest.cmd.verify_tempest_config:main tempest-2014.1.dev4108.gf22b6cc/tempest.egg-info/SOURCES.txt0000664000175000017500000010074212332757136023143 0ustar chuckchuck00000000000000.coveragerc .mailmap .testr.conf AUTHORS ChangeLog HACKING.rst LICENSE README.rst openstack-common.conf requirements.txt run_tempest.sh run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini doc/source/HACKING.rst doc/source/conf.py doc/source/index.rst doc/source/overview.rst doc/source/field_guide/api.rst doc/source/field_guide/cli.rst doc/source/field_guide/index.rst doc/source/field_guide/scenario.rst doc/source/field_guide/stress.rst doc/source/field_guide/thirdparty.rst doc/source/field_guide/unit_tests.rst etc/logging.conf.sample etc/tempest.conf.sample etc/whitelist.yaml etc/schemas/compute/admin/flavor_create.json etc/schemas/compute/flavors/flavor_details.json etc/schemas/compute/flavors/flavor_details_v3.json etc/schemas/compute/flavors/flavors_list.json etc/schemas/compute/flavors/flavors_list_v3.json etc/schemas/compute/servers/get_console_output.json tempest/README.rst tempest/__init__.py tempest/auth.py tempest/clients.py tempest/config.py tempest/exceptions.py tempest/manager.py tempest/test.py tempest.egg-info/PKG-INFO tempest.egg-info/SOURCES.txt tempest.egg-info/dependency_links.txt tempest.egg-info/entry_points.txt tempest.egg-info/not-zip-safe tempest.egg-info/requires.txt tempest.egg-info/top_level.txt tempest/api/README.rst tempest/api/__init__.py tempest/api/utils.py tempest/api/baremetal/__init__.py tempest/api/baremetal/base.py tempest/api/baremetal/test_api_discovery.py tempest/api/baremetal/test_chassis.py tempest/api/baremetal/test_drivers.py tempest/api/baremetal/test_nodes.py tempest/api/baremetal/test_nodestates.py tempest/api/baremetal/test_ports.py tempest/api/baremetal/test_ports_negative.py tempest/api/compute/__init__.py tempest/api/compute/base.py tempest/api/compute/test_authorization.py tempest/api/compute/test_extensions.py tempest/api/compute/test_live_block_migration.py tempest/api/compute/test_live_block_migration_negative.py tempest/api/compute/test_quotas.py tempest/api/compute/admin/__init__.py tempest/api/compute/admin/test_agents.py tempest/api/compute/admin/test_aggregates.py tempest/api/compute/admin/test_aggregates_negative.py tempest/api/compute/admin/test_availability_zone.py tempest/api/compute/admin/test_availability_zone_negative.py tempest/api/compute/admin/test_fixed_ips.py tempest/api/compute/admin/test_fixed_ips_negative.py tempest/api/compute/admin/test_flavors.py tempest/api/compute/admin/test_flavors_access.py tempest/api/compute/admin/test_flavors_access_negative.py tempest/api/compute/admin/test_flavors_extra_specs.py tempest/api/compute/admin/test_flavors_extra_specs_negative.py tempest/api/compute/admin/test_flavors_negative.py tempest/api/compute/admin/test_flavors_negative_xml.py tempest/api/compute/admin/test_hosts.py tempest/api/compute/admin/test_hosts_negative.py tempest/api/compute/admin/test_hypervisor.py tempest/api/compute/admin/test_hypervisor_negative.py tempest/api/compute/admin/test_instance_usage_audit_log.py tempest/api/compute/admin/test_instance_usage_audit_log_negative.py tempest/api/compute/admin/test_migrations.py tempest/api/compute/admin/test_quotas.py tempest/api/compute/admin/test_quotas_negative.py tempest/api/compute/admin/test_security_groups.py tempest/api/compute/admin/test_servers.py tempest/api/compute/admin/test_servers_negative.py tempest/api/compute/admin/test_services.py tempest/api/compute/admin/test_services_negative.py tempest/api/compute/admin/test_simple_tenant_usage.py tempest/api/compute/admin/test_simple_tenant_usage_negative.py tempest/api/compute/certificates/__init__.py tempest/api/compute/certificates/test_certificates.py tempest/api/compute/flavors/__init__.py tempest/api/compute/flavors/test_flavors.py tempest/api/compute/flavors/test_flavors_negative.py tempest/api/compute/flavors/test_flavors_negative_xml.py tempest/api/compute/floating_ips/__init__.py tempest/api/compute/floating_ips/base.py tempest/api/compute/floating_ips/test_floating_ips_actions.py tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py tempest/api/compute/floating_ips/test_list_floating_ips.py tempest/api/compute/floating_ips/test_list_floating_ips_negative.py tempest/api/compute/images/__init__.py tempest/api/compute/images/test_image_metadata.py tempest/api/compute/images/test_image_metadata_negative.py tempest/api/compute/images/test_images.py tempest/api/compute/images/test_images_negative.py tempest/api/compute/images/test_images_oneserver.py tempest/api/compute/images/test_images_oneserver_negative.py tempest/api/compute/images/test_list_image_filters.py tempest/api/compute/images/test_list_image_filters_negative.py tempest/api/compute/images/test_list_images.py tempest/api/compute/keypairs/__init__.py tempest/api/compute/keypairs/test_keypairs.py tempest/api/compute/keypairs/test_keypairs_negative.py tempest/api/compute/limits/__init__.py tempest/api/compute/limits/test_absolute_limits.py tempest/api/compute/limits/test_absolute_limits_negative.py tempest/api/compute/security_groups/__init__.py tempest/api/compute/security_groups/base.py tempest/api/compute/security_groups/test_security_group_rules.py tempest/api/compute/security_groups/test_security_group_rules_negative.py tempest/api/compute/security_groups/test_security_groups.py tempest/api/compute/security_groups/test_security_groups_negative.py tempest/api/compute/servers/__init__.py tempest/api/compute/servers/test_attach_interfaces.py tempest/api/compute/servers/test_availability_zone.py tempest/api/compute/servers/test_create_server.py tempest/api/compute/servers/test_delete_server.py tempest/api/compute/servers/test_disk_config.py tempest/api/compute/servers/test_instance_actions.py tempest/api/compute/servers/test_instance_actions_negative.py tempest/api/compute/servers/test_list_server_filters.py tempest/api/compute/servers/test_list_servers_negative.py tempest/api/compute/servers/test_multiple_create.py tempest/api/compute/servers/test_multiple_create_negative.py tempest/api/compute/servers/test_server_actions.py tempest/api/compute/servers/test_server_addresses.py tempest/api/compute/servers/test_server_addresses_negative.py tempest/api/compute/servers/test_server_metadata.py tempest/api/compute/servers/test_server_metadata_negative.py tempest/api/compute/servers/test_server_password.py tempest/api/compute/servers/test_server_personality.py tempest/api/compute/servers/test_server_rescue.py tempest/api/compute/servers/test_server_rescue_negative.py tempest/api/compute/servers/test_servers.py tempest/api/compute/servers/test_servers_negative.py tempest/api/compute/servers/test_servers_negative_new.py tempest/api/compute/servers/test_virtual_interfaces.py tempest/api/compute/servers/test_virtual_interfaces_negative.py tempest/api/compute/v2/__init__.py tempest/api/compute/v3/__init__.py tempest/api/compute/v3/test_extensions.py tempest/api/compute/v3/test_live_block_migration.py tempest/api/compute/v3/test_live_block_migration_negative.py tempest/api/compute/v3/test_quotas.py tempest/api/compute/v3/test_version.py tempest/api/compute/v3/admin/__init__.py tempest/api/compute/v3/admin/test_agents.py tempest/api/compute/v3/admin/test_aggregates.py tempest/api/compute/v3/admin/test_aggregates_negative.py tempest/api/compute/v3/admin/test_availability_zone.py tempest/api/compute/v3/admin/test_availability_zone_negative.py tempest/api/compute/v3/admin/test_flavors.py tempest/api/compute/v3/admin/test_flavors_access.py tempest/api/compute/v3/admin/test_flavors_access_negative.py tempest/api/compute/v3/admin/test_flavors_extra_specs.py tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py tempest/api/compute/v3/admin/test_flavors_negative.py tempest/api/compute/v3/admin/test_hosts.py tempest/api/compute/v3/admin/test_hosts_negative.py tempest/api/compute/v3/admin/test_hypervisor.py tempest/api/compute/v3/admin/test_hypervisor_negative.py tempest/api/compute/v3/admin/test_migrations.py tempest/api/compute/v3/admin/test_quotas.py tempest/api/compute/v3/admin/test_quotas_negative.py tempest/api/compute/v3/admin/test_servers.py tempest/api/compute/v3/admin/test_servers_negative.py tempest/api/compute/v3/admin/test_services.py tempest/api/compute/v3/admin/test_services_negative.py tempest/api/compute/v3/certificates/__init__.py tempest/api/compute/v3/certificates/test_certificates.py tempest/api/compute/v3/flavors/__init__.py tempest/api/compute/v3/flavors/test_flavors.py tempest/api/compute/v3/flavors/test_flavors_negative.py tempest/api/compute/v3/images/__init__.py tempest/api/compute/v3/images/test_images.py tempest/api/compute/v3/images/test_images_negative.py tempest/api/compute/v3/images/test_images_oneserver.py tempest/api/compute/v3/images/test_images_oneserver_negative.py tempest/api/compute/v3/keypairs/__init__.py tempest/api/compute/v3/keypairs/test_keypairs.py tempest/api/compute/v3/keypairs/test_keypairs_negative.py tempest/api/compute/v3/servers/__init__.py tempest/api/compute/v3/servers/test_attach_interfaces.py tempest/api/compute/v3/servers/test_attach_volume.py tempest/api/compute/v3/servers/test_create_server.py tempest/api/compute/v3/servers/test_delete_server.py tempest/api/compute/v3/servers/test_instance_actions.py tempest/api/compute/v3/servers/test_instance_actions_negative.py tempest/api/compute/v3/servers/test_list_server_filters.py tempest/api/compute/v3/servers/test_list_servers_negative.py tempest/api/compute/v3/servers/test_multiple_create.py tempest/api/compute/v3/servers/test_multiple_create_negative.py tempest/api/compute/v3/servers/test_server_actions.py tempest/api/compute/v3/servers/test_server_addresses.py tempest/api/compute/v3/servers/test_server_addresses_negative.py tempest/api/compute/v3/servers/test_server_metadata.py tempest/api/compute/v3/servers/test_server_metadata_negative.py tempest/api/compute/v3/servers/test_server_password.py tempest/api/compute/v3/servers/test_server_rescue.py tempest/api/compute/v3/servers/test_server_rescue_negative.py tempest/api/compute/v3/servers/test_servers.py tempest/api/compute/v3/servers/test_servers_negative.py tempest/api/compute/volumes/__init__.py tempest/api/compute/volumes/test_attach_volume.py tempest/api/compute/volumes/test_volumes_get.py tempest/api/compute/volumes/test_volumes_list.py tempest/api/compute/volumes/test_volumes_negative.py tempest/api/data_processing/__init__.py tempest/api/data_processing/base.py tempest/api/data_processing/test_node_group_templates.py tempest/api/data_processing/test_plugins.py tempest/api/database/__init__.py tempest/api/database/base.py tempest/api/database/flavors/__init__.py tempest/api/database/flavors/test_flavors.py tempest/api/database/flavors/test_flavors_negative.py tempest/api/identity/__init__.py tempest/api/identity/base.py tempest/api/identity/admin/__init__.py tempest/api/identity/admin/test_roles.py tempest/api/identity/admin/test_roles_negative.py tempest/api/identity/admin/test_services.py tempest/api/identity/admin/test_tenant_negative.py tempest/api/identity/admin/test_tenants.py tempest/api/identity/admin/test_tokens.py tempest/api/identity/admin/test_users.py tempest/api/identity/admin/test_users_negative.py tempest/api/identity/admin/v3/__init__.py tempest/api/identity/admin/v3/test_credentials.py tempest/api/identity/admin/v3/test_domains.py tempest/api/identity/admin/v3/test_endpoints.py tempest/api/identity/admin/v3/test_endpoints_negative.py tempest/api/identity/admin/v3/test_groups.py tempest/api/identity/admin/v3/test_policies.py tempest/api/identity/admin/v3/test_projects.py tempest/api/identity/admin/v3/test_projects_negative.py tempest/api/identity/admin/v3/test_roles.py tempest/api/identity/admin/v3/test_services.py tempest/api/identity/admin/v3/test_tokens.py tempest/api/identity/admin/v3/test_trusts.py tempest/api/identity/admin/v3/test_users.py tempest/api/image/__init__.py tempest/api/image/base.py tempest/api/image/v1/__init__.py tempest/api/image/v1/test_image_members.py tempest/api/image/v1/test_image_members_negative.py tempest/api/image/v1/test_images.py tempest/api/image/v1/test_images_negative.py tempest/api/image/v2/__init__.py tempest/api/image/v2/test_images.py tempest/api/image/v2/test_images_member.py tempest/api/image/v2/test_images_member_negative.py tempest/api/image/v2/test_images_negative.py tempest/api/image/v2/test_images_tags.py tempest/api/image/v2/test_images_tags_negative.py tempest/api/network/__init__.py tempest/api/network/base.py tempest/api/network/base_routers.py tempest/api/network/base_security_groups.py tempest/api/network/common.py tempest/api/network/test_extensions.py tempest/api/network/test_extra_dhcp_options.py tempest/api/network/test_floating_ips.py tempest/api/network/test_fwaas_extensions.py tempest/api/network/test_load_balancer.py tempest/api/network/test_metering_extensions.py tempest/api/network/test_networks.py tempest/api/network/test_networks_negative.py tempest/api/network/test_ports.py tempest/api/network/test_routers.py tempest/api/network/test_routers_negative.py tempest/api/network/test_security_groups.py tempest/api/network/test_security_groups_negative.py tempest/api/network/test_service_type_management.py tempest/api/network/test_vpnaas_extensions.py tempest/api/network/admin/__init__.py tempest/api/network/admin/test_agent_management.py tempest/api/network/admin/test_dhcp_agent_scheduler.py tempest/api/network/admin/test_external_network_extension.py tempest/api/network/admin/test_l3_agent_scheduler.py tempest/api/network/admin/test_lbaas_agent_scheduler.py tempest/api/network/admin/test_load_balancer_admin_actions.py tempest/api/network/admin/test_quotas.py tempest/api/object_storage/__init__.py tempest/api/object_storage/base.py tempest/api/object_storage/test_account_bulk.py tempest/api/object_storage/test_account_quotas.py tempest/api/object_storage/test_account_quotas_negative.py tempest/api/object_storage/test_account_services.py tempest/api/object_storage/test_account_services_negative.py tempest/api/object_storage/test_container_acl.py tempest/api/object_storage/test_container_acl_negative.py tempest/api/object_storage/test_container_quotas.py tempest/api/object_storage/test_container_services.py tempest/api/object_storage/test_container_staticweb.py tempest/api/object_storage/test_container_sync.py tempest/api/object_storage/test_crossdomain.py tempest/api/object_storage/test_healthcheck.py tempest/api/object_storage/test_object_expiry.py tempest/api/object_storage/test_object_formpost.py tempest/api/object_storage/test_object_formpost_negative.py tempest/api/object_storage/test_object_services.py tempest/api/object_storage/test_object_slo.py tempest/api/object_storage/test_object_temp_url.py tempest/api/object_storage/test_object_temp_url_negative.py tempest/api/object_storage/test_object_version.py tempest/api/orchestration/__init__.py tempest/api/orchestration/base.py tempest/api/orchestration/stacks/__init__.py tempest/api/orchestration/stacks/test_limits.py tempest/api/orchestration/stacks/test_neutron_resources.py tempest/api/orchestration/stacks/test_non_empty_stack.py tempest/api/orchestration/stacks/test_nova_keypair_resources.py tempest/api/orchestration/stacks/test_server_cfn_init.py tempest/api/orchestration/stacks/test_stacks.py tempest/api/orchestration/stacks/test_swift_resources.py tempest/api/orchestration/stacks/test_templates.py tempest/api/orchestration/stacks/test_templates_negative.py tempest/api/orchestration/stacks/test_update.py tempest/api/orchestration/stacks/test_volumes.py tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml tempest/api/orchestration/stacks/templates/cinder_basic.yaml tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml tempest/api/orchestration/stacks/templates/neutron_basic.yaml tempest/api/orchestration/stacks/templates/non_empty_stack.yaml tempest/api/orchestration/stacks/templates/nova_keypair.json tempest/api/orchestration/stacks/templates/nova_keypair.yaml tempest/api/orchestration/stacks/templates/swift_basic.yaml tempest/api/queuing/__init__.py tempest/api/queuing/base.py tempest/api/queuing/test_queues.py tempest/api/telemetry/__init__.py tempest/api/telemetry/base.py tempest/api/telemetry/test_telemetry_alarming_api.py tempest/api/volume/__init__.py tempest/api/volume/base.py tempest/api/volume/test_extensions.py tempest/api/volume/test_snapshot_metadata.py tempest/api/volume/test_volume_metadata.py tempest/api/volume/test_volume_transfers.py tempest/api/volume/test_volumes_actions.py tempest/api/volume/test_volumes_get.py tempest/api/volume/test_volumes_list.py tempest/api/volume/test_volumes_negative.py tempest/api/volume/test_volumes_snapshots.py tempest/api/volume/test_volumes_snapshots_negative.py tempest/api/volume/admin/__init__.py tempest/api/volume/admin/test_multi_backend.py tempest/api/volume/admin/test_snapshots_actions.py tempest/api/volume/admin/test_volume_hosts.py tempest/api/volume/admin/test_volume_quotas.py tempest/api/volume/admin/test_volume_quotas_negative.py tempest/api/volume/admin/test_volume_services.py tempest/api/volume/admin/test_volume_types.py tempest/api/volume/admin/test_volume_types_extra_specs.py tempest/api/volume/admin/test_volume_types_extra_specs_negative.py tempest/api/volume/admin/test_volume_types_negative.py tempest/api/volume/admin/test_volumes_actions.py tempest/api/volume/admin/test_volumes_backup.py tempest/api/volume/v2/__init__.py tempest/api/volume/v2/test_volumes_list.py tempest/api_schema/__init__.py tempest/api_schema/compute/__init__.py tempest/api_schema/compute/agents.py tempest/api_schema/compute/aggregates.py tempest/api_schema/compute/availability_zone.py tempest/api_schema/compute/certificates.py tempest/api_schema/compute/flavors.py tempest/api_schema/compute/flavors_access.py tempest/api_schema/compute/flavors_extra_specs.py tempest/api_schema/compute/hosts.py tempest/api_schema/compute/hypervisors.py tempest/api_schema/compute/interfaces.py tempest/api_schema/compute/keypairs.py tempest/api_schema/compute/migrations.py tempest/api_schema/compute/parameter_types.py tempest/api_schema/compute/quotas.py tempest/api_schema/compute/servers.py tempest/api_schema/compute/services.py tempest/api_schema/compute/version.py tempest/api_schema/compute/v2/__init__.py tempest/api_schema/compute/v2/agents.py tempest/api_schema/compute/v2/aggregates.py tempest/api_schema/compute/v2/availability_zone.py tempest/api_schema/compute/v2/certificates.py tempest/api_schema/compute/v2/extensions.py tempest/api_schema/compute/v2/fixed_ips.py tempest/api_schema/compute/v2/flavors.py tempest/api_schema/compute/v2/floating_ips.py tempest/api_schema/compute/v2/hosts.py tempest/api_schema/compute/v2/hypervisors.py tempest/api_schema/compute/v2/images.py tempest/api_schema/compute/v2/instance_usage_audit_logs.py tempest/api_schema/compute/v2/keypairs.py tempest/api_schema/compute/v2/limits.py tempest/api_schema/compute/v2/quotas.py tempest/api_schema/compute/v2/security_groups.py tempest/api_schema/compute/v2/servers.py tempest/api_schema/compute/v2/tenant_usages.py tempest/api_schema/compute/v2/volumes.py tempest/api_schema/compute/v3/__init__.py tempest/api_schema/compute/v3/agents.py tempest/api_schema/compute/v3/aggregates.py tempest/api_schema/compute/v3/availability_zone.py tempest/api_schema/compute/v3/certificates.py tempest/api_schema/compute/v3/extensions.py tempest/api_schema/compute/v3/flavors.py tempest/api_schema/compute/v3/hosts.py tempest/api_schema/compute/v3/hypervisors.py tempest/api_schema/compute/v3/keypairs.py tempest/api_schema/compute/v3/quotas.py tempest/api_schema/compute/v3/servers.py tempest/cli/README.rst tempest/cli/__init__.py tempest/cli/output_parser.py tempest/cli/simple_read_only/README.txt tempest/cli/simple_read_only/__init__.py tempest/cli/simple_read_only/test_ceilometer.py tempest/cli/simple_read_only/test_cinder.py tempest/cli/simple_read_only/test_glance.py tempest/cli/simple_read_only/test_heat.py tempest/cli/simple_read_only/test_keystone.py tempest/cli/simple_read_only/test_neutron.py tempest/cli/simple_read_only/test_nova.py tempest/cli/simple_read_only/test_nova_manage.py tempest/cli/simple_read_only/test_sahara.py tempest/cli/simple_read_only/heat_templates/heat_minimal.yaml tempest/cli/simple_read_only/heat_templates/heat_minimal_hot.yaml tempest/cmd/__init__.py tempest/cmd/verify_tempest_config.py tempest/common/__init__.py tempest/common/commands.py tempest/common/custom_matchers.py tempest/common/debug.py tempest/common/generate_sample_tempest.py tempest/common/glance_http.py tempest/common/http.py tempest/common/isolated_creds.py tempest/common/rest_client.py tempest/common/ssh.py tempest/common/tempest_fixtures.py tempest/common/waiters.py tempest/common/xml_utils.py tempest/common/generator/__init__.py tempest/common/generator/base_generator.py tempest/common/generator/negative_generator.py tempest/common/generator/valid_generator.py tempest/common/utils/__init__.py tempest/common/utils/data_utils.py tempest/common/utils/file_utils.py tempest/common/utils/misc.py tempest/common/utils/linux/__init__.py tempest/common/utils/linux/remote_client.py tempest/hacking/__init__.py tempest/hacking/checks.py tempest/openstack/__init__.py tempest/openstack/common/__init__.py tempest/openstack/common/excutils.py tempest/openstack/common/fileutils.py tempest/openstack/common/gettextutils.py tempest/openstack/common/importutils.py tempest/openstack/common/jsonutils.py tempest/openstack/common/local.py tempest/openstack/common/lockutils.py tempest/openstack/common/log.py tempest/openstack/common/timeutils.py tempest/openstack/common/config/__init__.py tempest/openstack/common/config/generator.py tempest/openstack/common/fixture/__init__.py tempest/openstack/common/fixture/config.py tempest/openstack/common/fixture/lockutils.py tempest/openstack/common/fixture/mockpatch.py tempest/openstack/common/fixture/moxstubout.py tempest/scenario/README.rst tempest/scenario/__init__.py tempest/scenario/manager.py tempest/scenario/test_aggregates_basic_ops.py tempest/scenario/test_baremetal_basic_ops.py tempest/scenario/test_dashboard_basic_ops.py tempest/scenario/test_large_ops.py tempest/scenario/test_load_balancer_basic.py tempest/scenario/test_minimum_basic.py tempest/scenario/test_network_advanced_server_ops.py tempest/scenario/test_network_basic_ops.py tempest/scenario/test_security_groups_basic_ops.py tempest/scenario/test_server_advanced_ops.py tempest/scenario/test_server_basic_ops.py tempest/scenario/test_snapshot_pattern.py tempest/scenario/test_stamp_pattern.py tempest/scenario/test_swift_basic_ops.py tempest/scenario/test_volume_boot_pattern.py tempest/scenario/utils.py tempest/scenario/orchestration/__init__.py tempest/scenario/orchestration/test_autoscaling.py tempest/scenario/orchestration/test_autoscaling.yaml tempest/services/__init__.py tempest/services/botoclients.py tempest/services/baremetal/__init__.py tempest/services/baremetal/base.py tempest/services/baremetal/v1/__init__.py tempest/services/baremetal/v1/base_v1.py tempest/services/baremetal/v1/client_json.py tempest/services/compute/__init__.py tempest/services/compute/json/__init__.py tempest/services/compute/json/agents_client.py tempest/services/compute/json/aggregates_client.py tempest/services/compute/json/availability_zone_client.py tempest/services/compute/json/certificates_client.py tempest/services/compute/json/extensions_client.py tempest/services/compute/json/fixed_ips_client.py tempest/services/compute/json/flavors_client.py tempest/services/compute/json/floating_ips_client.py tempest/services/compute/json/hosts_client.py tempest/services/compute/json/hypervisor_client.py tempest/services/compute/json/images_client.py tempest/services/compute/json/instance_usage_audit_log_client.py tempest/services/compute/json/interfaces_client.py tempest/services/compute/json/keypairs_client.py tempest/services/compute/json/limits_client.py tempest/services/compute/json/migrations_client.py tempest/services/compute/json/quotas_client.py tempest/services/compute/json/security_groups_client.py tempest/services/compute/json/servers_client.py tempest/services/compute/json/services_client.py tempest/services/compute/json/tenant_usages_client.py tempest/services/compute/json/volumes_extensions_client.py tempest/services/compute/v3/__init__.py tempest/services/compute/v3/json/__init__.py tempest/services/compute/v3/json/agents_client.py tempest/services/compute/v3/json/aggregates_client.py tempest/services/compute/v3/json/availability_zone_client.py tempest/services/compute/v3/json/certificates_client.py tempest/services/compute/v3/json/extensions_client.py tempest/services/compute/v3/json/flavors_client.py tempest/services/compute/v3/json/hosts_client.py tempest/services/compute/v3/json/hypervisor_client.py tempest/services/compute/v3/json/interfaces_client.py tempest/services/compute/v3/json/keypairs_client.py tempest/services/compute/v3/json/migration_client.py tempest/services/compute/v3/json/quotas_client.py tempest/services/compute/v3/json/servers_client.py tempest/services/compute/v3/json/services_client.py tempest/services/compute/v3/json/version_client.py tempest/services/compute/xml/__init__.py tempest/services/compute/xml/aggregates_client.py tempest/services/compute/xml/availability_zone_client.py tempest/services/compute/xml/certificates_client.py tempest/services/compute/xml/extensions_client.py tempest/services/compute/xml/fixed_ips_client.py tempest/services/compute/xml/flavors_client.py tempest/services/compute/xml/floating_ips_client.py tempest/services/compute/xml/hosts_client.py tempest/services/compute/xml/hypervisor_client.py tempest/services/compute/xml/images_client.py tempest/services/compute/xml/instance_usage_audit_log_client.py tempest/services/compute/xml/interfaces_client.py tempest/services/compute/xml/keypairs_client.py tempest/services/compute/xml/limits_client.py tempest/services/compute/xml/quotas_client.py tempest/services/compute/xml/security_groups_client.py tempest/services/compute/xml/servers_client.py tempest/services/compute/xml/services_client.py tempest/services/compute/xml/tenant_usages_client.py tempest/services/compute/xml/volumes_extensions_client.py tempest/services/data_processing/__init__.py tempest/services/data_processing/v1_1/__init__.py tempest/services/data_processing/v1_1/client.py tempest/services/database/__init__.py tempest/services/database/json/__init__.py tempest/services/database/json/flavors_client.py tempest/services/identity/__init__.py tempest/services/identity/json/__init__.py tempest/services/identity/json/identity_client.py tempest/services/identity/v3/__init__.py tempest/services/identity/v3/json/__init__.py tempest/services/identity/v3/json/credentials_client.py tempest/services/identity/v3/json/endpoints_client.py tempest/services/identity/v3/json/identity_client.py tempest/services/identity/v3/json/policy_client.py tempest/services/identity/v3/json/service_client.py tempest/services/identity/v3/xml/__init__.py tempest/services/identity/v3/xml/credentials_client.py tempest/services/identity/v3/xml/endpoints_client.py tempest/services/identity/v3/xml/identity_client.py tempest/services/identity/v3/xml/policy_client.py tempest/services/identity/v3/xml/service_client.py tempest/services/identity/xml/__init__.py tempest/services/identity/xml/identity_client.py tempest/services/image/__init__.py tempest/services/image/v1/__init__.py tempest/services/image/v1/json/__init__.py tempest/services/image/v1/json/image_client.py tempest/services/image/v2/__init__.py tempest/services/image/v2/json/__init__.py tempest/services/image/v2/json/image_client.py tempest/services/network/__init__.py tempest/services/network/network_client_base.py tempest/services/network/json/__init__.py tempest/services/network/json/network_client.py tempest/services/network/xml/__init__.py tempest/services/network/xml/network_client.py tempest/services/object_storage/__init__.py tempest/services/object_storage/account_client.py tempest/services/object_storage/container_client.py tempest/services/object_storage/object_client.py tempest/services/orchestration/__init__.py tempest/services/orchestration/json/__init__.py tempest/services/orchestration/json/orchestration_client.py tempest/services/queuing/__init__.py tempest/services/queuing/json/__init__.py tempest/services/queuing/json/queuing_client.py tempest/services/telemetry/__init__.py tempest/services/telemetry/telemetry_client_base.py tempest/services/telemetry/json/__init__.py tempest/services/telemetry/json/telemetry_client.py tempest/services/telemetry/xml/__init__.py tempest/services/telemetry/xml/telemetry_client.py tempest/services/volume/__init__.py tempest/services/volume/json/__init__.py tempest/services/volume/json/backups_client.py tempest/services/volume/json/extensions_client.py tempest/services/volume/json/snapshots_client.py tempest/services/volume/json/volumes_client.py tempest/services/volume/json/admin/__init__.py tempest/services/volume/json/admin/volume_hosts_client.py tempest/services/volume/json/admin/volume_quotas_client.py tempest/services/volume/json/admin/volume_services_client.py tempest/services/volume/json/admin/volume_types_client.py tempest/services/volume/v2/__init__.py tempest/services/volume/v2/json/__init__.py tempest/services/volume/v2/json/volumes_client.py tempest/services/volume/v2/xml/__init__.py tempest/services/volume/v2/xml/volumes_client.py tempest/services/volume/xml/__init__.py tempest/services/volume/xml/backups_client.py tempest/services/volume/xml/extensions_client.py tempest/services/volume/xml/snapshots_client.py tempest/services/volume/xml/volumes_client.py tempest/services/volume/xml/admin/__init__.py tempest/services/volume/xml/admin/volume_hosts_client.py tempest/services/volume/xml/admin/volume_quotas_client.py tempest/services/volume/xml/admin/volume_services_client.py tempest/services/volume/xml/admin/volume_types_client.py tempest/stress/README.rst tempest/stress/__init__.py tempest/stress/cleanup.py tempest/stress/driver.py tempest/stress/run_stress.py tempest/stress/stressaction.py tempest/stress/actions/__init__.py tempest/stress/actions/server_create_destroy.py tempest/stress/actions/ssh_floating.py tempest/stress/actions/unit_test.py tempest/stress/actions/volume_attach_delete.py tempest/stress/actions/volume_attach_verify.py tempest/stress/actions/volume_create_delete.py tempest/stress/etc/sample-unit-test.json tempest/stress/etc/server-create-destroy-test.json tempest/stress/etc/ssh_floating.json tempest/stress/etc/stress-tox-job.json tempest/stress/etc/volume-attach-delete-test.json tempest/stress/etc/volume-attach-verify.json tempest/stress/etc/volume-create-delete-test.json tempest/stress/tools/cleanup.py tempest/test_discover/__init__.py tempest/test_discover/test_discover.py tempest/tests/README.rst tempest/tests/__init__.py tempest/tests/base.py tempest/tests/fake_auth_provider.py tempest/tests/fake_config.py tempest/tests/fake_credentials.py tempest/tests/fake_http.py tempest/tests/fake_identity.py tempest/tests/test_auth.py tempest/tests/test_commands.py tempest/tests/test_compute_xml_common.py tempest/tests/test_credentials.py tempest/tests/test_decorators.py tempest/tests/test_glance_http.py tempest/tests/test_hacking.py tempest/tests/test_list_tests.py tempest/tests/test_rest_client.py tempest/tests/test_ssh.py tempest/tests/test_tenant_isolation.py tempest/tests/test_waiters.py tempest/tests/test_wrappers.py tempest/tests/cli/__init__.py tempest/tests/cli/test_output_parser.py tempest/tests/cmd/__init__.py tempest/tests/cmd/test_verify_tempest_config.py tempest/tests/common/__init__.py tempest/tests/common/test_debug.py tempest/tests/common/utils/__init__.py tempest/tests/common/utils/test_data_utils.py tempest/tests/common/utils/test_file_utils.py tempest/tests/common/utils/test_misc.py tempest/tests/files/__init__.py tempest/tests/files/failing-tests tempest/tests/files/passing-tests tempest/tests/files/setup.cfg tempest/tests/files/testr-conf tempest/tests/negative/__init__.py tempest/tests/negative/test_negative_auto_test.py tempest/tests/negative/test_negative_generators.py tempest/tests/stress/__init__.py tempest/tests/stress/test_stress.py tempest/tests/stress/test_stressaction.py tempest/thirdparty/README.rst tempest/thirdparty/__init__.py tempest/thirdparty/boto/__init__.py tempest/thirdparty/boto/test.py tempest/thirdparty/boto/test_ec2_instance_run.py tempest/thirdparty/boto/test_ec2_keys.py tempest/thirdparty/boto/test_ec2_network.py tempest/thirdparty/boto/test_ec2_security_groups.py tempest/thirdparty/boto/test_ec2_volumes.py tempest/thirdparty/boto/test_s3_buckets.py tempest/thirdparty/boto/test_s3_ec2_images.py tempest/thirdparty/boto/test_s3_objects.py tempest/thirdparty/boto/utils/__init__.py tempest/thirdparty/boto/utils/s3.py tempest/thirdparty/boto/utils/wait.py tools/check_logs.py tools/colorizer.py tools/find_stack_traces.py tools/generate_sample.sh tools/install_venv.py tools/install_venv_common.py tools/pretty_tox.sh tools/pretty_tox_serial.sh tools/skip_tracker.py tools/subunit-trace.py tools/tempest_auto_config.py tools/with_venv.sh tools/config/check_uptodate.sh tools/config/generate_sample.shtempest-2014.1.dev4108.gf22b6cc/LICENSE0000664000175000017500000002363712332757070017115 0ustar chuckchuck00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.