diff options
122 files changed, 14382 insertions, 4131 deletions
diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 14f1a11..464ba63 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -6,7 +6,7 @@ set -e sudo pip install virtualenv virtualenv ./vpy source ./vpy/bin/activate -pip install -e ../ +pip install -chttps://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/pike -e ../ # $1 is the IP to the pod's build server # $2 is the IP to the pod's control server diff --git a/docs/how-to-use/APITests.rst b/docs/how-to-use/APITests.rst index 4a8035a..50cd437 100644 --- a/docs/how-to-use/APITests.rst +++ b/docs/how-to-use/APITests.rst @@ -34,6 +34,12 @@ nova_utils_tests.py - NovaSmokeTests Ensures that a Nova client can be obtained as well as the proper exceptions thrown with the wrong credentials. +cinder_utils_tests.py - CinderSmokeTests +---------------------------------------- + +Ensures that a Cinder client can be obtained as well as the proper +exceptions thrown with the wrong credentials. + heat_utils_tests.py - HeatSmokeTests ------------------------------------ @@ -157,22 +163,63 @@ neutron_utils_tests.py - NeutronUtilsSubnetTests +---------------------------------------+---------------+-----------------------------------------------------------+ | Test Name | Neutron API | Description | +=======================================+===============+===========================================================+ -| test_create_subnet | 2 | Ensures neutron_utils.create_subnet() can properly create | +| test_create_subnet | 2 | Ensures neutron_utils.create_network() can properly create| | | | an OpenStack subnet object | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_subnet_null_name | 2 | Ensures neutron_utils.create_subnet() raises an exception | +| test_create_subnet_null_name | 2 | Ensures neutron_utils.create_network() raises an exception| | | | when the subnet name is None | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_subnet_empty_name | 2 | Ensures neutron_utils.create_subnet() raises an exception | +| test_create_subnet_empty_name | 2 | Ensures neutron_utils.create_network() raises an exception| | | | when the subnet name is an empty string | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_subnet_null_cidr | 2 | Ensures neutron_utils.create_subnet() raises an exception | +| test_create_subnet_null_cidr | 2 | Ensures neutron_utils.create_network() raises an exception| | | | when the subnet CIDR is None | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_subnet_empty_cidr | 2 | Ensures neutron_utils.create_subnet() raises an exception | +| test_create_subnet_empty_cidr | 2 | Ensures neutron_utils.create_network() raises an exception| | | | when the subnet CIDR is an empty string | +---------------------------------------+---------------+-----------------------------------------------------------+ +neutron_utils_tests.py - NeutronUtilsIPv6Tests +---------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Neutron API | Description | ++=======================================+===============+===========================================================+ +| test_create_network_slaac | 2 | Ensures neutron_utils.create_network() can properly create| +| | | an OpenStack network with an IPv6 subnet when DHCP is True| +| | | and modes are 'slaac' | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_stateful | 2 | Ensures neutron_utils.create_network() can properly create| +| | | an OpenStack network with an IPv6 subnet when DHCP is True| +| | | and modes are 'stateful' | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_stateless | 2 | Ensures neutron_utils.create_network() can properly create| +| | | an OpenStack network with an IPv6 subnet when DHCP is True| +| | | and modes are 'stateless' | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_no_dhcp_slaac | 2 | Ensures neutron_utils.create_network() raises a BadRequest| +| | | exception when deploying the network with an IPv6 subnet | +| | | when DHCP is False and modes are 'slaac' | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_invalid_start_ip | 2 | Ensures neutron_utils.create_network() sets the start IP | +| | | address to the minimum value when the start configuration | +| | | parameter is some garbage value | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_invalid_end_ip | 2 | Ensures neutron_utils.create_network() sets the end IP | +| | | address to the maximum value when the end configuration | +| | | parameter is some garbage value | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_with_bad_cidr | 2 | Ensures neutron_utils.create_network() raises a BadRequest| +| | | exception when the IPv6 CIDR is incorrect | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_invalid_gateway_ip| 2 | Ensures neutron_utils.create_network() raises a BadRequest| +| | | exception when the IPv6 gateway IP does not match the CIDR| ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_with_bad_dns | 2 | Ensures neutron_utils.create_network() raises a BadRequest| +| | | exception when the IPv6 DNS IP address is not a valid IPv6| +| | | address | ++---------------------------------------+---------------+-----------------------------------------------------------+ + neutron_utils_tests.py - NeutronUtilsRouterTests ------------------------------------------------ @@ -186,12 +233,6 @@ neutron_utils_tests.py - NeutronUtilsRouterTests | face | | an OpenStack router object with an interface to the | | | | external network | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_router_empty_name | 2 | Ensures neutron_utils.create_router() raises an exception | -| | | when the name is an empty string | -+---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_router_null_name | 2 | Ensures neutron_utils.create_router() raises an exception | -| | | when the name is None | -+---------------------------------------+---------------+-----------------------------------------------------------+ | test_add_interface_router | 2 | Ensures neutron_utils.add_interface_router() properly adds| | | | an interface to another subnet | +---------------------------------------+---------------+-----------------------------------------------------------+ @@ -201,6 +242,9 @@ neutron_utils_tests.py - NeutronUtilsRouterTests | test_add_interface_router_null_subnet | 2 | Ensures neutron_utils.add_interface_router() raises an | | | | exception when the subnet object is None | +---------------------------------------+---------------+-----------------------------------------------------------+ +| test_add_interface_router_missing_sub | 2 | Ensures neutron_utils.add_interface_router() raises an | +| net | | exception when the subnet object had been deleted | ++---------------------------------------+---------------+-----------------------------------------------------------+ | test_create_port | 2 | Ensures neutron_utils.create_port() can properly create an| | | | OpenStack port object | +---------------------------------------+---------------+-----------------------------------------------------------+ @@ -256,6 +300,95 @@ neutron_utils_tests.py - NeutronUtilsFloatingIpTests | test_floating_ips | 2 | Ensures that a floating IP can be created | +---------------------------------------+---------------+-----------------------------------------------------------+ +cinder_utils_tests.py - CinderUtilsQoSTests +------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++=======================================+===============+===========================================================+ +| test_create_qos_both | 2 & 3 | Ensures that a QoS Spec can be created with a Consumer | +| | | value of 'both' | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_qos_front | 2 & 3 | Ensures that a QoS Spec can be created with a Consumer | +| | | value of 'front-end' | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_qos_back | 2 & 3 | Ensures that a QoS Spec can be created with a Consumer | +| | | value of 'back-end' | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_qos | 2 & 3 | Ensures that a QoS Spec can be created and deleted | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +cinder_utils_tests.py - CinderUtilsSimpleVolumeTypeTests +-------------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++=======================================+===============+===========================================================+ +| test_create_simple_volume_type | 2 & 3 | Tests the creation of a simple volume type with the | +| | | function cinder_utils#create_volume_type() | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_volume_type | 2 & 3 | Tests the creation of a simple volume type with the | +| | | function cinder_utils#create_volume_type() then deletes | +| | | with the function cinder_utils#delete_volume_type() | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +cinder_utils_tests.py - CinderUtilsAddEncryptionTests +----------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++=======================================+===============+===========================================================+ +| test_create_simple_encryption | 2 & 3 | Tests the creation of a simple volume type encryption | +| | | with the function cinder_utils#create_volume_encryption() | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_encryption | 2 & 3 | Tests the creation of a simple volume type encryption | +| | | with the function cinder_utils#create_volume_encryption() | +| | | then deletes with the function | +| | | cinder_utils#delete_volume_type_encryption() | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_with_all_attrs | 2 & 3 | Tests the creation of a simple volume type encryption | +| | | with the function cinder_utils#create_volume_encryption() | +| | | where all configuration attributes have been set | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_bad_key_size | 2 & 3 | Tests to ensure that the function | +| | | cinder_utils#create_volume_encryption() raises a | +| | | BadRequest exception when the key_size attribute is -1 | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +cinder_utils_tests.py - CinderUtilsVolumeTypeCompleteTests +---------------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++=======================================+===============+===========================================================+ +| test_create_with_encryption | 2 & 3 | Tests the creation of a volume type with encryption | +| | | with the function cinder_utils#create_volume_type() | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_with_qos | 2 & 3 | Tests the creation of a volume type with a QoS Spec | +| | | with the function cinder_utils#create_volume_type() | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_with_invalid_qos | 2 & 3 | Tests the creation of a volume type with an invalid QoS | +| | | Spec with the function cinder_utils#create_volume_type() | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_with_qos_and_encryption | 2 & 3 | Tests the creation of a volume type with a QoS Spec and | +| | | encryption with the function | +| | | cinder_utils#create_volume_type() | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +cinder_utils_tests.py - CinderUtilsVolumeTests +---------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++=======================================+===============+===========================================================+ +| test_create_simple_volume | 2 & 3 | Tests the creation of a simple volume with the function | +| | | cinder_utils#create_volume() | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_volume | 2 & 3 | Tests the creation of a volume with the function | +| | | cinder_utils#create_volume() then deletion with the | +| | | function cinder_utils#delete_volume() | ++---------------------------------------+---------------+-----------------------------------------------------------+ + nova_utils_tests.py - NovaUtilsKeypairTests ------------------------------------------- @@ -295,6 +428,16 @@ nova_utils_tests.py - NovaUtilsInstanceTests | | | nova_utils.create_server() | +---------------------------------------+---------------+-----------------------------------------------------------+ +nova_utils_tests.py - NovaUtilsInstanceVolumeTests +-------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Nova API | Description | ++=======================================+===============+===========================================================+ +| test_add_remove_volume | 2 | Ensures that a VM instance can properly attach and detach | +| | | a volume using the nova interface | ++---------------------------------------+---------------+-----------------------------------------------------------+ + create_flavor_tests.py - CreateFlavorTests ------------------------------------------ @@ -324,9 +467,9 @@ heat_utils_tests.py - HeatUtilsCreateSimpleStackTests +---------------------------------------+---------------+-----------------------------------------------------------+ | Test Name | Heat API | Description | +=======================================+===============+===========================================================+ -| test_create_stack | 1 | Tests the heat_utils.create_stack() with a test template | +| test_create_stack | 1-3 | Tests the heat_utils.create_stack() with a test template | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_stack_x2 | 1 | Tests the heat_utils.create_stack() with a test template | +| test_create_stack_x2 | 1-3 | Tests the heat_utils.create_stack() with a test template | | | | and attempts to deploy a second time w/o actually | | | | deploying any objects | +---------------------------------------+---------------+-----------------------------------------------------------+ @@ -337,21 +480,120 @@ heat_utils_tests.py - HeatUtilsCreateComplexStackTests +---------------------------------------+---------------+-----------------------------------------------------------+ | Test Name | Heat API | Description | +=======================================+===============+===========================================================+ -| test_get_settings_from_stack | 1 | Tests the heat_utils functions that are responsible for | +| test_get_settings_from_stack | 1-3 | Tests the heat_utils functions that are responsible for | | | | reverse engineering settings objects of the types deployed| | | | by Heat | +---------------------------------------+---------------+-----------------------------------------------------------+ +heat_utils_tests.py - HeatUtilsRouterTests +------------------------------------------ + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_create_router_with_stack | 1-3 | Tests ability of the function | +| | | heat_utils.get_stack_routers() to return the correct | +| | | OpenStackRouter instance | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +heat_utils_tests.py - HeatUtilsVolumeTests +------------------------------------------ + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_create_vol_with_stack | 1-3 | Tests ability of the function | +| | | heat_utils.create_stack() to return the correct | +| | | Volume domain objects deployed with Heat | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_vol_types_with_stack | 1-3 | Tests ability of the function | +| | | heat_utils.get_stack_volumes_types() to return the correct| +| | | VolumeType domain objects deployed with Heat | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +heat_utils_tests.py - HeatUtilsKeypairTests +------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_create_keypair_with_stack | 1-3 | Tests ability of the function | +| | | heat_utils.get_stack_keypairs() to return the correct | +| | | Keypair domain objects deployed with Heat | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +heat_utils_tests.py - HeatUtilsSecurityGroupTests +------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_create_security_group_with_stack | 1-3 | Tests ability of the function | +| | | heat_utils.get_stack_security_groups() to return the | +| | | correct SecurityGroup domain objects deployed with Heat | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +heat_utils_tests.py - HeatUtilsFlavorTests +------------------------------------------ + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_create_flavor_with_stack | 1-3 | Tests ability of the function | +| | | heat_utils.get_stack_flavors() to return the correct | +| | | Flavor domain objects deployed with Heat | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +magnum_utils_tests.py - MagnumUtilsTests +---------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Magnum API | Description | ++=======================================+===============+===========================================================+ +| test_create_cluster_template_simple | 1 | Tests ability of the function | +| | | magnum_utils.create_cluster_template() to create a simple | +| | | cluster template OpenStack object with minimal config | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_all | 1 | Tests ability of the function | +| | | magnum_utils.create_cluster_template() to create a | +| | | cluster template OpenStack object with maximum config | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad_image| 1 | Ensures the function | +| | | magnum_utils.create_cluster_template() will raise a | +| | | BadRequest exception when the image does not exist | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad_ext | 1 | Ensures the function | +| _net | | magnum_utils.create_cluster_template() will raise a | +| | | BadRequest exception when the external network does not | +| | | exist | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad | 1 | Ensures the function | +| _flavor | | magnum_utils.create_cluster_template() will raise a | +| | | BadRequest exception when the flavor does not exist | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad | 1 | Ensures the function | +| _master_flavor | | magnum_utils.create_cluster_template() will raise a | +| | | BadRequest exception when the master flavor does not exist| ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad | 1 | Ensures the function | +| _network_driver | | magnum_utils.create_cluster_template() will raise a | +| | | BadRequest exception when the network driver is invalid | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad | 1 | Ensures the function | +| _volume_driver | | magnum_utils.create_cluster_template() will raise a | +| | | BadRequest exception when the volume driver is invalid | ++---------------------------------------+---------------+-----------------------------------------------------------+ + settings_utils_tests.py - SettingsUtilsNetworkingTests ------------------------------------------------------ +---------------------------------------+---------------+-----------------------------------------------------------+ | Test Name | API | Description | +=======================================+===============+===========================================================+ -| test_derive_net_settings_no_subnet | Neutron 2 | Tests to ensure that derived NetworkSettings from an | +| test_derive_net_settings_no_subnet | Neutron 2 | Tests to ensure that derived NetworkConfig from an | | | | OpenStack network are correct without a subnet | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_derive_net_settings_two_subnets | Neutron 2 | Tests to ensure that derived NetworkSettings from an | +| test_derive_net_settings_two_subnets | Neutron 2 | Tests to ensure that derived NetworkConfig from an | | | | OpenStack network are correct with two subnets | +---------------------------------------+---------------+-----------------------------------------------------------+ @@ -361,9 +603,9 @@ settings_utils_tests.py - SettingsUtilsVmInstTests +---------------------------------------+---------------+-----------------------------------------------------------+ | Test Name | API | Description | +=======================================+===============+===========================================================+ -| test_derive_vm_inst_settings | Neutron 2 | Tests to ensure that derived VmInstanceSettings from an | +| test_derive_vm_inst_config | Neutron 2 | Tests to ensure that derived VmInstanceSettings from an | | | | OpenStack VM instance is correct | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_derive_image_settings | Neutron 2 | Tests to ensure that derived ImageSettings from an | +| test_derive_image_settings | Neutron 2 | Tests to ensure that derived ImageConfig from an | | | | OpenStack VM instance is correct | +---------------------------------------+---------------+-----------------------------------------------------------+ diff --git a/docs/how-to-use/InstallSnaps.rst b/docs/how-to-use/InstallSnaps.rst index dc53ef0..f5e9cf8 100644 --- a/docs/how-to-use/InstallSnaps.rst +++ b/docs/how-to-use/InstallSnaps.rst @@ -69,4 +69,12 @@ The "pip" command below needs to be executed as root, if you are not using a vir sudo pip install -e <path to repo>/snaps/ (note: on CentOS 7 and Ubuntu 14.04 you may have to try the previous command several times) +SNAPS is now hosted on the Python Package Manager (PyPI). + +:: + + pip install snaps + +This will install the stable Euphrates version. + The install should now be complete and you can start using the SNAPS-OO libraries. diff --git a/docs/how-to-use/IntegrationTests.rst b/docs/how-to-use/IntegrationTests.rst index f082f9b..59ec8a9 100644 --- a/docs/how-to-use/IntegrationTests.rst +++ b/docs/how-to-use/IntegrationTests.rst @@ -80,7 +80,7 @@ create_image_tests.py - CreateImageNegativeTests | Test Name | Glance API | Description | +=======================================+===============+===========================================================+ | test_bad_image_name | 1 & 2 | Ensures OpenStackImage.create() results in an Exception | -| | | being raised when the ImageSettings.name attribute has | +| | | being raised when the ImageConfig.name attribute has | | | | not been set | +---------------------------------------+---------------+-----------------------------------------------------------+ | test_bad_image_url | 1 & 2 | Ensures OpenStackImage.create() results in an Exception | @@ -185,6 +185,18 @@ create_network_tests.py - CreateNetworkSuccessTests | | | 'admin' project ID | +---------------------------------------+---------------+-----------------------------------------------------------+ +create_network_tests.py - CreateNetworkIPv6Tests +------------------------------------------------ + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Neutron API | Description | ++=======================================+===============+===========================================================+ +| test_create_network_one_ipv6_subnet | 2 | Ensures that a network can be created with an IPv6 subnet | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_network_ipv4_ipv6_subnet | 2 | Ensures that a network can be created with an IPv4 and | +| | | IPv6 subnet | ++---------------------------------------+---------------+-----------------------------------------------------------+ + create_router_tests.py - CreateRouterSuccessTests ------------------------------------------------- @@ -229,38 +241,247 @@ create_router_tests.py - CreateRouterNegativeTests | | | create a router to an external network that does not exist| +----------------------------------------+---------------+-----------------------------------------------------------+ +create_qos_tests.py - CreateQoSTests +------------------------------------ + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++========================================+===============+===========================================================+ +| test_create_qos | 2 & 3 | Tests the creation of a QoS Spec with the class | +| | | OpenStackQoS | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_qos | 2 & 3 | Tests the creation of a QoS Spec with the class | +| | | OpenStackQoS, its deletion with cinder_utils.py the | +| | | the attempts to use the clean() method to ensure an | +| | | exception is not called | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_same_qos | 2 & 3 | Tests the creation of a QoS Spec with the class | +| | | OpenStackQoS then instantiates another OpenStackQoS | +| | | object with the same configuration to ensure the second | +| | | instance returns the ID of the original | ++----------------------------------------+---------------+-----------------------------------------------------------+ + +create_volume_type_tests.py - CreateSimpleVolumeTypeSuccessTests +---------------------------------------------------------------- + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++========================================+===============+===========================================================+ +| test_create_volume_type | 2 & 3 | Tests the creation of a Volume Type with the class | +| | | OpenStackVolumeType | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_volume_type | 2 & 3 | Tests the creation of a Volume Type with the class | +| | | OpenStackVolumeType, its deletion with cinder_utils.py, | +| | | then attempts to use the clean() method to ensure an | +| | | exception is not raised | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_same_volume_type | 2 & 3 | Tests the creation of a Volume Type with the class | +| | | OpenStackVolumeType then instantiates another | +| | | OpenStackVolumeType object with the same configuration to | +| | | ensure the second instance returns the ID of the original | ++----------------------------------------+---------------+-----------------------------------------------------------+ + +create_volume_type_tests.py - CreateSimpleVolumeTypeComplexTests +---------------------------------------------------------------- + ++-----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++=========================================+===============+===========================================================+ +| test_volume_type_with_qos | 2 & 3 | Tests the creation of a Volume Type with the class | +| | | OpenStackVolumeType with a QoSSpec | ++-----------------------------------------+---------------+-----------------------------------------------------------+ +| test_volume_type_with_encryption | 2 & 3 | Tests the creation of a Volume Type with the class | +| | | OpenStackVolumeType with encryption | ++-----------------------------------------+---------------+-----------------------------------------------------------+ +| test_volume_type_with_qos_and_encryption| 2 & 3 | Tests the creation of a Volume Type with the class | +| | | OpenStackVolumeType with encryption and QoS Spec | ++-----------------------------------------+---------------+-----------------------------------------------------------+ + +create_volume_tests.py - CreateSimpleVolumeSuccessTests +------------------------------------------------------- + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++========================================+===============+===========================================================+ +| test_create_volume_simple | 2 & 3 | Tests the creation of a Volume Type with the class | +| | | OpenStackVolume | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_volume | 2 & 3 | Tests the creation of a Volume with the class | +| | | OpenStackVolume, its deletion with cinder_utils.py, then | +| | | attempts to use the clean() method to ensure an | +| | | exception is not raised | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_same_volume | 2 & 3 | Tests the creation of a Volume with the class | +| | | OpenStackVolume then instantiates another | +| | | OpenStackVolume object with the same configuration to | +| | | ensure the second instance returns the ID of the original | ++----------------------------------------+---------------+-----------------------------------------------------------+ + +create_volume_tests.py - CreateSimpleVolumeFailureTests +------------------------------------------------------- + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++========================================+===============+===========================================================+ +| test_create_volume_bad_size | 2 & 3 | Tests to ensure that attempting to create a volume with a | +| | | size of -1 raises a BadRequest exception | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_volume_bad_type | 2 & 3 | Tests to ensure that attempting to create a volume with a | +| | | type that does not exist raises a NotFound exception | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_volume_bad_image | 2 & 3 | Tests to ensure that attempting to create a volume with an| +| | | image that does not exist raises a BadRequest exception | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_volume_bad_zone | 2 & 3 | Tests to ensure that attempting to create a volume with an| +| | | invalid availability zone raises a BadRequest exception | ++----------------------------------------+---------------+-----------------------------------------------------------+ + +create_volume_tests.py - CreateVolumeWithTypeTests +-------------------------------------------------- + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++========================================+===============+===========================================================+ +| test_bad_volume_type | 2 & 3 | Tests to ensure the creation of a Volume with the | +| | | OpenStackVolume#create() method raises a NotFound | +| | | exception when attempting to apply a VolumeType that does | +| | | not exist | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_valid_volume_type | 2 & 3 | Tests to ensure the creation of a Volume with the | +| | | OpenStackVolume#create() method properly creates the | +| | | volume when associating with a valid VolumeType | ++----------------------------------------+---------------+-----------------------------------------------------------+ + +create_volume_tests.py - CreateVolumeWithImageTests +--------------------------------------------------- + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Cinder API | Description | ++========================================+===============+===========================================================+ +| test_bad_image_name | 2 & 3 | Tests to ensure the creation of a Volume with the | +| | | OpenStackVolume#create() method raises a BadRequest | +| | | exception when attempting to apply an image that does not | +| | | exist | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_valid_volume_image | 2 & 3 | Tests to ensure the creation of a Volume with the | +| | | OpenStackVolume#create() method properly creates the | +| | | volume when associating with a valid image | ++----------------------------------------+---------------+-----------------------------------------------------------+ + create_stack_tests.py - CreateStackSuccessTests ----------------------------------------------- +---------------------------------------+---------------+-----------------------------------------------------------+ -| Test Name | Neutron API | Description | +| Test Name | Heat API | Description | +=======================================+===============+===========================================================+ -| test_create_stack_template_file | 2 | Ensures that a Heat stack can be created with a file-based| +| test_create_stack_template_file | 1-3 | Ensures that a Heat stack can be created with a file-based| | | | Heat template file | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_stack_template_dict | 2 | Ensures that a Heat stack can be created with a dictionary| +| test_create_stack_template_dict | 1-3 | Ensures that a Heat stack can be created with a dictionary| | | | Heat template | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_delete_stack | 2 | Ensures that a Heat stack can be created and deleted | +| test_create_delete_stack | 1-3 | Ensures that a Heat stack can be created and deleted | | | | while having clean() called 2x without an exception | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_create_same_stack | 2 | Ensures that a Heat stack with the same name cannot be | +| test_create_same_stack | 1-3 | Ensures that a Heat stack with the same name cannot be | | | | created 2x | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_retrieve_network_creators | 2 | Ensures that an OpenStackHeatStack instance can return an | +| test_retrieve_network_creators | 1-3 | Ensures that an OpenStackHeatStack instance can return an | | | | OpenStackNetwork instance configured as deployed | +---------------------------------------+---------------+-----------------------------------------------------------+ -| test_retrieve_vm_inst_creators | 2 | Ensures that an OpenStackHeatStack instance can return an | +| test_retrieve_vm_inst_creators | 1-3 | Ensures that an OpenStackHeatStack instance can return an | | | | OpenStackVmInstance instance configured as deployed | +---------------------------------------+---------------+-----------------------------------------------------------+ +create_stack_tests.py - CreateStackVolumeTests +---------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_retrieve_volume_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a | +| | | OpenStackVolume instance that it was responsible for | +| | | deploying | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_retrieve_volume_type_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a | +| | | OpenStackVolumeType instance that it was responsible for | +| | | deploying | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +create_stack_tests.py - CreateStackFloatingIpTests +-------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_connect_via_ssh_heat_vm | 1 | Ensures that an OpenStackHeatStack instance can create a | +| | | VM with a floating IP that can be accessed via | +| | | OpenStackVmInstance | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +create_stack_tests.py - CreateStackNestedResourceTests +------------------------------------------------------ + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_nested | 1 | Ensures that an OpenStackHeatStack with an external | +| | | resource file with VMs with floating IPs can be accessed | +| | | in the class OpenStackVmInstance and return the associated| +| | | initialized OpenStackVmInstance objects | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +create_stack_tests.py - CreateStackRouterTests +---------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_retrieve_router_creator | 1 | Ensures that an OpenStackHeatStack instance can return a | +| | | OpenStackRouter instance that it was responsible for | +| | | deploying | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +create_stack_tests.py - CreateStackFlavorTests +---------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_retrieve_flavor_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a | +| | | OpenStackFlavor instance that it was responsible for | +| | | deploying | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +create_stack_tests.py - CreateStackKeypairTests +----------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_retrieve_keypair_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a | +| | | OpenStackKeypair instance that it was responsible for | +| | | deploying | ++---------------------------------------+---------------+-----------------------------------------------------------+ + +create_stack_tests.py - CreateStackSecurityGroupTests +----------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++=======================================+===============+===========================================================+ +| test_retrieve_security_group_creator | 1-3 | Ensures that an OpenStackHeatStack instance can return a | +| | | OpenStackSecurityGroup instance that it was responsible | +| | | for deploying | ++---------------------------------------+---------------+-----------------------------------------------------------+ + create_stack_tests.py - CreateComplexStackTests ----------------------------------------------- +---------------------------------------+---------------+-----------------------------------------------------------+ -| Test Name | Neutron API | Description | +| Test Name | Heat API | Description | +=======================================+===============+===========================================================+ -| test_connect_via_ssh_heat_vm | 2 | Ensures that two OpenStackHeatStack instances can return | +| test_connect_via_ssh_heat_vm | 1-3 | Ensures that two OpenStackHeatStack instances can return | | | | OpenStackVmInstance instances one configured with a | | | | floating IP and keypair and can be access via SSH | +---------------------------------------+---------------+-----------------------------------------------------------+ @@ -269,15 +490,25 @@ create_stack_tests.py - CreateStackNegativeTests ------------------------------------------------ +----------------------------------------+---------------+-----------------------------------------------------------+ -| Test Name | Neutron API | Description | +| Test Name | Heat API | Description | +========================================+===============+===========================================================+ -| test_missing_dependencies | 2 | Ensures that a Heat template fails to deploy when expected| +| test_missing_dependencies | 1-3 | Ensures that a Heat template fails to deploy when expected| | | | dependencies are missing | +----------------------------------------+---------------+-----------------------------------------------------------+ -| test_bad_stack_file | 2 | Ensures that a Heat template fails to deploy when the Heat| +| test_bad_stack_file | 1-3 | Ensures that a Heat template fails to deploy when the Heat| | | | template file does not exist | +----------------------------------------+---------------+-----------------------------------------------------------+ +create_stack_tests.py - CreateStackFailureTests +----------------------------------------------- + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Heat API | Description | ++========================================+===============+===========================================================+ +| test_stack_failure | 1-3 | Ensures that a Heat template fails to deploy when expected| +| | | dependencies are missing | ++----------------------------------------+---------------+-----------------------------------------------------------+ + create_instance_tests.py - CreateInstanceSimpleTests ---------------------------------------------------- @@ -323,6 +554,15 @@ create_instance_tests.py - CreateInstanceSingleNetworkTests | test_ssh_client_fip_after_active | Nova 2 | Ensures that an instance can be reached over SSH when the | | | Neutron 2 | floating IP is assigned after to the VM becoming ACTIVE | +---------------------------------------+---------------+-----------------------------------------------------------+ +| test_ssh_client_fip_after_init | Nova 2 | Ensures that an instance can have a floating IP assigned | +| | Neutron 2 | added after initialization | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_ssh_client_fip_reverse_engineer | Nova 2 | Ensures that an instance can be reverse engineered and | +| | Neutron 2 | allows for a floating IP to be added after initialization | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_ssh_client_fip_after_reboot | Nova 2 | Ensures that an instance can be reached over SSH after | +| | Neutron 2 | a reboot call has been issued | ++---------------------------------------+---------------+-----------------------------------------------------------+ | test_ssh_client_fip_second_creator | Nova 2 | Ensures that an instance can be reached over SSH via a | | | Neutron 2 | second identical creator object | +---------------------------------------+---------------+-----------------------------------------------------------+ @@ -378,14 +618,18 @@ create_instance_tests.py - CreateInstanceFromThreePartImage | | Neutron 2 | delete it when using a 3-part image | +-----------------------------------------------------+---------------+-----------------------------------------------------------+ -create_instance_tests.py - CreateInstancePubPrivNetTests --------------------------------------------------------- +create_instance_tests.py - CreateInstanceIPv6NetworkTests (Staging) +------------------------------------------------------------------- +---------------------------------------+---------------+-----------------------------------------------------------+ | Test Name | API Versions | Description | +=======================================+===============+===========================================================+ -| test_dual_ports_dhcp | Nova 2 | Ensures that a VM with two ports/NICs can have its second | -| | Neutron 2 | NIC configured via SSH/Ansible after startup | +| test_v4fip_v6overlay | Nova 2 | Expects a BadRequest exception to be raised when | +| | Neutron 2 | attempting to add an IPv4 floating IP to a VM with an IPv6| +| | | port | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_fip_v4and6_overlay | Nova 2 | Connects to a VM via a floating IP joined to a port that | +| | Neutron 2 | has been confiured with both IPv4 and IPv6 addresses | +---------------------------------------+---------------+-----------------------------------------------------------+ create_instance_tests.py - InstanceSecurityGroupTests @@ -410,6 +654,19 @@ create_instance_tests.py - InstanceSecurityGroupTests | | Neutron 2 | that has already been added to the instance | +---------------------------------------+---------------+-----------------------------------------------------------+ +create_instance_tests.py - CreateInstanceVolumeTests +---------------------------------------------------- + ++---------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | API Versions | Description | ++=======================================+===============+===========================================================+ +| test_create_instance_with_one_volume | Nova 2 | Ensures that a VM instance can have one volume attached | +| | Cinder 2 & 3 | to it | ++---------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_instance_with_two_volumes | Nova 2 | Ensures that a VM instance can have two volumes attached | +| | Cinder 2 & 3 | to it | ++---------------------------------------+---------------+-----------------------------------------------------------+ + ansible_utils_tests.py - AnsibleProvisioningTests ------------------------------------------------- @@ -423,3 +680,35 @@ ansible_utils_tests.py - AnsibleProvisioningTests | | Neutron 2 | apply a Ansible playbook containing Jinga2 substitution | | | | values | +---------------------------------------+---------------+-----------------------------------------------------------+ + +cluster_template_tests.py - CreateClusterTemplateTests +------------------------------------------------------ + ++----------------------------------------+---------------+-----------------------------------------------------------+ +| Test Name | Magnum API | Description | ++========================================+===============+===========================================================+ +| test_create_cluster_template | 1 | Tests the creation of a Cluster template with the class | +| | | OpenStackClusterTemplate | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_delete_cluster_template | 1 | Tests the creation and deletiong of a Cluster template | +| | | with the class OpenStackClusterTemplate | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_same_cluster_template | 1 | Tests the creation of a Cluster template 2x using the same| +| | | config object to ensure it was only created once | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad_flavor| 1 | Tests to ensure OpenStackClusterTemplate#create() will | +| | | raise an exception when the flavor is invalid | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad_master| 1 | Tests to ensure OpenStackClusterTemplate#create() will | +| _flavor | | raise an exception when the master flavor is invalid | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad_image | 1 | Tests to ensure OpenStackClusterTemplate#create() will | +| | | raise an exception when the image is invalid | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad | 1 | Tests to ensure OpenStackClusterTemplate#create() will | +| _network_driver | | raise an exception when the network driver is invalid | ++----------------------------------------+---------------+-----------------------------------------------------------+ +| test_create_cluster_template_bad | 1 | Tests to ensure OpenStackClusterTemplate#create() will | +| _volume_driver | | raise an exception when the volume driver is invalid | ++----------------------------------------+---------------+-----------------------------------------------------------+ + diff --git a/docs/how-to-use/LibraryUsage.rst b/docs/how-to-use/LibraryUsage.rst index 16cf446..aa7bf91 100644 --- a/docs/how-to-use/LibraryUsage.rst +++ b/docs/how-to-use/LibraryUsage.rst @@ -30,15 +30,16 @@ attributes are listed below: - auth\_url - project\_name (aka. tenant\_name) - identity\_api\_version (for obtaining Keystone authorization token. - Versions 2.0 & v3 only validated.) -- image\_api\_version (Glance version 1 & 2 only validated) + default = 2, Versions 2.0 & v3 only validated.) +- image\_api\_version (default = 2, Glance version 1 & 2 only validated) - network\_api\_version (Neutron version 2 currently only validated) - compute\_api\_version (Nova version 2 currently only validated) - heat\_api\_version (Heat version 1 currently only validated) +- volume\_api\_version (default = 2, Heat versions 2 & 3 currently only validated) - user\_domain\_id (default='default') -- user\_domain\_name (default='default') +- user\_domain\_name (default='Default') - project\_domain\_id (default='default') -- project\_domain\_name (default='default') +- project\_domain\_name (default='Default') - interface (default='admin', used to specify the endpoint type for keystone: public, admin, internal) - cacert (default=False, expected values T|F to denote server certificate verification, else value contains the path to an HTTPS certificate) - region_name (The region name default=None) @@ -78,7 +79,7 @@ Create User ----------- - User - snaps.openstack.create\_user.OpenStackUser - - snaps.openstack.create\_user.UserSettings + - snaps.openstack.user.UserConfig - name - the username (required) - password - the user's password (required) @@ -88,11 +89,14 @@ Create User - email - the user's email address (optional) - enabled - flag to determine whether or not the user should be enabled (default=True) + - roles - dict where key is the role's name and value is the name + the project to associate with the role (optional) .. code:: python - from snaps.openstack.create_user import UserSettings, OpenStackUser - user_settings = UserSettings(name='username', password='password') + from snaps.config.user import UserConfig + from snaps.openstack.create_user import OpenStackUser + user_settings = UserConfig(name='username', password='password') user_creator = OpenStackUser(os_creds, user_settings) user_creator.create() @@ -109,19 +113,20 @@ Create Project -------------- - Project - snaps.openstack.create\_project.OpenStackProject - - snaps.openstack.create\_project.ProjectSettings + - snaps.openstack.project.ProjectConfig - name - the project name (required) - domain - the project's domain (default='default') - description - the project's description (optional) - - enables - flag to determine whether or not the project should + - enabled - flag to determine whether or not the project should be enabled (default=True) .. code:: python - from snaps.openstack.create_project import ProjectSettings, OpenStackProject - project_settings = ProjectSettings(name='username', password='password') + from snaps.openstack.project import ProjectConfig + from snaps.openstack.create_project import OpenStackProject + project_settings = ProjectConfig(name='username', password='password') project_creator = OpenStackProject(os_creds, project_settings) project_creator.create() @@ -135,7 +140,7 @@ Create Flavor ------------- - Flavor - snaps.openstack.create\_flavor.OpenStackFlavor - - snaps.openstack.create\_flavor.FlavorSettings + - snaps.config.flavor.FlavorConfig - name - the flavor name (required) - flavor\_id - the flavor's string ID (default='auto') @@ -148,11 +153,13 @@ Create Flavor if backend supports QoS extension (default=1.0) - is\_public - flag that denotes whether or not other projects can access image (default=True) + - metadata - freeform dict() for special metadata (optional) .. code:: python - from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor - flavor_settings = FlavorSettings(name='flavor-name', ram=4, disk=10, vcpus=2) + from snaps.config.flavor import FlavorConfig + from snaps.openstack.create_flavor import OpenStackFlavor + flavor_settings = FlavorConfig(name='flavor-name', ram=4, disk=10, vcpus=2) flavor_creator = OpenStackFlavor(os_creds, flavor_settings) flavor_creator.create() @@ -166,27 +173,33 @@ Create Image ------------ - Image - snaps.openstack.create\_image.OpenStackImage - - snaps.openstack.create\_image.ImageSettings + - snaps.config.image.ImageConfig - name - the image name (required) - image\_user - the default image user generally used by OpenStackVMInstance class for obtaining an SSH connection (required) - - img\_format - the image's format (i.e. qcow2) (required) + - img\_format or format - the image's format (i.e. qcow2) (required) - url - the download URL to obtain the image file (this or image\_file must be configured, not both) - image\_file - the location of the file to be sourced from the local filesystem (this or url must be configured, not both) + - extra\_properties - dict() object containing extra parameters to + pass when loading the image (i.e. ids of kernel and initramfs images) - nic\_config\_pb\_loc - the location of the ansible playbook that can configure additional NICs. Floating IPs are required - to perform this operation. (optional) + to perform this operation. (optional and deprecated) + - kernel\_image\_settings - the image settings for a kernel image (optional) + - ramdisk\_image\_settings - the image settings for a ramdisk image (optional) + - public - image will be created with public visibility when True (default = False) .. code:: python - from snaps.openstack.create_image import ImageSettings, OpenStackImage - image_settings = ImageSettings(name='image-name', image_user='ubuntu', img_format='qcow2', - url='http://uec-images.ubuntu.com/releases/trusty/14.04/ubuntu-14.04-server-cloudimg-amd64-disk1.img') + from snaps.openstack.create_image import OpenStackImage + from snaps.config.image import ImageConfig + image_settings = ImageConfig(name='image-name', image_user='ubuntu', img_format='qcow2', + url='http://uec-images.ubuntu.com/releases/trusty/14.04/ubuntu-14.04-server-cloudimg-amd64-disk1.img') image_creator = OpenStackImage(os_creds, image_settings) image_creator.create() @@ -200,7 +213,7 @@ Create Keypair -------------- - Keypair - snaps.openstack.create\_keypair.OpenStackKeypair - - snaps.openstack.create\_keypair.KeypairSettings + - snaps.openstack.keypair.KeypairConfig - name - the keypair name (required) - public\_filepath - the file location to where the public key is @@ -209,11 +222,16 @@ Create Keypair file is to be written or currently resides (optional but highly recommended to leverage or the private key will be lost forever) + - key\_size - The number of bytes for the key size when it needs to + be generated (value must be >=512, default = 1024) + - delete\_on\_clean - when True, the key files will be deleted when + OpenStackKeypair#clean() is called (default = False) .. code:: python - from snaps.openstack.create_keypairs import KeypairSettings, OpenStackKeypair - keypair_settings = KeypairSettings(name='kepair-name', private_filepath='/tmp/priv-kp') + from snaps.openstack.keypair.KeypairConfig + from snaps.openstack.create_keypairs import OpenStackKeypair + keypair_settings = KeypairConfig(name='kepair-name', private_filepath='/tmp/priv-kp') keypair_creator = OpenStackKeypair(os_creds, keypair_settings) keypair_creator.create() @@ -228,7 +246,7 @@ Create Network - Network - snaps.openstack.create\_network.OpenStackNetwork - - snaps.openstack.create\_network.NetworkSettings + - snaps.config_network.NetworkConfig - name - the name of the network (required) - admin\_state\_up - flag denoting the administrative status of @@ -242,8 +260,10 @@ Create Network - network\_type - the type of network (i.e. vlan\|vxlan\|flat) - physical\_network - the name of the physical network (required when network\_type is 'flat') + - segmentation\_id - the id of the segmentation (required + when network\_type is 'vlan') - subnet\_settings (list of optional - snaps.openstack.create\_network.SubnetSettings objects) + snaps.config.network.SubnetConfig objects) - cidr - the subnet's CIDR (required) - ip\_version - 4 or 6 (default=4) @@ -267,10 +287,11 @@ Create Network .. code:: python - from snaps.openstack.create_network import NetworkSettings, SubnetSettings, OpenStackNetwork + from snaps.config.network import NetworkConfig, SubnetConfig + from snaps.openstack.create_network import OpenStackNetwork - subnet_settings = SubnetSettings(name='subnet-name', cidr='10.0.0.0/24') - network_settings = NetworkSettings(name='network-name', subnet_settings=[subnet_settings]) + subnet_settings = SubnetConfig(name='subnet-name', cidr='10.0.0.0/24') + network_settings = NetworkConfig(name='network-name', subnet_settings=[subnet_settings]) network_creator = OpenStackNetwork(os_creds, network_settings) network_creator.create() @@ -320,10 +341,12 @@ Create Security Group .. code:: python + from snaps.config.network import SubnetConfig + from snaps.config.rule import RuleConfig from snaps.openstack.create_security_group import SecurityGroupSettings, SecurityGroupRuleSettings, Direction, OpenStackSecurityGroup - rule_settings = SubnetSettings(name='subnet-name', cidr='10.0.0.0/24') - network_settings = NetworkSettings(name='network-name', subnet_settings=[subnet_settings]) + rule_settings = RuleConfig(name='subnet-name', cidr='10.0.0.0/24') + network_settings = SubnetConfig(name='network-name', subnet_settings=[subnet_settings]) sec_grp_name = 'sec-grp-name' rule_settings = SecurityGroupRuleSettings(name=sec_grp_name, direction=Direction.ingress) @@ -343,7 +366,7 @@ Create Router - Router - snaps.openstack.create\_router.OpenStackRouter - - snaps.openstack.create\_router.RouterSettings + - snaps.openstack.router.RouterConfig - name - the router name (required) - project\_name - the name of the project (optional - can only be @@ -356,30 +379,41 @@ Create Router - internal\_subnets - list of subnet names to which this router will connect (optional) - port\_settings (list of optional - snaps.openstack.create\_router.PortSettings objects) - creates + snaps.config.network.PortConfig objects) - creates custom ports to internal subnets (similar to internal\_subnets with more control) - - name - - network\_name - - admin\_state\_up + - name - the port's display name + - network\_name - the name of the network on which to create the port + - admin\_state\_up - A boolean value denoting the administrative + status of the port (default = True) - project\_name - the name of the project (optional - can only be set by admin users) - - mac\_address - - ip\_addrs - - fixed\_ips - - security\_groups - - allowed\_address\_pairs - - opt\_value - - opt\_name - - device\_owner - - device\_id + - mac\_address - the port's MAC address to set (optional and + recommended not to set this configuration value) + - ip\_addrs - list of dict() objects containing two keys 'subnet_name' + and 'ip' where the value of the 'ip' entry is the expected IP + address assigned. This value gets mapped to the fixed\_ips + attribute (optional) + - fixed\_ips - dict() where the key is the subnet ID and value is the + associated IP address to assign to the port (optional) + - security\_groups - list of security group IDs (not tested) + - allowed\_address\_pairs - A dictionary containing a set of zero or + more allowed address pairs. An address pair contains an IP address + and MAC address (optional) + - opt\_value - the extra DHCP option value (optional) + - opt\_name - the extra DHCP option name (optional) + - device\_owner - The ID of the entity that uses this port. + For example, a DHCP agent (optional) + - device\_id - The ID of the device that uses this port. + For example, a virtual server (optional) .. code:: python - from snaps.openstack.create_router import RouterSettings, OpenStackRouter + from snaps.config.router import RouterConfig + from snaps.openstack.create_router import OpenStackRouter - router_settings = RouterSettings(name='router-name', external_gateway='external') + router_settings = RouterConfig(name='router-name', external_gateway='external') router_creator = OpenStackRouter(os_creds, router_settings) router_creator.create() @@ -389,6 +423,122 @@ Create Router # Cleanup router_creator.clean() +Create QoS Spec +--------------- + +- Volume Type - snaps.openstack.create\_qos.OpenStackQoS + + - snaps.openstack.qos.QoSConfig + + - name - the volume type's name (required) + - consumer - the qos's consumer type of the enum type Consumer (required) + - specs - freeform dict() to be added as 'specs' (optional) + +.. code:: python + + from snaps.openstack.qos import QoSConfig + from snaps.openstack.create_qos import OpenStackQoS + + qos_settings = QoSConfig(name='stack-name', consumer=Consumer.front-end) + qos_creator = OpenStackQoS(os_creds, vol_type_settings) + qos_creator.create() + + # Perform logic + ... + + # Cleanup + qos_creator.clean() + +Create Volume Type +------------------ + +- Volume Type - snaps.openstack.create\_volume\_type.OpenStackVolumeType + + - snaps.config.volume\_type.VolumeTypeConfig + + - name - the volume type's name (required) + - description - the volume type's description (optional) + - encryption - instance or config for VolumeTypeEncryptionConfig (optional) + - qos\_spec\_name - name of the QoS Spec to associate (optional) + - public - instance or config for VolumeTypeEncryptionConfig (optional) + +.. code:: python + + from snaps.config.volume_type import VolumeTypeConfig + from snaps.openstack.create_volume_type import OpenStackVolumeType + + vol_type_settings = VolumeTypeConfig(name='stack-name') + vol_type_creator = OpenStackHeatStack(os_creds, vol_type_settings) + vol_type_creator.create() + + # Perform logic + ... + + # Cleanup + vol_type_creator.clean() + +Create Volume +------------- + +- Volume - snaps.openstack.create\_volume.OpenStackVolume + + - snaps.config.volume.VolumeConfig + + - name - the volume type's name (required) + - description - the volume type's description (optional) + - size - size of volume in GB (default = 1) + - image_name - when a glance image is used for the image source (optional) + - type\_name - the associated volume's type name (optional) + - availability\_zone - the name of the compute server on which to + deploy the volume (optional) + - multi_attach - when true, volume can be attached to more than one + server (default = False) + +.. code:: python + + from snaps.config.volume import VolumeConfig + from snaps.openstack.create\_volume import OpenStackVolume + + vol_settings = VolumeConfig(name='stack-name') + vol_creator = OpenStackVolume(os_creds, vol_settings) + vol_creator.create() + + # Perform logic + ... + + # Cleanup + vol_type_creator.clean() + +Create Heat Stack +----------------- + +- Heat Stack - snaps.openstack.create\_stack.OpenStackHeatStack + + - snaps.config.stack.StackConfig + + - name - the stack's name (required) + - template - the heat template in dict() format (required when + template_path is None) + - template\_path - the location of the heat template file (required + when template is None) + - env\_values - dict() of strings for substitution of template + default values (optional) + +.. code:: python + + from snaps.config.stack import StackConfig + from snaps.openstack.create_stack import OpenStackHeatStack + + stack_settings = StackConfig(name='stack-name', template_path='/tmp/template.yaml') + stack_creator = OpenStackHeatStack(os_creds, stack_settings) + stack_creator.create() + + # Perform logic + ... + + # Cleanup + stack_creator.clean() + Create VM Instance ------------------ @@ -399,7 +549,7 @@ Create VM Instance - name - the name of the VM (required) - flavor - the name of the flavor (required) - port\_settings - list of - snaps.openstack.create\_network.PortSettings objects where each + snaps.config.network.PortConfig objects where each denote a NIC (see above in create router section for details) API does not require, but newer NFVIs now require VMs have at least one network @@ -434,17 +584,17 @@ Create VM Instance - userdata - the cloud-init script to execute after VM has been started - - image\_settings - see snaps.openstack.create\_image.ImageSettings + - image\_settings - see snaps.config.image.ImageConfig above (required) - keypair\_settings - see - snaps.openstack.create\_keypairs.KeypairSettings above (optional) + snaps.openstack.keypair.KeypairConfig above (optional) .. code:: python from snaps.openstack.create_instance import VmInstanceSettings, FloatingIpSettings, OpenStackVmInstance - from snaps.openstack.create_network import PortSettings + from snaps.config.network import PortConfig - port_settings = PortSettings(name='port-name', network_name=network_settings.name) + port_settings = PortConfig(name='port-name', network_name=network_settings.name) floating_ip_settings = FloatingIpSettings(name='fip1', port_name=port_settings.name, router_name=router_settings.name) instance_settings = VmInstanceSettings(name='vm-name', flavor='flavor_settings.name', port_settings=[port_settings], floating_ip_settings=[floating_ip_settings]) @@ -503,7 +653,12 @@ an example of this pattern as this is the only API where SNAPS is supporting more than one version) - snaps.openstack.utils.keystone\_utils - for calls to the Keystone - APIs + APIs (support for versions 2 & 3) - snaps.openstack.utils.glance\_utils - for calls to the Glance APIs + (support for versions 1 & 2) - snaps.openstack.utils.neutron\_utils - for calls to the Neutron APIs -- snaps.openstack.utils.nova\_utils - for calls to the Nova APIs + (version 2) +- snaps.openstack.utils.nova\_utils - for calls to the Nova APIs (version 2) +- snaps.openstack.utils.heat\_utils - for calls to the Heat APIs (version 1) +- snaps.openstack.utils.cinder\_utils - for calls to the Cinder APIs + (support for versions 2 & 3) diff --git a/docs/how-to-use/UnitTests.rst b/docs/how-to-use/UnitTests.rst index 5fb04db..5bd4f08 100644 --- a/docs/how-to-use/UnitTests.rst +++ b/docs/how-to-use/UnitTests.rst @@ -36,11 +36,17 @@ OSCredsUnitTests Ensures that all required members are included when constructing a OSCreds object +SecurityGroupRuleConfigUnitTests +-------------------------------- + +Ensures that all required members are included when constructing a +SecurityGroupRuleConfig object + SecurityGroupRuleSettingsUnitTests ---------------------------------- Ensures that all required members are included when constructing a -SecurityGroupRuleSettings object +deprecated SecurityGroupRuleSettings object SecurityGroupRuleDomainObjectTests ---------------------------------- @@ -48,11 +54,17 @@ SecurityGroupRuleDomainObjectTests Ensures that all required members are included when constructing a SecurityGroupRule domain object +SecurityGroupConfigUnitTests +---------------------------- + +Ensures that all required members are included when constructing a +SecuirtyGroupConfig object + SecurityGroupSettingsUnitTests ------------------------------ Ensures that all required members are included when constructing a -SecuirtyGroupSettings object +deprecated SecuirtyGroupSettings object SecurityGroupDomainObjectTests ------------------------------ @@ -60,11 +72,17 @@ SecurityGroupDomainObjectTests Ensures that all required members are included when constructing a SecurityGroup domain object +ImageConfigUnitTests +-------------------- + +Ensures that all required members are included when constructing a +ImageConfig object + ImageSettingsUnitTests ---------------------- Ensures that all required members are included when constructing a -ImageSettings object +ImageSettings object (deprecated see ImageConfigUnitTests) ImageDomainObjectTests ---------------------- @@ -72,11 +90,17 @@ ImageDomainObjectTests Ensures that all required members are included when constructing a Image domain object +FlavorConfigUnitTests +--------------------- + +Ensures that all required members are included when constructing a +FlavorConfig object + FlavorSettingsUnitTests ----------------------- Ensures that all required members are included when constructing a -FlavorSettings object +deprecated FlavorSettings object FlavorDomainObjectTests ----------------------- @@ -84,11 +108,17 @@ FlavorDomainObjectTests Ensures that all required members are included when constructing a Flavor domain object +KeypairConfigUnitTests +---------------------- + +Ensures that all required members are included when constructing a +KeypairConfig object + KeypairSettingsUnitTests ------------------------ Ensures that all required members are included when constructing a -KeypairSettings object +deprecated KeypairSettings object KeypairDomainObjectTests ------------------------ @@ -96,11 +126,17 @@ KeypairDomainObjectTests Ensures that all required members are included when constructing a Keypair domain object +UserConfigUnitTests +------------------- + +Ensures that all required members are included when constructing a +UserConfig object + UserSettingsUnitTests --------------------- Ensures that all required members are included when constructing a -UserSettings object +deprecated UserSettings object UserDomainObjectTests --------------------- @@ -108,11 +144,17 @@ UserDomainObjectTests Ensures that all required members are included when constructing a User domain object +ProjectConfigUnitTests +---------------------- + +Ensures that all required members are included when constructing a +ProjectConfig object + ProjectSettingsUnitTests ------------------------ Ensures that all required members are included when constructing a -ProjectSettings object +deprecated ProjectSettings object ProjectDomainObjectTests ------------------------ @@ -144,11 +186,17 @@ RoleDomainObjectTests Ensures that all required members are included when constructing a Role domain object +NetworkConfigUnitTests +---------------------- + +Ensures that all required members are included when constructing a +NetworkConfig object + NetworkSettingsUnitTests ------------------------ Ensures that all required members are included when constructing a -NetworkSettings object +deprecated NetworkSettings object NetworkObjectTests ------------------ @@ -156,11 +204,17 @@ NetworkObjectTests Ensures that all required members are included when constructing a Network domain object +SubnetConfigUnitTests +--------------------- + +Ensures that all required members are included when constructing a +SubnetConfig object + SubnetSettingsUnitTests ----------------------- Ensures that all required members are included when constructing a -SubnetSettings object +deprecated SubnetSettings object SubnetObjectTests ----------------- @@ -168,11 +222,17 @@ SubnetObjectTests Ensures that all required members are included when constructing a Subnet domain object +PortConfigUnitTests +------------------- + +Ensures that all required members are included when constructing a +PortConfig object + PortSettingsUnitTests --------------------- Ensures that all required members are included when constructing a -PortSettings object +deprecated PortSettings object PortDomainObjectTests --------------------- @@ -180,11 +240,17 @@ PortDomainObjectTests Ensures that all required members are included when constructing a Port domain object +RouterConfigUnitTests +--------------------- + +Ensures that all required members are included when constructing a +RouterConfig object + RouterSettingsUnitTests ----------------------- Ensures that all required members are included when constructing a -RouterSettings object +deprecated RouterSettings object RouterDomainObjectTests ----------------------- @@ -198,11 +264,17 @@ InterfaceRouterDomainObjectTests Ensures that all required members are included when constructing a InterfaceRouter domain object +StackConfigUnitTests +-------------------- + +Ensures that all required members are included when constructing a +StackConfig object + StackSettingsUnitTests ---------------------- Ensures that all required members are included when constructing a -StackSettings object +deprecated StackSettings object StackDomainObjectTests ---------------------- @@ -222,11 +294,83 @@ OutputDomainObjectTests Ensures that all required members are included when constructing a Output domain object (for Heat) +VolumeConfigUnitTests +--------------------- + +Ensures that all required members are included when constructing a +VolumeConfig object + +VolumeSettingsUnitTests +----------------------- + +Ensures that all required members are included when constructing a +deprecated VolumeSettings object + +VolumeDomainObjectTests +----------------------- + +Ensures that all required members are included when constructing a +Volume domain object (for Cinder) + +VolumeTypeConfigUnitTests +------------------------- + +Ensures that all required members are included when constructing a +VolumeTypeConfig object + +VolumeTypeSettingsUnitTests +--------------------------- + +Ensures that all required members are included when constructing a +deprecated VolumeTypeSettings object + +VolumeTypeDomainObjectTests +--------------------------- + +Ensures that all required members are included when constructing a +VolumeType domain object (for Cinder) + +VolumeTypeEncryptionObjectTests +------------------------------- + +Ensures that all required members are included when constructing a +VolumeTypeEncryption domain object (for Cinder) + +QoSConfigUnitTests +------------------ + +Ensures that all required members are included when constructing a +QoSConfig object + +QoSSettingsUnitTests +-------------------- + +Ensures that all required members are included when constructing a +deprecated QoSSettings object + +QoSSpecDomainObjectTests +------------------------ + +Ensures that all required members are included when constructing a +QoSSpec domain object (for Cinder) + +VolumeDomainObjectTests +----------------------- + +Ensures that all required members are included when constructing a +Volume domain object (for Cinder) + +FloatingIpConfigUnitTests +------------------------- + +Ensures that all required members are included when constructing a +FloatingIpConfig object + FloatingIpSettingsUnitTests --------------------------- Ensures that all required members are included when constructing a -FloatingIpSettings object +depecated FloatingIpSettings object FloatingIpDomainObjectTests --------------------------- @@ -234,14 +378,46 @@ FloatingIpDomainObjectTests Ensures that all required members are included when constructing a FloatingIp domain object +VmInstanceConfigUnitTests +------------------------- + +Ensures that all required members are included when constructing a +VmInstanceConfig object + VmInstanceSettingsUnitTests --------------------------- Ensures that all required members are included when constructing a -VmInstanceSettings object +deprecated VmInstanceSettings object VmInstDomainObjectTests ----------------------- Ensures that all required members are included when constructing a VmInst domain object + +ClusterTemplateConfigUnitTests +------------------------------ + +Ensures that all required members are included when constructing a +ClusterTemplateConfig object + +ClusterTemplateUnitTests +------------------------ + +Ensures that all required members are included when constructing a +ClusterTemplate object + +SettingsUtilsUnitTests +---------------------- + +Ensures that the settings_utils.py#create_volume_config() function properly +maps a snaps.domain.Volume object correctly to a +snaps.config.volume.VolumeConfig object as well as a +snaps.domain.VolumeType object to a +snaps.config.volume.VolumeConfig object + + +Ensures that the settings_utils.py#create_flavor_config() function properly +maps a snaps.domain.Flavor object correctly to a +snaps.config.flavor.FlavorConfig object
\ No newline at end of file diff --git a/docs/how-to-use/VirtEnvDeploy.rst b/docs/how-to-use/VirtEnvDeploy.rst index dd95202..6c99992 100644 --- a/docs/how-to-use/VirtEnvDeploy.rst +++ b/docs/how-to-use/VirtEnvDeploy.rst @@ -3,9 +3,10 @@ Try an example Use launcher.py to deploy and clean up example environments. These examples are described in YAML files. -#. Add your OpenStack connection information to the deploy-complex-network.yaml. +#. Add your OpenStack connection information. - Edit <path to repo>/examples/complex-network/deploy-complex-network.yaml + Edit <path to repo>/examples/inst-w-volume/deploy-env.yaml with your OpenStack + credentials and authorization URL - openstack: the top level tag that denotes configuration for the OpenStack components @@ -16,8 +17,7 @@ Use launcher.py to deploy and clean up example environments. These examples are - auth\_url: - the URL to the OpenStack APIs (required) - project\_name: - the name of the OpenStack project for the user (required) - - http\_proxy: - the {{ host }}:{{ port }} of the proxy server the - HTTPPhotoman01(optional) + - http\_proxy: - the {{ host }}:{{ port }} of the proxy server (optional) #. Go to the examples directory. @@ -29,13 +29,13 @@ Use launcher.py to deploy and clean up example environments. These examples are :: - python launch.py -t ./complex-network/deploy-complex-network.yaml -d + python launch.py -t ./inst-w-volume/deploy-vm-with-volume.yaml -e ./inst-w-volume/deploy-env.yaml -d #. Clean the deployment. :: - python launch.py -t ./complex-network/deploy-complex-network.yaml -c + python launch.py -t ./complex-network/deploy-complex-network.yaml -e ./inst-w-volume/deploy-env.yaml -c #. Customize the deployment by changing the yaml file. @@ -46,183 +46,374 @@ Use launcher.py to deploy and clean up example environments. These examples are - openstack: the top level tag that denotes configuration for the OpenStack components - - connection: - contains the credentials and endpoints required to - connect with OpenStack - - username: - the project's user (required) - - password: - the tentant's user password (required) - - auth\_url: - the URL to the OpenStack APIs (required) - - project\_name: - the name of the OpenStack project for the user - (required) - - http\_proxy: - the {{ host }}:{{ port }} of the proxy server the - HTTPPhotoman01(optional) - - images: - describes each image - - image: - - - name: The unique image name. If the name already exists for - your project, a new one will not be created (required) - - format: The format type of the image i.e. qcow2 (required) - - download\_url: The HTTP download location of the image file - (required) - - nic\_config\_pb\_loc: The file location relative to the CWD - (python directory) to the Ansible Playbook used to configure - VMs with more than one port. VMs get their first NIC configured - for free while subsequent ones are not. This value/script will - only be leveraged when necessary. Centos has been supported - with - "provisioning/ansible/centos-network-setup/configure\_host.yml". + - connections: the different connections/credentials to be used by the + launcher application + + - connection: the credentials and endpoints required to connect to an + OpenStack project/tenant + + - name: the name of the credentials for use when creating objects (required) + - username: the project's user (required) + - password: the tentant's user password (required) + - auth\_url: the URL to the OpenStack APIs (required) + - project\_name: the name of the OpenStack project for the user + (required) + - identity\_api\_version: the Keystone client version to use (default = 2) + - image\_api\_version: the Glance client version to use (default = 2) + - network\_api\_version: the Neutron client version to use (default = 2) + - compute\_api\_version: the Nova client version to use (default = 2) + - heat\_api\_version: the Heat client version to use (default = 1) + - volume\_api\_version: the Cinder client version to use (default = 2) + - user\_domain\_id: the user domain ID to use (default = 'default') + - user\_domain\_name: the user domain name to use (default = 'Default') + - project\_domain\_id: the project domain ID to use (default = 'default') + - project\_domain\_name: the project domain name to use (default = 'Default') + - interface: Used to specify the endpoint type for keystone (default = 'public') + - cacert: True for https or the certification file location (default = False) + - region\_name: the region (default = None) + - proxy\_settings: for accessing APIs hidden behind an HTTP proxy + + - host: hostname or IP of HTTP proxy host (required) + - port: port number of the HTTP proxy server (required) + - http\_host: hostname or IP of HTTPS proxy host (default = host) + - port: port number of the HTTPS proxy server (default = port) + - ssh\_proxy\_cmd: the OpenSSH command used to access the SSH port + of a VM (optional) + + - projects: the projects/tenants to create + + - project: a project/tenant to create (admin user credentials required) + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - name: the project's name (required) + - domain or domain_name: the project's domain name (default = 'Default') + - description: the description (optional) + - users: a list of users to associate to the project (optional) + - enabled: when True the project will be enabled on creation (default = True) + + - users: the users to create + + - user: a user to create (admin user credentials required) + + - os\_creds\_name: the connection name (required) + - name: the username (required) + - password: the user's password (required) + - project\_name: the user's primary project name (optional) + - domain\_name: the user's domain name (default = 'Default') + - email: the user's email address (optional) + - roles: dict where key is the role's name and value is the name + of the project to associate with the role (optional) + + - flavors: the flavors to create + + - flavor: a flavor to create (admin user credentials required) + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - name: the name (required) + - flavor\_id: the string ID (default 'auto') + - ram: the required RAM in MB (required) + - disk: the size of the root disk in GB (required) + - vcpus: the number of virtual CPUs (required) + - ephemeral: the size of the ephemeral disk in GB (default 0) + - swap: the size of the dedicated swap disk in GB (default 0) + - rxtx\_factor: the receive/transmit factor to be set on ports if + backend supports QoS extension (default 1.0) + - is\_public: denotes whether or not the flavor is public (default = True) + - metadata: freeform dict() for special metadata (optional) + + - qos_specs: the QoS Specs to create + + - qos_spec: a QoS Spec to create (admin user credentials required) + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - name: the name (required) + - consumer: enumerations: 'front-end', 'back-end', 'both' (required) + - specs: dict of custom values (optional) + + - volume_types: the Volume Type to create + + - volume_type: a Volume Type to create (admin user credentials required) + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - name: the name (required) + - description: the description (optional) + - qos_spec_name: the name of the associate QoS Spec (optional) + - public: visibility (default - False) + - encryption: the encryption settings (optional) + + - name: the name (required) + - provider_class: the provider class (required i.e. LuksEncryptor) + - control_location: enumerations: 'front-end', 'back-end' (required) + - cipher: the encryption algorithm/mode to use (optional) + - key_size: the size of the encryption key, in bits (optional) + + - volumes: the Volume to create + + - volume: a Volume to create + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - os\_user: the connection from a new user defined in template + (required or use "os\_creds\_name" above + + - name: the user's name (required) + - project\_name: the project name to use + + - name: the name (required) + - description: the description (optional) + - size: the volume size in GB (default = 1) + - image_name: the image name to leverage (optional) + - type_name: the volume type name to associate (optional) + - availability_zone: the zone name on which to deploy (optional) + - multi_attach: when true, volume can be attached to more than one VM + (default = False) + + - images: describes each image to create + + - image: + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - os\_user: the connection from a new user defined in template + (required or use "os\_creds\_name" above + + - name: the user's name (required) + - project\_name: the project name to use + + - name: The unique image name. If the name already exists for + your project, a new one will not be created (required) + - image\_user: the image's default sudo user (required) + - format or img\_format: the image format type (required i.e. qcow2) + - url or download\_url: The HTTP download location of the image file + (required when "image_file" below has not been configured) + - image\_file: the image file location (required when "url" has not + been configured) + - kernel\_image\_settings: the settings for a kernel image (optional) + - ramdisk\_image\_settings: the settings for a kernel image (optional) + - public: publically visibile when True (default = True) - networks: - - network: - - - name: The name of the network to be created. If one already - exists, a new one will not be created (required) - - admin\_state\_up: T\|F (default True) - - shared: (optional) - - project\_name: Name of the project who owns the network. Note: - only administrative users can specify projects other than their - own (optional) - - external: T\|F whether or not network is external (default - False) - - network\_type: The type of network to create. (optional) - - subnets: - - subnet: - - - name: The name of the network to be created. If one already - exists, a new one will not be created. Note: although - OpenStack allows for multiple subnets to be applied to any - given network, we have not included support as our current - use cases does not utilize this functionality (required) - - cidr: The subnet mask value (required) - - dns\_nameservers: A list of IP values used for DNS - resolution (default: 8.8.8.8) - - ip\_version: 4\|6 (default: 4) - - project\_name: Name of the project who owns the network. - Note: only administrative users can specify projects other - than their own (optional) - - start: The start address for allocation\_pools (optional) - - end: The ending address for allocation\_pools (optional) - - gateway\_ip: The IP address to the gateway (optional) - - enable\_dhcp: T\|F (optional) - - dns\_nameservers: List of DNS server IPs - - host\_routes: A list of host route dictionaries (optional) - i.e.: - ``yaml "host_routes":[ { "destination":"0.0.0.0/0", "nexthop":"123.456.78.9" }, { "destination":"192.168.0.0/24", "nexthop":"192.168.0.1" } ]`` - - destination: The destination for a static route (optional) - - nexthop: The next hop for the destination (optional) - - ipv6\_ra\_mode: Valid values: "dhcpv6-stateful", - "dhcpv6-stateless", and "slaac" (optional) - - ipv6\_address\_mode: Valid values: "dhcpv6-stateful", - "dhcpv6-stateless", and "slaac" (optional) + - network: + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - os\_user: the connection from a new user defined in template + (required or use "os\_creds\_name" above + + - name: the user's name (required) + - project\_name: the project name to use + + - name: The name of the network to be created. If one already + exists, a new one will not be created (required) + - admin\_state\_up: T\|F (default True) + - shared: (optional) + - project\_name: Name of the project who owns the network. Note: + only administrative users can specify projects other than their + own (optional) + - external: T\|F whether or not network is external (default False) + - network\_type: The type of network to create (optional) + - physical\_network: the name of the physical network + (required when network_type is 'flat') + - segmentation\_id: the id of the segmentation + (required when network_type is 'vlan') + - subnets: + - subnet: + + - name: The name of the network to be created. If one already + exists, a new one will not be created. Note: although + OpenStack allows for multiple subnets to be applied to any + given network, we have not included support as our current + use cases does not utilize this functionality (required) + - cidr: The subnet mask value (required) + - dns\_nameservers: A list of IP values used for DNS + resolution (default: 8.8.8.8) + - ip\_version: 4\|6 (default: 4) + - project\_name: Name of the project who owns the network. + Note: only administrative users can specify projects other + than their own (optional) + - start: The start address for allocation\_pools (optional) + - end: The ending address for allocation\_pools (optional) + - gateway\_ip: The IP address to the gateway (optional) + - enable\_dhcp: T\|F (optional) + - dns\_nameservers: List of DNS server IPs (default = ['8.8.8.8'] + - host\_routes: A list of host route dictionaries (optional) + i.e.: + ``yaml "host_routes":[ { "destination":"0.0.0.0/0", "nexthop":"123.456.78.9" }, { "destination":"192.168.0.0/24", "nexthop":"192.168.0.1" } ]`` + - destination: The destination for a static route (optional) + - nexthop: The next hop for the destination (optional) + - ipv6\_ra\_mode: Valid values: "dhcpv6-stateful", + "dhcpv6-stateless", and "slaac" (optional) + - ipv6\_address\_mode: Valid values: "dhcpv6-stateful", + "dhcpv6-stateless", and "slaac" (optional) + + - security_groups: + + - security_group: + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - os\_user: the connection from a new user defined in template + (required or use "os\_creds\_name" above + + - name: the user's name (required) + - project\_name: the project name to use + + - name: The name of the security group to be created (required) + - description: The security group's description (optional) + - project\_name: Name of the project who owns the security group (optional) + - rule\_settings: List of rules to place onto security group (optional) + + - description: the rule's description (optional) + - protocol: rule's protcol ('icmp' or 'tcp' or 'udp' or 'null') + - ethertype: rule's ethertype ('4' or '6') + - port\_range\_min: The minimum port number in the range that is + matched by the security group rule. When the protocol is 'tcp' + or 'udp', this value must be <= 'port_range_max' (optional) + - port\_range\_max: The maximum port number in the range that is + matched by the security group rule. When the protocol is 'tcp' + or 'udp', this value must be <= 'port_range_max' (optional) + - remote\_ip\_prefix: The remote IP prefix to associate with this + metering rule packet (optional) - routers: - router: - - name: The name of the router to be created. If one already - exists, a new one will not be created (required) - - project\_name: Name of the project who owns the network. Note: - only administrative users can specify projects other than their - own (optional) - - internal\_subnets: A list of subnet names on which the router - will be placed (optional) - - external\_gateway: A dictionary containing the external gateway - parameters: "network\_id", "enable\_snat", - "external\_fixed\_ips" (optional) - - interfaces: A list of port interfaces to create to other - subnets (optional) - - - port (Leverages the same class/structure as port objects on - VM instances. See port definition below for a - full accounting of the port attributes. The ones listed - below are generally used for routers) - - - name: The name given to the new port (must be unique for - project) (required) - - network\_name: The name of the new port's network - (required) - - ip\_addrs: A list of k/v pairs (optional) - - subnet\_name: the name of a subnet that is on the port's - network - - ip: An IP address of the associated subnet to assign to - the new port (optional but generally required for router - interfaces) + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - os\_user: the connection from a new user defined in template + (required or use "os\_creds\_name" above + + - name: the user's name (required) + - project\_name: the project name to use + + - name: The name of the router to be created (required) + - project\_name: Name of the project who owns the network (optional) + - external\_gateway: Name of the external network to which to route + (optional) + - admin\_state\_up: T\|F (default True) + - external\_fixed\_ids: Dictionary containing the IP address + parameters (optional) + - internal\_subnets: List of subnet names to which to connect this + router (optional) + + - port_settings (Leverages the same class/structure as port objects on + VM instances. See port definition below for a + full accounting of the port attributes. The ones listed + below are generally used for routers) + + - name: The name given to the new port (required and must be + unique for project) + - network\_name: The name of the network on which to create + the port (optional) + - admin\_state\_up: T\|F (default True) + - project\_name: Name of the project who owns the network (optional) + - mac\_address: The port's MAC address (optional) + - ip\_addrs: A list of k/v pairs (optional) + - security\_groups: a list of names of the the security groups + to apply to the port + - opt\_value: The extra DHCP option value (optional) + - opt\_name: The extra DHCP option name (optional) - keypairs: - keypair: - - name: The name of the keypair to be created. If one already - exists, a new one will not be created but simply loaded from - its configured file location (required) - - public\_filepath: The path to where the generated public key - will be stored if it does not exist (optional but really - required for provisioning purposes) - - private\_filepath: The path to where the generated private key - will be stored if it does not exist (optional but really - required for provisioning purposes) + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - os\_user: the connection from a new user defined in template + (required or use "os\_creds\_name" above + + - name: the user's name (required) + - project\_name: the project name to use + + - name: The name of the keypair to be created. If one already + exists, a new one will not be created but simply loaded from + its configured file location (required) + - public\_filepath: The path to where the generated public key + will be stored if it does not exist (optional but really + required for provisioning purposes) + - private\_filepath: The path to where the generated private key + will be stored if it does not exist (optional but really + required for provisioning purposes) - instances: - instance: - - name: The unique instance name for project. (required) - - flavor: Must be one of the preconfigured flavors (required) - - imageName: The name of the image to be used for deployment - (required) - - keypair\_name: The name of the keypair to attach to instance - (optional but required for NIC configuration and Ansible - provisioning) - - sudo\_user: The name of a sudo\_user that is attached to the - keypair (optional but required for NIC configuration and - Ansible provisioning) - - vm\_boot\_timeout: The number of seconds to block waiting for - an instance to deploy and boot (default 900) - - vm\_delete\_timeout: The number of seconds to block waiting for - an instance to be deleted (default 300) - - ssh\_connect\_timeout: The number of seconds to block waiting - for an instance to achieve an SSH connection (default 120) - - ports: A list of port configurations (should contain at least - one) - - port: Denotes the configuration of a NIC - - - name: The unique port name for project (required) - - network\_name: The name of the network to which the port is - attached (required) - - ip\_addrs: Static IP addresses to be added to the port by - subnet (optional) - - subnet\_name: The name of the subnet - - ip: The assigned IP address (when null, OpenStack will - assign an IP to the port) - - admin\_state\_up: T\|F (default True) - - project\_name: The name of the project who owns the network. - Only administrative users can specify a the project ID other - than their own (optional) - - mac\_address: The desired MAC for the port (optional) - - fixed\_ips: A dictionary that allows one to specify only a - subnet ID, OpenStack Networking allocates an available IP - from that subnet to the port. If you specify both a subnet - ID and an IP address, OpenStack Networking tries to allocate - the specified address to the port. (optional) - - seurity\_groups: A list of security group IDs (optional) - - allowed\_address\_pairs: A dictionary containing a set of - zero or more allowed address pairs. An address pair contains - an IP address and MAC address. (optional) - - opt\_value: The extra DHCP option value (optional) - - opt\_name: The extra DHCP option name (optional) - - device\_owner: The ID of the entity that uses this port. For - example, a DHCP agent (optional) - - device\_id: The ID of the device that uses this port. For - example, a virtual server (optional) - - - floating\_ips: list of floating\_ip configurations (optional) - - - floating\_ip: - - name: Must be unique for VM instance (required) - - port\_name: The name of the port requiring access to the - external network (required) - - subnet\_name: The name of the subnet contains the IP address on - the port on which to create the floating IP (optional) - - router\_name: The name of the router connected to an external - network used to attach the floating IP (required) - - provisioning: (True\|False) Denotes whether or not this IP can - be used for Ansible provisioning (default True) + + - os\_creds\_name: the connection name (default = 'default' + required or use "os\_user" below instead) + - os\_user: the connection from a new user defined in template + (required or use "os\_creds\_name" above + + - name: the user's name (required) + - project\_name: the project name to use + + - name: The unique instance name for project. (required) + - flavor: Must be one of the preconfigured flavors (required) + - imageName: The name of the image to be used for deployment + (required) + - keypair\_name: The name of the keypair to attach to instance + (optional but required for NIC configuration and Ansible + provisioning) + - sudo\_user: The name of a sudo\_user that is attached to the + keypair (optional but required for NIC configuration and + Ansible provisioning) + - vm\_boot\_timeout: The number of seconds to block waiting for + an instance to deploy and boot (default 900) + - vm\_delete\_timeout: The number of seconds to block waiting for + an instance to be deleted (default 300) + - ssh\_connect\_timeout: The number of seconds to block waiting + for an instance to achieve an SSH connection (default 120) + - ports: A list of port configurations (should contain at least + one) + - port: Denotes the configuration of a NIC + + - name: The unique port name for project (required) + - network\_name: The name of the network to which the port is + attached (required) + - ip\_addrs: Static IP addresses to be added to the port by + subnet (optional) + - subnet\_name: The name of the subnet + - ip: The assigned IP address (when null, OpenStack will + assign an IP to the port) + - admin\_state\_up: T\|F (default True) + - project\_name: The name of the project who owns the network. + Only administrative users can specify a the project ID other + than their own (optional) + - mac\_address: The desired MAC for the port (optional) + - fixed\_ips: A dictionary that allows one to specify only a + subnet ID, OpenStack Networking allocates an available IP + from that subnet to the port. If you specify both a subnet + ID and an IP address, OpenStack Networking tries to allocate + the specified address to the port. (optional) + - seurity\_groups: A list of security group IDs (optional) + - allowed\_address\_pairs: A dictionary containing a set of + zero or more allowed address pairs. An address pair contains + an IP address and MAC address. (optional) + - opt\_value: The extra DHCP option value (optional) + - opt\_name: The extra DHCP option name (optional) + - device\_owner: The ID of the entity that uses this port. For + example, a DHCP agent (optional) + - device\_id: The ID of the device that uses this port. For + example, a virtual server (optional) + + - floating\_ips: list of floating\_ip configurations (optional) + + - floating\_ip: + - name: Must be unique for VM instance (required) + - port\_name: The name of the port requiring access to the + external network (required) + - subnet\_name: The name of the subnet contains the IP address on + the port on which to create the floating IP (optional) + - router\_name: The name of the router connected to an external + network used to attach the floating IP (required) + - provisioning: (True\|False) Denotes whether or not this IP can + be used for Ansible provisioning (default True) - ansible: Each set of attributes below are contained in a list diff --git a/examples/demo.py b/examples/demo.py index 108bdc0..87c095b 100644 --- a/examples/demo.py +++ b/examples/demo.py @@ -1,22 +1,29 @@ import logging + +from snaps.config.vm_inst import VmInstanceConfig + logging.basicConfig(level=logging.INFO) # Credentials from snaps.openstack.os_credentials import OSCreds, ProxySettings -proxy_settings = ProxySettings(host='10.197.123.27', port='3128', - ssh_proxy_cmd='/usr/local/bin/corkscrew 10.197.123.27 3128 %h %p') +proxy_settings = ProxySettings( + host='10.197.123.27', port='3128', + ssh_proxy_cmd='/usr/local/bin/corkscrew 10.197.123.27 3128 %h %p') -os_creds = OSCreds(username='admin', password='cable123', auth_url='http://192.168.67.10:5000/v2.0/', - project_name='admin', proxy_settings=proxy_settings) +os_creds = OSCreds( + username='admin', password='cable123', + auth_url='http://192.168.67.10:5000/v2.0/', project_name='admin', + proxy_settings=proxy_settings) # Images -from snaps.openstack.create_image import ImageSettings, OpenStackImage +from snaps.openstack.create_image import OpenStackImage +from snaps.config.image import ImageConfig -image_settings = ImageSettings(name='cirros-test', image_user='cirros', img_format='qcow2', - url='http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img') +image_settings = ImageConfig(name='cirros-test', image_user='cirros', img_format='qcow2', + url='http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img') image = OpenStackImage(os_creds, image_settings) image.create() @@ -24,27 +31,33 @@ image.create() # Network -from snaps.openstack.create_network import NetworkSettings, SubnetSettings, OpenStackNetwork +from snaps.config.network import NetworkConfig, SubnetConfig +from snaps.openstack.create_network import OpenStackNetwork -subnet_settings = SubnetSettings(name='test-subnet', cidr='10.0.0.1/24') -network_settings = NetworkSettings(name='test-net', subnet_settings=[subnet_settings]) +subnet_settings = SubnetConfig(name='test-subnet', cidr='10.0.0.1/24') +network_settings = NetworkConfig( + name='test-net', subnet_settings=[subnet_settings]) network = OpenStackNetwork(os_creds, network_settings) network.create() # Flavors -from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor +from snaps.config.flavor import FlavorConfig +from snaps.openstack.create_flavor import OpenStackFlavor -flavor_settings = FlavorSettings(name='test-flavor', ram=256, disk=10, vcpus=2) +flavor_settings = FlavorConfig(name='test-flavor', ram=256, disk=10, vcpus=2) flavor = OpenStackFlavor(os_creds, flavor_settings) flavor.create() # Instances -from snaps.openstack.create_network import PortSettings -from snaps.openstack.create_instance import VmInstanceSettings, OpenStackVmInstance - -port_settings = PortSettings(name='test-port', network_name=network_settings.name) -instance_settings = VmInstanceSettings(name='test-inst', flavor=flavor_settings.name, port_settings=[port_settings]) +from snaps.config.network import PortConfig +from snaps.openstack.create_instance import OpenStackVmInstance + +port_settings = PortConfig( + name='test-port', network_name=network_settings.name) +instance_settings = VmInstanceConfig( + name='test-inst', flavor=flavor_settings.name, + port_settings=[port_settings]) vm_inst = OpenStackVmInstance(os_creds, instance_settings, image_settings) vm_inst.create(block=True) diff --git a/examples/inst-w-volume/deploy-env.yaml b/examples/inst-w-volume/deploy-env.yaml new file mode 100644 index 0000000..7baf8a2 --- /dev/null +++ b/examples/inst-w-volume/deploy-env.yaml @@ -0,0 +1,44 @@ +--- +admin_user: admin +admin_pass: ChangeMe +admin_proj: admin +auth_url: http://10.197.103.31:5000/ +proxy_host: +proxy_port: +id_api_version: 3 + +ext_net: public1 + +username: test_user +pass: test_user +proj: example-volume + +flavor_name: example.m1.small +flavor_ram: 2048 +flavor_disk: 10 +flavor_cpus: 1 + +qos_name: example_qos +vol_type_name: example-vol-type +vol_type_encryption_name: example_vol-type-encryption +volume_name: example-volume + +image_name: example-image +image_format: qcow2 +image_url: http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img +image_file: +image_user: cirros + +net_name: example-net +subnet_name: example-subnet +cidr: 10.0.8.0/24 +router_name: example-router + +kp_name: example-kp +kp_pub_path: ~/tmp/example-kp.pub +kp_priv_path: ~/tmp/example-kp + +sg_name: example-sg + +port_name: example-port +inst_name: example-inst diff --git a/examples/inst-w-volume/deploy-vm-with-volume.yaml b/examples/inst-w-volume/deploy-vm-with-volume.yaml new file mode 100644 index 0000000..30dbcc0 --- /dev/null +++ b/examples/inst-w-volume/deploy-vm-with-volume.yaml @@ -0,0 +1,141 @@ +# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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: + connections: + # Note - when http_proxy is set, you must also configure ssh for proxy tunneling on your host. + - connection: + name: admin-creds + username: {{ admin_user }} + project_name: {{ admin_proj }} + password: {{ admin_pass }} + auth_url: {{ auth_url }} + identity_api_version: {{ id_api_version }} + projects: + - project: + os_creds_name: admin-creds + name: {{ proj }} + description: Project for Orchestrators + users: + - {{ username }} + - {{ admin_user }} + users: + - user: + os_creds_name: admin-creds + name: {{ username }} + password: {{ pass }} + project_name: {{ proj }} + roles: {admin: {{ proj }}} + flavors: + - flavor: + os_creds_name: admin-creds + name: {{ flavor_name }} + ram: {{ flavor_ram }} + disk: {{ flavor_disk }} + vcpus: {{ flavor_cpus }} + qos_specs: + - qos_spec: + os_creds_name: admin-creds + name: {{ qos_name }} + consumer: both + volume_types: + - volume_type: + os_creds_name: admin-creds + name: {{ vol_type_name }} + encryption: + name: {{ vol_type_encryption_name }} + provider_class: LuksEncryptor + control_location: front-end + volumes: + - volume: + os_user: + name: {{ username }} + project_name: {{ proj }} + name: {{ volume_name }} + size: 10 + images: + - image: + os_creds_name: admin-creds + name: {{ image_name }} + format: {{ image_format }} + image_user: {{ image_user }} + download_url: {{ image_url }} + image_file: {{ image_file }} + public: True + networks: + - network: + os_user: + name: {{ username }} + project_name: {{ proj }} + name: {{ net_name }} + project_name: {{ proj }} + subnets: + - subnet: + name: {{ subnet_name }} + project_name: {{ proj }} + cidr: {{ cidr }} + dns_nameservers: [8.8.8.8] + routers: + - router: + os_user: + name: {{ username }} + project_name: {{ proj }} + name: {{ router_name }} + external_gateway: {{ ext_net }} + internal_subnets: + - {{ subnet_name }} + keypairs: + - keypair: + os_user: + name: {{ username }} + project_name: {{ proj }} + name: {{ kp_name }} + public_filepath: {{ kp_pub_path }} + private_filepath: {{ kp_priv_path }} + delete_on_clean: True + security_groups: + - security_group: + os_user: + name: {{ username }} + project_name: {{ proj }} + name: {{ sg_name }} + rules: + - direction: ingress + protocol: icmp + - direction: ingress + protocol: tcp + port_range_min: 22 + port_range_max: 22 + instances: + - instance: + os_user: + name: {{ username }} + project_name: {{ proj }} + name: {{ inst_name }} + flavor: {{ flavor_name }} + imageName: {{ image_name }} + keypair_name: {{ kp_name }} + security_group_names: [{{ sg_name }}] + volume_names: + - {{ volume_name }} + ports: + - port: + name: {{ port_name_prfx }}-1a + network_name: {{ net_name }} + floating_ips: + - floating_ip: + name: fip1 + port_name: {{ port_name }} + router_name: {{ router_name }} diff --git a/examples/launch.py b/examples/launch.py index 76353a2..04bc5d6 100644 --- a/examples/launch.py +++ b/examples/launch.py @@ -18,544 +18,19 @@ # This script is responsible for deploying virtual environments import argparse import logging -import re -import time from jinja2 import Environment, FileSystemLoader import os import yaml from snaps import file_utils -from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor -from snaps.openstack.create_image import ImageSettings, OpenStackImage -from snaps.openstack.create_instance import VmInstanceSettings -from snaps.openstack.create_keypairs import KeypairSettings, OpenStackKeypair -from snaps.openstack.create_network import ( - PortSettings, NetworkSettings, OpenStackNetwork) -from snaps.openstack.create_project import OpenStackProject, ProjectSettings -from snaps.openstack.create_router import RouterSettings, OpenStackRouter -from snaps.openstack.create_security_group import ( - OpenStackSecurityGroup, SecurityGroupSettings) -from snaps.openstack.create_user import OpenStackUser, UserSettings -from snaps.openstack.os_credentials import OSCreds, ProxySettings -from snaps.openstack.utils import deploy_utils -from snaps.provisioning import ansible_utils +from snaps.openstack.utils import launch_utils __author__ = 'spisarski' logger = logging.getLogger('snaps_launcher') ARG_NOT_SET = "argument not set" -DEFAULT_CREDS_KEY = 'admin' - - -def __get_creds_dict(os_conn_config): - """ - Returns a dict of OSCreds where the key is the creds name. - For backwards compatibility, credentials not contained in a list (only - one) will be returned with the key of None - :param os_conn_config: the credential configuration - :return: a dict of OSCreds objects - """ - if 'connection' in os_conn_config: - return {DEFAULT_CREDS_KEY: __get_os_credentials(os_conn_config)} - elif 'connections' in os_conn_config: - out = dict() - for os_conn_dict in os_conn_config['connections']: - config = os_conn_dict.get('connection') - if not config: - raise Exception('Invalid connection format') - - name = config.get('name') - if not name: - raise Exception('Connection config requires a name field') - - out[name] = __get_os_credentials(os_conn_dict) - return out - - -def __get_creds(os_creds_dict, os_user_dict, inst_config): - """ - Returns the appropriate credentials - :param os_creds_dict: a dictionary of OSCreds objects where the name is the - key - :param os_user_dict: a dictionary of OpenStackUser objects where the name - is the key - :param inst_config: - :return: an OSCreds instance or None - """ - os_creds = os_creds_dict.get(DEFAULT_CREDS_KEY) - if 'os_user' in inst_config: - os_user_conf = inst_config['os_user'] - if 'name' in os_user_conf: - user_creator = os_user_dict.get(os_user_conf['name']) - if user_creator: - return user_creator.get_os_creds( - project_name=os_user_conf.get('project_name')) - elif 'os_creds_name' in inst_config: - if 'os_creds_name' in inst_config: - os_creds = os_creds_dict[inst_config['os_creds_name']] - return os_creds - - -def __get_os_credentials(os_conn_config): - """ - Returns an object containing all of the information required to access - OpenStack APIs - :param os_conn_config: The configuration holding the credentials - :return: an OSCreds instance - """ - config = os_conn_config.get('connection') - if not config: - raise Exception('Invalid connection configuration') - - proxy_settings = None - http_proxy = config.get('http_proxy') - if http_proxy: - tokens = re.split(':', http_proxy) - ssh_proxy_cmd = config.get('ssh_proxy_cmd') - proxy_settings = ProxySettings(host=tokens[0], port=tokens[1], - ssh_proxy_cmd=ssh_proxy_cmd) - else: - if 'proxy_settings' in config: - host = config['proxy_settings'].get('host') - port = config['proxy_settings'].get('port') - if host and host != 'None' and port and port != 'None': - proxy_settings = ProxySettings(**config['proxy_settings']) - - if proxy_settings: - config['proxy_settings'] = proxy_settings - else: - del config['proxy_settings'] - - return OSCreds(**config) - - -def __parse_ports_config(config): - """ - Parses the "ports" configuration - :param config: The dictionary to parse - :return: a list of PortConfig objects - """ - out = list() - for port_config in config: - out.append(PortSettings(**port_config.get('port'))) - return out - - -def __create_instances(os_creds_dict, creator_class, config_class, config, - config_key, cleanup=False, os_users_dict=None): - """ - Returns a dictionary of SNAPS creator objects where the key is the name - :param os_creds_dict: Dictionary of OSCreds objects where the key is the - name - :param config: The list of configurations for the same type - :param config_key: The list of configurations for the same type - :param cleanup: Denotes whether or not this is being called for cleanup - :return: dictionary - """ - out = {} - - if config: - try: - for config_dict in config: - inst_config = config_dict.get(config_key) - if inst_config: - creator = creator_class( - __get_creds(os_creds_dict, os_users_dict, inst_config), - config_class(**inst_config)) - - if cleanup: - creator.initialize() - else: - creator.create() - out[inst_config['name']] = creator - logger.info('Created configured %s', config_key) - except Exception as e: - logger.error('Unexpected error instantiating creator [%s] ' - 'with exception %s', creator_class, e) - - return out - - -def __create_vm_instances(os_creds_dict, os_users_dict, instances_config, - image_dict, keypairs_dict, cleanup=False): - """ - Returns a dictionary of OpenStackVmInstance objects where the key is the - instance name - :param os_creds_dict: Dictionary of OSCreds objects where the key is the - name - :param os_users_dict: Dictionary of OpenStackUser objects where the key is - the username - :param instances_config: The list of VM instance configurations - :param image_dict: A dictionary of images that will probably be used to - instantiate the VM instance - :param keypairs_dict: A dictionary of keypairs that will probably be used - to instantiate the VM instance - :param cleanup: Denotes whether or not this is being called for cleanup - :return: dictionary - """ - vm_dict = {} - - if instances_config: - try: - for instance_config in instances_config: - conf = instance_config.get('instance') - if conf: - if image_dict: - image_creator = image_dict.get(conf.get('imageName')) - if image_creator: - instance_settings = VmInstanceSettings( - **instance_config['instance']) - kp_name = conf.get('keypair_name') - vm_dict[conf[ - 'name']] = deploy_utils.create_vm_instance( - __get_creds( - os_creds_dict, os_users_dict, conf), - instance_settings, - image_creator.image_settings, - keypair_creator=keypairs_dict[kp_name], - init_only=cleanup) - else: - raise Exception('Image creator instance not found.' - ' Cannot instantiate') - else: - raise Exception('Image dictionary is None. Cannot ' - 'instantiate') - else: - raise Exception('Instance configuration is None. Cannot ' - 'instantiate') - logger.info('Created configured instances') - except Exception as e: - logger.error('Unexpected error creating VM instances - %s', e) - return vm_dict - - -def __apply_ansible_playbooks(ansible_configs, os_creds_dict, vm_dict, - image_dict, flavor_dict, env_file): - """ - Applies ansible playbooks to running VMs with floating IPs - :param ansible_configs: a list of Ansible configurations - :param os_creds_dict: Dictionary of OSCreds objects where the key is the - name - :param vm_dict: the dictionary of newly instantiated VMs where the name is - the key - :param image_dict: the dictionary of newly instantiated images where the - name is the key - :param flavor_dict: the dictionary of newly instantiated flavors where the - name is the key - :param env_file: the path of the environment for setting the CWD so - playbook location is relative to the deployment file - :return: t/f - true if successful - """ - logger.info("Applying Ansible Playbooks") - if ansible_configs: - # Ensure all hosts are accepting SSH session requests - for vm_inst in list(vm_dict.values()): - if not vm_inst.vm_ssh_active(block=True): - logger.warning( - "Timeout waiting for instance to respond to SSH requests") - return False - - # Set CWD so the deployment file's playbook location can leverage - # relative paths - orig_cwd = os.getcwd() - env_dir = os.path.dirname(env_file) - os.chdir(env_dir) - - # Apply playbooks - for ansible_config in ansible_configs: - if 'pre_sleep_time' in ansible_config: - try: - sleep_time = int(ansible_config['pre_sleep_time']) - logger.info('Waiting %s seconds to apply playbooks', - sleep_time) - time.sleep(sleep_time) - except: - pass - - os_creds = os_creds_dict.get(None, 'admin') - __apply_ansible_playbook(ansible_config, os_creds, vm_dict, - image_dict, flavor_dict) - - # Return to original directory - os.chdir(orig_cwd) - - return True - - -def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict, - flavor_dict): - """ - Applies an Ansible configuration setting - :param ansible_config: the configuration settings - :param os_creds: the OpenStack credentials object - :param vm_dict: the dictionary of newly instantiated VMs where the name is - the key - :param image_dict: the dictionary of newly instantiated images where the - name is the key - :param flavor_dict: the dictionary of newly instantiated flavors where the - name is the key - """ - if ansible_config: - (remote_user, floating_ips, private_key_filepath, - proxy_settings) = __get_connection_info( - ansible_config, vm_dict) - if floating_ips: - retval = ansible_utils.apply_playbook( - ansible_config['playbook_location'], floating_ips, remote_user, - private_key_filepath, - variables=__get_variables(ansible_config.get('variables'), - os_creds, vm_dict, image_dict, - flavor_dict), - proxy_setting=proxy_settings) - if retval != 0: - # Not a fatal type of event - logger.warning( - 'Unable to apply playbook found at location - %s', - ansible_config.get('playbook_location')) - - -def __get_connection_info(ansible_config, vm_dict): - """ - Returns a tuple of data required for connecting to the running VMs - (remote_user, [floating_ips], private_key_filepath, proxy_settings) - :param ansible_config: the configuration settings - :param vm_dict: the dictionary of VMs where the VM name is the key - :return: tuple where the first element is the user and the second is a list - of floating IPs and the third is the - private key file location and the fourth is an instance of the - snaps.ProxySettings class - (note: in order to work, each of the hosts need to have the same sudo_user - and private key file location values) - """ - if ansible_config.get('hosts'): - hosts = ansible_config['hosts'] - if len(hosts) > 0: - floating_ips = list() - remote_user = None - pk_file = None - proxy_settings = None - for host in hosts: - vm = vm_dict.get(host) - if vm: - fip = vm.get_floating_ip() - if fip: - remote_user = vm.get_image_user() - - if fip: - floating_ips.append(fip.ip) - else: - raise Exception( - 'Could not find floating IP for VM - ' + - vm.name) - - pk_file = vm.keypair_settings.private_filepath - proxy_settings = vm.get_os_creds().proxy_settings - else: - logger.error('Could not locate VM with name - ' + host) - - return remote_user, floating_ips, pk_file, proxy_settings - return None - - -def __get_variables(var_config, os_creds, vm_dict, image_dict, flavor_dict): - """ - Returns a dictionary of substitution variables to be used for Ansible - templates - :param var_config: the variable configuration settings - :param os_creds: the OpenStack credentials object - :param vm_dict: the dictionary of newly instantiated VMs where the name is - the key - :param image_dict: the dictionary of newly instantiated images where the - name is the key - :param flavor_dict: the dictionary of newly instantiated flavors where the - name is the key - :return: dictionary or None - """ - if var_config and vm_dict and len(vm_dict) > 0: - variables = dict() - for key, value in var_config.items(): - value = __get_variable_value(value, os_creds, vm_dict, image_dict, - flavor_dict) - if key and value: - variables[key] = value - logger.info( - "Set Jinga2 variable with key [%s] the value [%s]", - key, value) - else: - logger.warning('Key [%s] or Value [%s] must not be None', - str(key), str(value)) - return variables - return None - - -def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict, - flavor_dict): - """ - Returns the associated variable value for use by Ansible for substitution - purposes - :param var_config_values: the configuration dictionary - :param os_creds: the OpenStack credentials object - :param vm_dict: the dictionary of newly instantiated VMs where the name is - the key - :param image_dict: the dictionary of newly instantiated images where the - name is the key - :param flavor_dict: the dictionary of newly instantiated flavors where the - name is the key - :return: - """ - if var_config_values['type'] == 'string': - return __get_string_variable_value(var_config_values) - if var_config_values['type'] == 'vm-attr': - return __get_vm_attr_variable_value(var_config_values, vm_dict) - if var_config_values['type'] == 'os_creds': - return __get_os_creds_variable_value(var_config_values, os_creds) - if var_config_values['type'] == 'port': - return __get_vm_port_variable_value(var_config_values, vm_dict) - if var_config_values['type'] == 'floating_ip': - return __get_vm_fip_variable_value(var_config_values, vm_dict) - if var_config_values['type'] == 'image': - return __get_image_variable_value(var_config_values, image_dict) - if var_config_values['type'] == 'flavor': - return __get_flavor_variable_value(var_config_values, flavor_dict) - return None - - -def __get_string_variable_value(var_config_values): - """ - Returns the associated string value - :param var_config_values: the configuration dictionary - :return: the value contained in the dictionary with the key 'value' - """ - return var_config_values['value'] - - -def __get_vm_attr_variable_value(var_config_values, vm_dict): - """ - Returns the associated value contained on a VM instance - :param var_config_values: the configuration dictionary - :param vm_dict: the dictionary containing all VMs where the key is the VM's - name - :return: the value - """ - vm = vm_dict.get(var_config_values['vm_name']) - if vm: - if var_config_values['value'] == 'floating_ip': - return vm.get_floating_ip().ip - if var_config_values['value'] == 'image_user': - return vm.get_image_user() - - -def __get_os_creds_variable_value(var_config_values, os_creds): - """ - Returns the associated OS credentials value - :param var_config_values: the configuration dictionary - :param os_creds: the credentials - :return: the value - """ - logger.info("Retrieving OS Credentials") - if os_creds: - if var_config_values['value'] == 'username': - logger.info("Returning OS username") - return os_creds.username - elif var_config_values['value'] == 'password': - logger.info("Returning OS password") - return os_creds.password - elif var_config_values['value'] == 'auth_url': - logger.info("Returning OS auth_url") - return os_creds.auth_url - elif var_config_values['value'] == 'project_name': - logger.info("Returning OS project_name") - return os_creds.project_name - - logger.info("Returning none") - return None - - -def __get_vm_port_variable_value(var_config_values, vm_dict): - """ - Returns the associated OS credentials value - :param var_config_values: the configuration dictionary - :param vm_dict: the dictionary containing all VMs where the key is the VM's - name - :return: the value - """ - port_name = var_config_values.get('port_name') - vm_name = var_config_values.get('vm_name') - - if port_name and vm_name: - vm = vm_dict.get(vm_name) - if vm: - port_value_id = var_config_values.get('port_value') - if port_value_id: - if port_value_id == 'mac_address': - return vm.get_port_mac(port_name) - if port_value_id == 'ip_address': - return vm.get_port_ip(port_name) - - -def __get_vm_fip_variable_value(var_config_values, vm_dict): - """ - Returns the floating IP value if found - :param var_config_values: the configuration dictionary - :param vm_dict: the dictionary containing all VMs where the key is the VM's - name - :return: the floating IP string value or None - """ - fip_name = var_config_values.get('fip_name') - vm_name = var_config_values.get('vm_name') - - if vm_name: - vm = vm_dict.get(vm_name) - if vm: - fip = vm.get_floating_ip(fip_name) - if fip: - return fip.ip - - -def __get_image_variable_value(var_config_values, image_dict): - """ - Returns the associated image value - :param var_config_values: the configuration dictionary - :param image_dict: the dictionary containing all images where the key is - the name - :return: the value - """ - logger.info("Retrieving image values") - - if image_dict: - if var_config_values.get('image_name'): - image_creator = image_dict.get(var_config_values['image_name']) - if image_creator: - if var_config_values.get('value') and \ - var_config_values['value'] == 'id': - return image_creator.get_image().id - if var_config_values.get('value') and \ - var_config_values['value'] == 'user': - return image_creator.image_settings.image_user - - logger.info("Returning none") - return None - - -def __get_flavor_variable_value(var_config_values, flavor_dict): - """ - Returns the associated flavor value - :param var_config_values: the configuration dictionary - :param flavor_dict: the dictionary containing all flavor creators where the - key is the name - :return: the value or None - """ - logger.info("Retrieving flavor values") - - if flavor_dict: - if var_config_values.get('flavor_name'): - flavor_creator = flavor_dict.get(var_config_values['flavor_name']) - if flavor_creator: - if var_config_values.get('value') and \ - var_config_values['value'] == 'id': - return flavor_creator.get_flavor().id def main(arguments): @@ -591,108 +66,11 @@ def main(arguments): config = yaml.load(output) if config: - os_config = config.get('openstack') - - creators = list() - vm_dict = dict() - images_dict = dict() - flavors_dict = dict() - os_creds_dict = dict() clean = arguments.clean is not ARG_NOT_SET - - if os_config: - os_creds_dict = __get_creds_dict(os_config) - - try: - # Create projects - projects_dict = __create_instances( - os_creds_dict, OpenStackProject, ProjectSettings, - os_config.get('projects'), 'project', clean) - creators.append(projects_dict) - - # Create users - users_dict = __create_instances( - os_creds_dict, OpenStackUser, UserSettings, - os_config.get('users'), 'user', clean) - creators.append(users_dict) - - # Associate new users to projects - if not clean: - for project_creator in projects_dict.values(): - users = project_creator.project_settings.users - for user_name in users: - user_creator = users_dict.get(user_name) - if user_creator: - project_creator.assoc_user( - user_creator.get_user()) - - # Create flavors - flavors_dict = __create_instances( - os_creds_dict, OpenStackFlavor, FlavorSettings, - os_config.get('flavors'), 'flavor', clean, users_dict) - creators.append(flavors_dict) - - # Create images - images_dict = __create_instances( - os_creds_dict, OpenStackImage, ImageSettings, - os_config.get('images'), 'image', clean, users_dict) - creators.append(images_dict) - - # Create networks - creators.append(__create_instances( - os_creds_dict, OpenStackNetwork, NetworkSettings, - os_config.get('networks'), 'network', clean, users_dict)) - - # Create routers - creators.append(__create_instances( - os_creds_dict, OpenStackRouter, RouterSettings, - os_config.get('routers'), 'router', clean, users_dict)) - - # Create keypairs - keypairs_dict = __create_instances( - os_creds_dict, OpenStackKeypair, KeypairSettings, - os_config.get('keypairs'), 'keypair', clean, users_dict) - creators.append(keypairs_dict) - - # Create security groups - creators.append(__create_instances( - os_creds_dict, OpenStackSecurityGroup, - SecurityGroupSettings, - os_config.get('security_groups'), 'security_group', clean, - users_dict)) - - # Create instance - vm_dict = __create_vm_instances( - os_creds_dict, users_dict, os_config.get('instances'), - images_dict, keypairs_dict, - arguments.clean is not ARG_NOT_SET) - creators.append(vm_dict) - logger.info( - 'Completed creating/retrieving all configured instances') - except Exception as e: - logger.error( - 'Unexpected error deploying environment. Rolling back due' - ' to - ' + str(e)) - raise - - # Must enter either block - if arguments.clean is not ARG_NOT_SET: - # Cleanup Environment - __cleanup(creators, arguments.clean_image is not ARG_NOT_SET) - elif arguments.deploy is not ARG_NOT_SET: - logger.info('Configuring NICs where required') - for vm in vm_dict.values(): - vm.config_nics() - logger.info('Completed NIC configuration') - - # Provision VMs - ansible_config = config.get('ansible') - if ansible_config and vm_dict: - if not __apply_ansible_playbooks(ansible_config, - os_creds_dict, vm_dict, - images_dict, flavors_dict, - arguments.tmplt_file): - logger.error("Problem applying ansible playbooks") + clean_image = arguments.clean_image is not ARG_NOT_SET + deploy = arguments.deploy is not ARG_NOT_SET + launch_utils.launch_config( + config, arguments.tmplt_file, deploy, clean, clean_image) else: logger.error( 'Unable to read configuration file - ' + arguments.tmplt_file) @@ -701,17 +79,6 @@ def main(arguments): exit(0) -def __cleanup(creators, clean_image=False): - for creator_dict in reversed(creators): - for key, creator in creator_dict.items(): - if ((isinstance(creator, OpenStackImage) and clean_image) - or not isinstance(creator, OpenStackImage)): - try: - creator.clean() - except Exception as e: - logger.warning('Error cleaning component - %s', e) - - if __name__ == '__main__': # To ensure any files referenced via a relative path will begin from the # directory in which this file resides diff --git a/requirements.txt b/requirements.txt index b0b60c0..137159f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,14 @@ -python-novaclient!=7.0.0,>=6.0.0 # Apache-2.0 -python-neutronclient>=5.1.0 # Apache-2.0 +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +python-novaclient>=9.0.0 # Apache-2.0 +python-neutronclient>=6.3.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 -python-glanceclient>=2.5.0 # Apache-2.0 +python-glanceclient>=2.8.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 -python-cinderclient -ansible>=2.1.0,<2.4 +python-cinderclient>=3.1.0 # Apache-2.0 +python-magnumclient>=2.0.0 # Apache-2.0 +ansible<2.4,>=2.1.0 wrapt>=1.7.0 # BSD License scp -cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0 +cryptography!=2.0,>=1.6 # BSD/Apache-2.0 @@ -1,17 +1,29 @@ -#!/usr/bin/env python - -# Copyright (c) 2016 Cable Television Laboratories and others. +# 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 # -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. -from setuptools import setup +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools -__author__ = 'spisarski' +# 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 -setup( - setup_requires=['pbr>=1.9', 'setuptools>=17.1'], - pbr=True, -) +setuptools.setup( + setup_requires=['pbr>=2.0.0'], + pbr=True) diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/configure_host.yml b/snaps/config/__init__.py index 5d43f96..271c742 100644 --- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/configure_host.yml +++ b/snaps/config/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs") +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") # and others. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,15 +12,4 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ---- -- name: Configure NIC - hosts: all - become: yes - become_method: sudo - become_user: root - - tasks: - - name: Setup /etc/network/interfaces.d/{{nic_name}}.cfg file - action: template owner=root group=root mode=644 src=../templates/ethN.cfg dest=/etc/network/interfaces.d/{{nic_name}}.cfg - - name : Restart Network - command: service networking restart
\ No newline at end of file +__author__ = 'spisarski' diff --git a/snaps/config/cluster_template.py b/snaps/config/cluster_template.py new file mode 100644 index 0000000..0acf25a --- /dev/null +++ b/snaps/config/cluster_template.py @@ -0,0 +1,308 @@ +# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import enum +from neutronclient.common.utils import str2bool + + +class ServerType(enum.Enum): + """ + The cluter server types supported + """ + vm = 'vm' + baremetal = 'baremetal' + + +class ContainerOrchestrationEngine(enum.Enum): + """ + The types of supported COEs + """ + kubernetes = 'kubernetes' + swarm = 'swarm' + mesos = 'mesos' + + +class DockerStorageDriver(enum.Enum): + """ + Drivers for managing storage for the images in the container's writable + layer + """ + devicemapper = 'devicemapper' + overlay = 'overlay' + + +class ClusterTemplateConfig(object): + """ + Configuration settings for OpenStack cluster template creation + """ + + def __init__(self, **kwargs): + """ + Constructor + :param name: the cluster template's name (required) + :param image: name or ID of the base image in Glance used to boot the + cluster's servers. The image must have the attribute + 'os-distro' defined as appropriate for the cluster + driver (required) + :param keypair: name or ID of the keypair to gain cluster machine + access (required) + :param network_driver: The name of a network driver for providing the + networks for the containers. Note that this is + different and separate from the Neutron network + for the bay/cluster. The operation and + networking model are specific to the particular + driver (optional) + :param external_net: name or IDof the external Neutron network to + provide connectivity to the cluster (required) + :param floating_ip_enabled: Whether enable or not using the floating IP + of cloud provider. Some cloud providers + used floating IP, some used public IP, + thus Magnum provide this option for + specifying the choice of using floating IP + (default - True) + :param docker_volume_size: The size in GB for the local storage on each + server for the Docker daemon to cache the + images and host the containers. Cinder + volumes provide the storage. The default is + 25 GB. For the devicemapper storage driver, + the minimum value is 3GB. For the overlay + storage driver, the minimum value is 1GB. + (default - 3) + :param server_type: ServerType enumeration (default - vm) + :param flavor: name or ID of the nova flavor for booting the node + servers (default - m1.small) + :param master_flavor: name or ID of the nova flavor of the master node + for this cluster (optional) + :param coe: ContainerOrchestrationEngine enum instance + (default - kubernetes) + :param fixed_net: name of a Neutron network to provide connectivity + to the internal network for the cluster + (optional) + :param fixed_subnet: Fixed subnet that are using to allocate network + address for nodes in bay/cluster (optional) + :param registry_enabled: Docker images by default are pulled from the + public Docker registry, but in some cases, + users may want to use a private registry. + This option provides an alternative registry + based on the Registry V2: Magnum will create a + local registry in the bay/cluster backed by + swift to host the images (default - True) + :param insecure_registry: The URL pointing to the user's own private + insecure docker registry to deploy and run + docker containers (optional) + :param docker_storage_driver: DockerStorageDriver enum instance to + manage storage for the images and + container's writable layer + (default - devicemapper) + :param dns_nameserver: The DNS nameserver for the servers and + containers in the bay/cluster to use. + This is configured in the private Neutron + network for the bay/cluster. + (default provided by Magnum - 8.8.8.8) + :param public: denotes whether or not the cluster template is public + (default False) + :param tls_disabled: denotes whether or not TLS should be enabled + (default False) + :param http_proxy: host:port for a proxy to use when direct HTTP + access from the servers to sites on the external + internet is blocked (optional) + :param https_proxy: host:port for a proxy to use when direct HTTPS + access from the servers to sites on the external + internet is blocked (optional) + :param no_proxy: comma separated list of IPs that should not be + redirected through the proxy (optional) + :param volume_driver: The name of a volume driver for managing the + persistent storage for the containers. The + functionality supported are specific to the + driver (optional) + :param master_lb_enabled: Since multiple masters may exist in a + bay/cluster, a Neutron load balancer is + created to provide the API endpoint for the + bay/cluster and to direct requests to the + masters. In some cases, such as when the + LBaaS service is not available, this option + can be set to false to create a bay/cluster + without the load balancer. In this case, one + of the masters will serve as the API endpoint + (default - True) + :param labels: Arbitrary labels in the form of a dict. The accepted + keys and valid values are defined in the bay/cluster + drivers. They are used as a way to pass additional + parameters that are specific to a bay/cluster driver. + (optional) + """ + self.name = kwargs.get('name') + self.image = kwargs.get('image') + self.keypair = kwargs.get('keypair') + self.network_driver = kwargs.get('network_driver') + self.external_net = kwargs.get('external_net') + self.floating_ip_enabled = str2bool( + str(kwargs.get('floating_ip_enabled', True))) + self.docker_volume_size = int(kwargs.get('docker_volume_size', 3)) + self.server_type = map_server_type( + kwargs.get('server_type', ServerType.vm)) + self.flavor = kwargs.get('flavor') + self.master_flavor = kwargs.get('master_flavor') + self.coe = map_coe( + kwargs.get('coe', ContainerOrchestrationEngine.kubernetes)) + self.fixed_net = kwargs.get('fixed_net') + self.fixed_subnet = kwargs.get('fixed_subnet') + self.registry_enabled = str2bool( + str(kwargs.get('registry_enabled', True))) + self.insecure_registry = kwargs.get('insecure_registry') + self.docker_storage_driver = map_docker_storage_driver( + kwargs.get('docker_storage_driver', + DockerStorageDriver.devicemapper)) + self.dns_nameserver = kwargs.get('dns_nameserver') + self.public = str2bool(str(kwargs.get('public', False))) + self.tls_disabled = str2bool(str(kwargs.get('tls_disabled', False))) + self.http_proxy = kwargs.get('http_proxy') + self.https_proxy = kwargs.get('https_proxy') + self.no_proxy = kwargs.get('no_proxy') + self.volume_driver = kwargs.get('volume_driver') + self.master_lb_enabled = str2bool( + str(kwargs.get('master_lb_enabled', True))) + self.labels = kwargs.get('labels') + + if (not self.name or not self.image or not self.keypair + or not self.external_net): + raise ClusterTemplateConfigError( + 'The attributes name, image, keypair, and ' + 'external_net are required for ClusterTemplateConfig') + + def magnum_dict(self): + """ + Returns a dictionary object representing this object. + This is meant to be sent into as kwargs into the Magnum client + + :return: the dictionary object + """ + out = dict() + + if self.name: + out['name'] = self.name + if self.image: + out['image_id'] = self.image + if self.keypair: + out['keypair_id'] = self.keypair + if self.network_driver: + out['network_driver'] = self.network_driver + if self.external_net: + out['external_network_id'] = self.external_net + if self.floating_ip_enabled: + out['floating_ip_enabled'] = self.floating_ip_enabled + if self.docker_volume_size: + out['docker_volume_size'] = self.docker_volume_size + if self.server_type: + out['server_type'] = self.server_type.value + if self.flavor: + out['flavor_id'] = self.flavor + if self.master_flavor: + out['master_flavor_id'] = self.master_flavor + if self.coe: + out['coe'] = self.coe.value + if self.fixed_net: + out['fixed_network'] = self.fixed_net + if self.fixed_subnet: + out['fixed_subnet'] = self.fixed_subnet + if self.registry_enabled: + out['registry_enabled'] = self.registry_enabled + if self.insecure_registry: + out['insecure_registry'] = self.insecure_registry + if self.docker_storage_driver: + out['docker_storage_driver'] = self.docker_storage_driver.value + if self.dns_nameserver: + out['dns_nameserver'] = self.dns_nameserver + if self.public: + out['public'] = self.public + if self.tls_disabled: + out['tls_disabled'] = self.tls_disabled + if self.http_proxy: + out['http_proxy'] = self.http_proxy + if self.https_proxy: + out['https_proxy'] = self.https_proxy + if self.no_proxy: + out['no_proxy'] = self.no_proxy + if self.volume_driver: + out['volume_driver'] = self.volume_driver + if self.master_lb_enabled: + out['master_lb_enabled'] = self.master_lb_enabled + if self.labels: + out['labels'] = self.labels + return out + + +class ClusterTemplateConfigError(Exception): + """ + Exception to be thrown when a cluster template configuration is incorrect + """ + + +def map_server_type(server_type): + """ + Takes a the server_type value maps it to the ServerType enum. When None + return None + :param server_type: the server_type value to map + :return: the ServerType enum object + :raise: ClusterTemplateConfigError if value is invalid + """ + if not server_type: + return None + if isinstance(server_type, ServerType): + return server_type + elif isinstance(server_type, str): + for this_type in ServerType: + if this_type.value == server_type: + return this_type + raise ClusterTemplateConfigError( + 'Invalid server type - ' + server_type) + + +def map_coe(coe): + """ + Takes a the coe value maps it to the ContainerOrchestrationEngine enum. + When None return None + :param coe: the COE value to map + :return: the ContainerOrchestrationEngine enum object + :raise: ClusterTemplateConfigError if value is invalid + """ + if not coe: + return None + if isinstance(coe, ContainerOrchestrationEngine): + return coe + elif isinstance(coe, str): + for this_type in ContainerOrchestrationEngine: + if this_type.value == coe: + return this_type + raise ClusterTemplateConfigError('Invalid COE - ' + coe) + + +def map_docker_storage_driver(driver): + """ + Takes a the coe value maps it to the ContainerOrchestrationEngine enum. + When None return None + :param driver: the docker storage driver value to map + :return: the DockerStorageDriver enum object + :raise: ClusterTemplateConfigError if value is invalid + """ + if not driver: + return None + if isinstance(driver, DockerStorageDriver): + return driver + elif isinstance(driver, str): + for this_type in DockerStorageDriver: + if this_type.value == driver: + return this_type + raise ClusterTemplateConfigError( + 'Invalid DockerStorageDriver - ' + driver) diff --git a/snaps/config/flavor.py b/snaps/config/flavor.py new file mode 100644 index 0000000..b29fb7c --- /dev/null +++ b/snaps/config/flavor.py @@ -0,0 +1,107 @@ +# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 FlavorConfig(object): + """ + Configuration settings for OpenStack flavor creation + """ + + def __init__(self, **kwargs): + """ + Constructor + :param name: the flavor's name (required) + :param flavor_id: the string ID (default 'auto') + :param ram: the required RAM in MB (required) + :param disk: the size of the root disk in GB (required) + :param vcpus: the number of virtual CPUs (required) + :param ephemeral: the size of the ephemeral disk in GB (default 0) + :param swap: the size of the dedicated swap disk in GB (default 0) + :param rxtx_factor: the receive/transmit factor to be set on ports if + backend supports QoS extension (default 1.0) + :param is_public: denotes whether or not the flavor is public + (default True) + :param metadata: freeform dict() for special metadata + """ + self.name = kwargs.get('name') + + if kwargs.get('flavor_id'): + self.flavor_id = kwargs['flavor_id'] + else: + self.flavor_id = 'auto' + + self.ram = kwargs.get('ram') + self.disk = kwargs.get('disk') + self.vcpus = kwargs.get('vcpus') + + if kwargs.get('ephemeral'): + self.ephemeral = kwargs['ephemeral'] + else: + self.ephemeral = 0 + + if kwargs.get('swap'): + self.swap = kwargs['swap'] + else: + self.swap = 0 + + if kwargs.get('rxtx_factor'): + self.rxtx_factor = kwargs['rxtx_factor'] + else: + self.rxtx_factor = 1.0 + + if kwargs.get('is_public') is not None: + self.is_public = kwargs['is_public'] + else: + self.is_public = True + + if kwargs.get('metadata'): + self.metadata = kwargs['metadata'] + else: + self.metadata = None + + if not self.name or not self.ram or not self.disk or not self.vcpus: + raise FlavorConfigError( + 'The attributes name, ram, disk, and vcpus are required for' + 'FlavorConfig') + + if not isinstance(self.ram, int): + raise FlavorConfigError('The ram attribute must be a integer') + + if not isinstance(self.disk, int): + raise FlavorConfigError('The ram attribute must be a integer') + + if not isinstance(self.vcpus, int): + raise FlavorConfigError('The vcpus attribute must be a integer') + + if self.ephemeral and not isinstance(self.ephemeral, int): + raise FlavorConfigError( + 'The ephemeral attribute must be an integer') + + if self.swap and not isinstance(self.swap, int): + raise FlavorConfigError('The swap attribute must be an integer') + + if self.rxtx_factor and not isinstance(self.rxtx_factor, (int, float)): + raise FlavorConfigError( + 'The is_public attribute must be an integer or float') + + if self.is_public and not isinstance(self.is_public, bool): + raise FlavorConfigError( + 'The is_public attribute must be a boolean') + + +class FlavorConfigError(Exception): + """ + Exception to be thrown when a flavor configuration is incorrect + """ diff --git a/snaps/config/image.py b/snaps/config/image.py new file mode 100644 index 0000000..fe1c913 --- /dev/null +++ b/snaps/config/image.py @@ -0,0 +1,110 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 ImageConfig(object): + def __init__(self, **kwargs): + """ + Constructor + :param name: the image's name (required) + :param image_user: the image's default sudo user (required) + :param format or img_format: the image format type (required) + :param url or download_url: the image download location (requires url + or img_file) + :param image_file: the image file location (requires url or img_file) + :param extra_properties: dict() object containing extra parameters to + pass when loading the image; + can be ids of kernel and initramfs images for + a 3-part image + :param nic_config_pb_loc: the file location to the Ansible Playbook + that can configure multiple NICs + :param kernel_image_settings: the settings for a kernel image + :param ramdisk_image_settings: the settings for a ramdisk image + :param exists: When True, an image with the given name must exist + :param public: When True, an image will be created with public + visibility + """ + + self.name = kwargs.get('name') + self.image_user = kwargs.get('image_user') + self.format = kwargs.get('format') + if not self.format: + self.format = kwargs.get('img_format') + + self.url = kwargs.get('url') + if not self.url: + self.url = kwargs.get('download_url') + if self.url == 'None': + self.url = None + + self.image_file = kwargs.get('image_file') + if self.image_file == 'None': + self.image_file = None + + self.extra_properties = kwargs.get('extra_properties') + self.nic_config_pb_loc = kwargs.get('nic_config_pb_loc') + + kernel_image_settings = kwargs.get('kernel_image_settings') + if kernel_image_settings: + if isinstance(kernel_image_settings, dict): + self.kernel_image_settings = ImageConfig( + **kernel_image_settings) + else: + self.kernel_image_settings = kernel_image_settings + else: + self.kernel_image_settings = None + + ramdisk_image_settings = kwargs.get('ramdisk_image_settings') + if ramdisk_image_settings: + if isinstance(ramdisk_image_settings, dict): + self.ramdisk_image_settings = ImageConfig( + **ramdisk_image_settings) + else: + self.ramdisk_image_settings = ramdisk_image_settings + else: + self.ramdisk_image_settings = None + + if 'exists' in kwargs and kwargs['exists'] is True: + self.exists = True + else: + self.exists = False + + if 'public' in kwargs and kwargs['public'] is True: + self.public = True + else: + self.public = False + + if not self.name: + raise ImageConfigError("The attribute name is required") + + if not (self.url or self.image_file) and not self.exists: + raise ImageConfigError( + 'URL or image file must be set or image must already exist') + + if not self.image_user: + raise ImageConfigError('Image user is required') + + if not self.format and not self.exists: + raise ImageConfigError( + 'Format is required when the image should not already exist') + + +class ImageConfigError(Exception): + """ + Exception to be thrown when an image settings are incorrect + """ + + def __init__(self, message): + Exception.__init__(self, message) diff --git a/snaps/config/keypair.py b/snaps/config/keypair.py new file mode 100644 index 0000000..2304c6e --- /dev/null +++ b/snaps/config/keypair.py @@ -0,0 +1,61 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from neutronclient.common.utils import str2bool + + +class KeypairConfig(object): + """ + Class representing a keypair configuration + """ + + def __init__(self, **kwargs): + """ + Constructor - all parameters are optional + :param name: The keypair name. + :param public_filepath: The path to/from the filesystem where the + public key file is or will be stored + :param private_filepath: The path where the generated private key file + will be stored + :param key_size: The number of bytes for the key size when it needs to + be generated (Must be >=512 default 1024) + :param delete_on_clean: when True, the key files will be deleted when + OpenStackKeypair#clean() is called + :return: + """ + + self.name = kwargs.get('name') + self.public_filepath = kwargs.get('public_filepath') + self.private_filepath = kwargs.get('private_filepath') + self.key_size = int(kwargs.get('key_size', 1024)) + + if kwargs.get('delete_on_clean') is not None: + if isinstance(kwargs.get('delete_on_clean'), bool): + self.delete_on_clean = kwargs.get('delete_on_clean') + else: + self.delete_on_clean = str2bool(kwargs.get('delete_on_clean')) + else: + self.delete_on_clean = None + + if not self.name: + raise KeypairConfigError('Name is a required attribute') + + if self.key_size < 512: + raise KeypairConfigError('key_size must be >=512') + + +class KeypairConfigError(Exception): + """ + Exception to be thrown when keypair settings are incorrect + """ diff --git a/snaps/config/network.py b/snaps/config/network.py new file mode 100644 index 0000000..39a4254 --- /dev/null +++ b/snaps/config/network.py @@ -0,0 +1,518 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import enum +from neutronclient.common.utils import str2bool + +from snaps.openstack.utils import keystone_utils, neutron_utils + + +class NetworkConfig(object): + """ + Class representing a network configuration + """ + + def __init__(self, **kwargs): + """ + Constructor - all parameters are optional + :param name: The network name. + :param admin_state_up: The administrative status of the network. + True = up / False = down (default True) + :param shared: Boolean value indicating whether this network is shared + across all projects/tenants. By default, only + administrative users can change this value. + :param project_name: Admin-only. The name of the project that will own + the network. This project can be different from + the project that makes the create network request. + However, only administrative users can specify a + project ID other than their own. You cannot change + this value through authorization policies. + :param external: when true, will setup an external network + (default False). + :param network_type: the type of network (i.e. vlan|flat). + :param physical_network: the name of the physical network + (required when network_type is 'flat') + :param segmentation_id: the id of the segmentation + (this is required when network_type is 'vlan') + :param subnets or subnet_settings: List of SubnetConfig objects. + :return: + """ + + self.project_id = None + + self.name = kwargs.get('name') + if kwargs.get('admin_state_up') is not None: + self.admin_state_up = str2bool(str(kwargs['admin_state_up'])) + else: + self.admin_state_up = True + + if kwargs.get('shared') is not None: + self.shared = str2bool(str(kwargs['shared'])) + else: + self.shared = None + + self.project_name = kwargs.get('project_name') + + if kwargs.get('external') is not None: + self.external = str2bool(str(kwargs.get('external'))) + else: + self.external = False + + self.network_type = kwargs.get('network_type') + self.physical_network = kwargs.get('physical_network') + self.segmentation_id = kwargs.get('segmentation_id') + + self.subnet_settings = list() + subnet_settings = kwargs.get('subnets') + if not subnet_settings: + subnet_settings = kwargs.get('subnet_settings', list()) + if subnet_settings: + for subnet_config in subnet_settings: + if isinstance(subnet_config, SubnetConfig): + self.subnet_settings.append(subnet_config) + else: + self.subnet_settings.append( + SubnetConfig(**subnet_config['subnet'])) + + if not self.name or len(self.name) < 1: + raise NetworkConfigError('Name required for networks') + + def get_project_id(self, os_creds): + """ + Returns the project ID for a given project_name or None + :param os_creds: the credentials required for keystone client retrieval + :return: the ID or None + """ + if self.project_id: + return self.project_id + else: + if self.project_name: + keystone = keystone_utils.keystone_client(os_creds) + project = keystone_utils.get_project( + keystone=keystone, project_name=self.project_name) + if project: + return project.id + + return None + + def dict_for_neutron(self, os_creds): + """ + Returns a dictionary object representing this object. + This is meant to be converted into JSON designed for use by the Neutron + API + TODO - expand automated testing to exercise all parameters + + :param os_creds: the OpenStack credentials + :return: the dictionary object + """ + out = dict() + + if self.name: + out['name'] = self.name + if self.admin_state_up is not None: + out['admin_state_up'] = self.admin_state_up + if self.shared: + out['shared'] = self.shared + if self.project_name: + project_id = self.get_project_id(os_creds) + if project_id: + out['tenant_id'] = project_id + else: + raise NetworkConfigError( + 'Could not find project ID for project named - ' + + self.project_name) + if self.network_type: + out['provider:network_type'] = self.network_type + if self.physical_network: + out['provider:physical_network'] = self.physical_network + if self.segmentation_id: + out['provider:segmentation_id'] = self.segmentation_id + if self.external: + out['router:external'] = self.external + return {'network': out} + + +class NetworkConfigError(Exception): + """ + Exception to be thrown when networks settings attributes are incorrect + """ + + +class IPv6Mode(enum.Enum): + """ + A rule's direction + """ + slaac = 'slaac' + stateful = 'dhcpv6-stateful' + stateless = 'dhcpv6-stateless' + + +class SubnetConfig(object): + """ + Class representing a subnet configuration + """ + + def __init__(self, **kwargs): + """ + Constructor - all parameters are optional except cidr (subnet mask) + :param name: The subnet name (required) + :param cidr: The CIDR (required) + :param ip_version: The IP version, which is 4 or 6 (required) + :param project_name: The name of the project who owns the network. + Only administrative users can specify a project ID + other than their own. You cannot change this value + through authorization policies (optional) + :param start: The start address for the allocation pools (optional) + :param end: The end address for the allocation pools (optional) + :param gateway_ip: The gateway IP address (optional) + :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is + disabled (optional) + :param dns_nameservers: A list of DNS name servers for the subnet. + Specify each name server as an IP address + and separate multiple entries with a space. + For example [8.8.8.7 8.8.8.8] + (default '8.8.8.8') + :param host_routes: A list of host route dictionaries for the subnet. + For example: + "host_routes":[ + { + "destination":"0.0.0.0/0", + "nexthop":"123.456.78.9" + }, + { + "destination":"192.168.0.0/24", + "nexthop":"192.168.0.1" + } + ] + :param destination: The destination for static route (optional) + :param nexthop: The next hop for the destination (optional) + :param ipv6_ra_mode: an instance of the IPv6Mode enum + (optional when enable_dhcp is True) + :param ipv6_address_mode: an instance of the IPv6Mode enum + (optional when enable_dhcp is True) + :raise: SubnetConfigError when config does not have or cidr values + are None + """ + self.cidr = kwargs.get('cidr') + if kwargs.get('ip_version'): + self.ip_version = kwargs['ip_version'] + else: + self.ip_version = 4 + + # Optional attributes that can be set after instantiation + self.name = kwargs.get('name') + self.project_name = kwargs.get('project_name') + self.start = kwargs.get('start') + self.end = kwargs.get('end') + self.gateway_ip = kwargs.get('gateway_ip') + self.enable_dhcp = kwargs.get('enable_dhcp') + + if 'dns_nameservers' in kwargs: + self.dns_nameservers = kwargs.get('dns_nameservers') + else: + if self.ip_version == 4: + self.dns_nameservers = ['8.8.8.8'] + else: + self.dns_nameservers = list() + + self.host_routes = kwargs.get('host_routes') + self.destination = kwargs.get('destination') + self.nexthop = kwargs.get('nexthop') + self.ipv6_ra_mode = map_mode(kwargs.get('ipv6_ra_mode')) + self.ipv6_address_mode = map_mode(kwargs.get('ipv6_address_mode')) + + if not self.name or not self.cidr: + raise SubnetConfigError('Name and cidr required for subnets') + + def dict_for_neutron(self, os_creds, network=None): + """ + Returns a dictionary object representing this object. + This is meant to be converted into JSON designed for use by the Neutron + API + :param os_creds: the OpenStack credentials + :param network: The network object on which the subnet will be created + (optional) + :return: the dictionary object + """ + out = { + 'cidr': self.cidr, + 'ip_version': self.ip_version, + } + + if network: + out['network_id'] = network.id + if self.name: + out['name'] = self.name + if self.project_name: + keystone = keystone_utils.keystone_client(os_creds) + project = keystone_utils.get_project( + keystone=keystone, project_name=self.project_name) + project_id = None + if project: + project_id = project.id + if project_id: + out['tenant_id'] = project_id + else: + raise SubnetConfigError( + 'Could not find project ID for project named - ' + + self.project_name) + if self.start and self.end: + out['allocation_pools'] = [{'start': self.start, 'end': self.end}] + if self.gateway_ip: + out['gateway_ip'] = self.gateway_ip + if self.enable_dhcp is not None: + out['enable_dhcp'] = self.enable_dhcp + if self.dns_nameservers and len(self.dns_nameservers) > 0: + out['dns_nameservers'] = self.dns_nameservers + if self.host_routes and len(self.host_routes) > 0: + out['host_routes'] = self.host_routes + if self.destination: + out['destination'] = self.destination + if self.nexthop: + out['nexthop'] = self.nexthop + if self.ipv6_ra_mode: + out['ipv6_ra_mode'] = self.ipv6_ra_mode.value + if self.ipv6_address_mode: + out['ipv6_address_mode'] = self.ipv6_address_mode.value + return out + + +def map_mode(mode): + """ + Takes a the direction value maps it to the Direction enum. When None return + None + :param mode: the mode value + :return: the IPv6Mode enum object + :raise: SubnetConfigError if value is invalid + """ + if not mode: + return None + if isinstance(mode, IPv6Mode): + return mode + elif isinstance(mode, str): + mode_str = str(mode) + if mode_str == 'slaac': + return IPv6Mode.slaac + elif mode_str == 'dhcpv6-stateful': + return IPv6Mode.stateful + elif mode_str == 'stateful': + return IPv6Mode.stateful + elif mode_str == 'dhcpv6-stateless': + return IPv6Mode.stateless + elif mode_str == 'stateless': + return IPv6Mode.stateless + else: + raise SubnetConfigError('Invalid mode - ' + mode_str) + else: + return map_mode(mode.value) + + +class SubnetConfigError(Exception): + """ + Exception to be thrown when subnet settings attributes are incorrect + """ + + +class PortConfig(object): + """ + Class representing a port configuration + """ + + def __init__(self, **kwargs): + """ + Constructor + :param name: A symbolic name for the port (optional). + :param network_name: The name of the network on which to create the + port (required). + :param admin_state_up: A boolean value denoting the administrative + status of the port (default = True) + :param project_name: The name of the project who owns the network. + Only administrative users can specify a project ID + other than their own. You cannot change this value + through authorization policies (optional) + :param mac_address: The MAC address. If you specify an address that is + not valid, a Bad Request (400) status code is + returned. If you do not specify a MAC address, + OpenStack Networking tries to allocate one. If a + failure occurs, a Service Unavailable (503) status + code is returned (optional) + :param ip_addrs: A list of dict objects where each contains two keys + 'subnet_name' and 'ip' values which will get mapped to + self.fixed_ips. These values will be directly + translated into the fixed_ips dict (optional) + :param security_groups: One or more security group IDs. + :param port_security_enabled: When True, security groups will be + applied to the port else not + (default - True) + :param allowed_address_pairs: A dictionary containing a set of zero or + more allowed address pairs. An address + pair contains an IP address and MAC + address (optional) + :param opt_value: The extra DHCP option value (optional) + :param opt_name: The extra DHCP option name (optional) + :param device_owner: The ID of the entity that uses this port. + For example, a DHCP agent (optional) + :param device_id: The ID of the device that uses this port. + For example, a virtual server (optional) + :param extra_dhcp_opts: k/v of options to use with your DHCP (optional) + :return: + """ + if 'port' in kwargs: + kwargs = kwargs['port'] + + self.name = kwargs.get('name') + self.network_name = kwargs.get('network_name') + + if kwargs.get('admin_state_up') is not None: + self.admin_state_up = str2bool(str(kwargs['admin_state_up'])) + else: + self.admin_state_up = True + + self.project_name = kwargs.get('project_name') + self.mac_address = kwargs.get('mac_address') + self.ip_addrs = kwargs.get('ip_addrs') + self.security_groups = kwargs.get('security_groups') + + if kwargs.get('port_security_enabled') is not None: + self.port_security_enabled = str2bool( + str(kwargs['port_security_enabled'])) + else: + self.port_security_enabled = None + + self.allowed_address_pairs = kwargs.get('allowed_address_pairs') + self.opt_value = kwargs.get('opt_value') + self.opt_name = kwargs.get('opt_name') + self.device_owner = kwargs.get('device_owner') + self.device_id = kwargs.get('device_id') + self.extra_dhcp_opts = kwargs.get('extra_dhcp_opts') + + if not self.network_name: + raise PortConfigError( + 'The attribute network_name is required') + + def __get_fixed_ips(self, neutron): + """ + Sets the self.fixed_ips value + :param neutron: the Neutron client + :return: None + """ + + fixed_ips = list() + if self.ip_addrs: + + for ip_addr_dict in self.ip_addrs: + subnet = neutron_utils.get_subnet( + neutron, subnet_name=ip_addr_dict['subnet_name']) + if subnet and 'ip' in ip_addr_dict: + fixed_ips.append({'ip_address': ip_addr_dict['ip'], + 'subnet_id': subnet.id}) + else: + raise PortConfigError( + 'Invalid port configuration, subnet does not exist ' + 'with name - ' + ip_addr_dict['subnet_name']) + + return fixed_ips + + def dict_for_neutron(self, neutron, os_creds): + """ + Returns a dictionary object representing this object. + This is meant to be converted into JSON designed for use by the Neutron + API + + TODO - expand automated testing to exercise all parameters + :param neutron: the Neutron client + :param os_creds: the OpenStack credentials + :return: the dictionary object + """ + + out = dict() + + project_id = None + if self.project_name: + keystone = keystone_utils.keystone_client(os_creds) + project = keystone_utils.get_project( + keystone=keystone, project_name=self.project_name) + if project: + project_id = project.id + + network = neutron_utils.get_network( + neutron, network_name=self.network_name, project_id=project_id) + if not network: + raise PortConfigError( + 'Cannot locate network with name - ' + self.network_name) + + out['network_id'] = network.id + + if self.admin_state_up is not None: + out['admin_state_up'] = self.admin_state_up + if self.name: + out['name'] = self.name + if self.project_name: + if project_id: + out['tenant_id'] = project_id + else: + raise PortConfigError( + 'Could not find project ID for project named - ' + + self.project_name) + if self.mac_address: + out['mac_address'] = self.mac_address + + fixed_ips = self.__get_fixed_ips(neutron) + if fixed_ips and len(fixed_ips) > 0: + out['fixed_ips'] = fixed_ips + + if self.security_groups: + sec_grp_ids = list() + for sec_grp_name in self.security_groups: + sec_grp = neutron_utils.get_security_group( + neutron, sec_grp_name=sec_grp_name) + if sec_grp: + sec_grp_ids.append(sec_grp.id) + out['security_groups'] = sec_grp_ids + if self.port_security_enabled is not None: + out['port_security_enabled'] = self.port_security_enabled + if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0: + out['allowed_address_pairs'] = self.allowed_address_pairs + if self.opt_value: + out['opt_value'] = self.opt_value + if self.opt_name: + out['opt_name'] = self.opt_name + if self.device_owner: + out['device_owner'] = self.device_owner + if self.device_id: + out['device_id'] = self.device_id + if self.extra_dhcp_opts: + out['extra_dhcp_opts'] = self.extra_dhcp_opts + return {'port': out} + + def __eq__(self, other): + return (self.name == other.name and + self.network_name == other.network_name and + self.admin_state_up == other.admin_state_up and + self.project_name == other.project_name and + self.mac_address == other.mac_address and + self.ip_addrs == other.ip_addrs and + # self.fixed_ips == other.fixed_ips and + self.security_groups == other.security_groups and + self.allowed_address_pairs == other.allowed_address_pairs and + self.opt_value == other.opt_value and + self.opt_name == other.opt_name and + self.device_owner == other.device_owner and + self.device_id == other.device_id) + + +class PortConfigError(Exception): + """ + Exception to be thrown when port settings attributes are incorrect + """ diff --git a/snaps/config/project.py b/snaps/config/project.py new file mode 100644 index 0000000..6790609 --- /dev/null +++ b/snaps/config/project.py @@ -0,0 +1,57 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 ProjectConfig(object): + """ + Class to hold the configuration settings required for creating OpenStack + project objects + """ + + def __init__(self, **kwargs): + + """ + Constructor + :param name: the project's name (required) + :param domain or domain_name: the project's domain name + (default = 'Default'). + Field is used for v3 clients + :param description: the description (optional) + :param users: list of users to associate project to (optional) + :param enabled: denotes whether or not the project is enabled + (default True) + """ + + self.name = kwargs.get('name') + self.domain_name = kwargs.get( + 'domain', kwargs.get('domain', 'Default')) + + self.description = kwargs.get('description') + if kwargs.get('enabled') is not None: + self.enabled = kwargs['enabled'] + else: + self.enabled = True + + self.users = kwargs.get('users', list()) + + if not self.name: + raise ProjectConfigError( + "The attribute name is required for ProjectConfig") + + +class ProjectConfigError(Exception): + """ + Exception to be thrown when project settings attributes are incorrect + """ diff --git a/snaps/config/qos.py b/snaps/config/qos.py new file mode 100644 index 0000000..e507d2d --- /dev/null +++ b/snaps/config/qos.py @@ -0,0 +1,92 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import enum + + +class Consumer(enum.Enum): + """ + QoS Specification consumer types + """ + front_end = 'front-end' + back_end = 'back-end' + both = 'both' + + +class QoSConfig(object): + def __init__(self, **kwargs): + """ + Constructor + :param name: the qos's name (required) + :param consumer: the qos's consumer type of the enum type Consumer + (required) + :param specs: dict of key/values + """ + + self.name = kwargs.get('name') + + if kwargs.get('consumer'): + self.consumer = map_consumer(kwargs['consumer']) + else: + self.consumer = None + + self.specs = kwargs.get('specs') + if not self.specs: + self.specs = dict() + + if not self.name or not self.consumer: + raise QoSConfigError( + "The attributes name and consumer are required") + + +def map_consumer(consumer): + """ + Takes a the protocol value maps it to the Consumer enum. When None return + None + :param consumer: the value to map to the Enum + :return: the Protocol enum object + :raise: Exception if value is invalid + """ + if not consumer: + return None + elif isinstance(consumer, Consumer): + return consumer + elif isinstance(consumer, str): + proto_str = str(consumer) + if proto_str == 'front-end': + return Consumer.front_end + elif proto_str == 'back-end': + return Consumer.back_end + elif proto_str == 'both': + return Consumer.both + else: + raise QoSConfigError('Invalid Consumer - ' + proto_str) + else: + if consumer.value == 'front-end': + return Consumer.front_end + elif consumer.value == 'back-end': + return Consumer.back_end + elif consumer.value == 'both': + return Consumer.both + else: + raise QoSConfigError('Invalid Consumer - ' + consumer.value) + + +class QoSConfigError(Exception): + """ + Exception to be thrown when an qos settings are incorrect + """ + + def __init__(self, message): + Exception.__init__(self, message) diff --git a/snaps/config/router.py b/snaps/config/router.py new file mode 100644 index 0000000..72164f2 --- /dev/null +++ b/snaps/config/router.py @@ -0,0 +1,113 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from snaps.config.network import PortConfig +from snaps.openstack.utils import neutron_utils, keystone_utils + + +class RouterConfig(object): + """ + Class representing a router configuration + """ + + def __init__(self, **kwargs): + """ + Constructor - all parameters are optional + :param name: The router name. + :param project_name: The name of the project who owns the network. Only + administrative users can specify a project ID + other than their own. You cannot change this value + through authorization policies. + :param external_gateway: Name of the external network to which to route + :param admin_state_up: The administrative status of the router. + True = up / False = down (default True) + :param internal_subnets: List of subnet names to which to connect this + router for Floating IP purposes + :param port_settings: List of PortConfig objects + :return: + """ + self.name = kwargs.get('name') + self.project_name = kwargs.get('project_name') + self.external_gateway = kwargs.get('external_gateway') + + self.admin_state_up = kwargs.get('admin_state_up', True) + self.enable_snat = kwargs.get('enable_snat') + if kwargs.get('internal_subnets'): + self.internal_subnets = kwargs['internal_subnets'] + else: + self.internal_subnets = list() + + self.port_settings = list() + if kwargs.get('interfaces', kwargs.get('port_settings')): + interfaces = kwargs.get('interfaces', kwargs.get('port_settings')) + for interface in interfaces: + if isinstance(interface, PortConfig): + self.port_settings.append(interface) + else: + self.port_settings.append( + PortConfig(**interface['port'])) + + if not self.name: + raise RouterConfigError('Name is required') + + def dict_for_neutron(self, neutron, os_creds): + """ + Returns a dictionary object representing this object. + This is meant to be converted into JSON designed for use by the Neutron + API + + TODO - expand automated testing to exercise all parameters + :param neutron: The neutron client to retrieve external network + information if necessary + :param os_creds: The OpenStack credentials + :return: the dictionary object + """ + out = dict() + ext_gw = dict() + + if self.name: + out['name'] = self.name + if self.project_name: + keystone = keystone_utils.keystone_client(os_creds) + project = keystone_utils.get_project( + keystone=keystone, project_name=self.project_name) + project_id = None + if project: + project_id = project.id + if project_id: + out['tenant_id'] = project_id + else: + raise RouterConfigError( + 'Could not find project ID for project named - ' + + self.project_name) + if self.admin_state_up is not None: + out['admin_state_up'] = self.admin_state_up + if self.external_gateway: + ext_net = neutron_utils.get_network( + neutron, network_name=self.external_gateway) + if ext_net: + ext_gw['network_id'] = ext_net.id + out['external_gateway_info'] = ext_gw + else: + raise RouterConfigError( + 'Could not find the external network named - ' + + self.external_gateway) + + return {'router': out} + + +class RouterConfigError(Exception): + """ + Exception to be thrown when router settings attributes are incorrect + """ diff --git a/snaps/config/security_group.py b/snaps/config/security_group.py new file mode 100644 index 0000000..32a1e95 --- /dev/null +++ b/snaps/config/security_group.py @@ -0,0 +1,388 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import enum + +from snaps.openstack.utils import keystone_utils, neutron_utils + + +class SecurityGroupConfig(object): + """ + Class representing a keypair configuration + """ + + def __init__(self, **kwargs): + """ + Constructor + :param name: The security group's name (required) + :param description: The security group's description (optional) + :param project_name: The name of the project under which the security + group will be created + :param rule_settings: a list of SecurityGroupRuleConfig objects + :return: + """ + self.name = kwargs.get('name') + self.description = kwargs.get('description') + self.project_name = kwargs.get('project_name') + self.rule_settings = list() + + rule_settings = kwargs.get('rules') + if not rule_settings: + rule_settings = kwargs.get('rule_settings') + + if rule_settings: + for rule_setting in rule_settings: + if isinstance(rule_setting, SecurityGroupRuleConfig): + self.rule_settings.append(rule_setting) + else: + rule_setting['sec_grp_name'] = self.name + self.rule_settings.append(SecurityGroupRuleConfig( + **rule_setting)) + + if not self.name: + raise SecurityGroupConfigError('The attribute name is required') + + for rule_setting in self.rule_settings: + if rule_setting.sec_grp_name is not self.name: + raise SecurityGroupConfigError( + 'Rule settings must correspond with the name of this ' + 'security group') + + def dict_for_neutron(self, keystone): + """ + Returns a dictionary object representing this object. + This is meant to be converted into JSON designed for use by the Neutron + API + + TODO - expand automated testing to exercise all parameters + :param keystone: the Keystone client + :return: the dictionary object + """ + out = dict() + + if self.name: + out['name'] = self.name + if self.description: + out['description'] = self.description + if self.project_name: + project = keystone_utils.get_project( + keystone=keystone, project_name=self.project_name) + project_id = None + if project: + project_id = project.id + if project_id: + out['tenant_id'] = project_id + else: + raise SecurityGroupConfigError( + 'Could not find project ID for project named - ' + + self.project_name) + + return {'security_group': out} + + +class Direction(enum.Enum): + """ + A rule's direction + """ + ingress = 'ingress' + egress = 'egress' + + +class Protocol(enum.Enum): + """ + A rule's protocol + """ + ah = 51 + dccp = 33 + egp = 8 + esp = 50 + gre = 47 + icmp = 1 + icmpv6 = 58 + igmp = 2 + ipv6_encap = 41 + ipv6_frag = 44 + ipv6_icmp = 58 + ipv6_nonxt = 59 + ipv6_opts = 60 + ipv6_route = 43 + ospf = 89 + pgm = 113 + rsvp = 46 + sctp = 132 + tcp = 6 + udp = 17 + udplite = 136 + vrrp = 112 + any = 'any' + null = 'null' + + +class Ethertype(enum.Enum): + """ + A rule's ethertype + """ + IPv4 = 4 + IPv6 = 6 + + +class SecurityGroupConfigError(Exception): + """ + Exception to be thrown when security group settings attributes are + invalid + """ + + +class SecurityGroupRuleConfig(object): + """ + Class representing a keypair configuration + """ + + def __init__(self, **kwargs): + """ + Constructor - all parameters are optional + :param sec_grp_name: The security group's name on which to add the + rule. (required) + :param description: The rule's description + :param direction: An enumeration of type + create_security_group.RULE_DIRECTION (required) + :param remote_group_id: The group ID to associate with this rule + (this should be changed to group name once + snaps support Groups) (optional) + :param protocol: An enumeration of type + create_security_group.RULE_PROTOCOL or a string value + that will be mapped accordingly (optional) + :param ethertype: An enumeration of type + create_security_group.RULE_ETHERTYPE (optional) + :param port_range_min: The minimum port number in the range that is + matched by the security group rule. When the + protocol is TCP or UDP, this value must be <= + port_range_max. + :param port_range_max: The maximum port number in the range that is + matched by the security group rule. When the + protocol is TCP or UDP, this value must be <= + port_range_max. + :param remote_ip_prefix: The remote IP prefix to associate with this + metering rule packet (optional) + + TODO - Need to support the tenant... + """ + + self.description = kwargs.get('description') + self.sec_grp_name = kwargs.get('sec_grp_name') + self.remote_group_id = kwargs.get('remote_group_id') + self.direction = None + if kwargs.get('direction'): + self.direction = map_direction(kwargs['direction']) + + self.protocol = None + if kwargs.get('protocol'): + self.protocol = map_protocol(kwargs['protocol']) + else: + self.protocol = Protocol.null + + self.ethertype = None + if kwargs.get('ethertype'): + self.ethertype = map_ethertype(kwargs['ethertype']) + + self.port_range_min = kwargs.get('port_range_min') + self.port_range_max = kwargs.get('port_range_max') + self.remote_ip_prefix = kwargs.get('remote_ip_prefix') + + if not self.direction or not self.sec_grp_name: + raise SecurityGroupRuleConfigError( + 'direction and sec_grp_name are required') + + def dict_for_neutron(self, neutron): + """ + Returns a dictionary object representing this object. + This is meant to be converted into JSON designed for use by the Neutron + API + + :param neutron: the neutron client for performing lookups + :return: the dictionary object + """ + out = dict() + + if self.description: + out['description'] = self.description + if self.direction: + out['direction'] = self.direction.name + if self.port_range_min: + out['port_range_min'] = self.port_range_min + if self.port_range_max: + out['port_range_max'] = self.port_range_max + if self.ethertype: + out['ethertype'] = self.ethertype.name + if self.protocol and self.protocol.value != 'null': + out['protocol'] = self.protocol.value + if self.sec_grp_name: + sec_grp = neutron_utils.get_security_group( + neutron, sec_grp_name=self.sec_grp_name) + if sec_grp: + out['security_group_id'] = sec_grp.id + else: + raise SecurityGroupRuleConfigError( + 'Cannot locate security group with name - ' + + self.sec_grp_name) + if self.remote_group_id: + out['remote_group_id'] = self.remote_group_id + if self.remote_ip_prefix: + out['remote_ip_prefix'] = self.remote_ip_prefix + + return {'security_group_rule': out} + + def rule_eq(self, rule): + """ + Returns True if this setting created the rule + :param rule: the rule to evaluate + :return: T/F + """ + if self.description is not None: + if rule.description is not None and rule.description != '': + return False + elif self.description != rule.description: + if rule.description != '': + return False + + if self.direction.name != rule.direction: + return False + + if self.ethertype and rule.ethertype: + if self.ethertype.name != rule.ethertype: + return False + + if self.port_range_min and rule.port_range_min: + if self.port_range_min != rule.port_range_min: + return False + + if self.port_range_max and rule.port_range_max: + if self.port_range_max != rule.port_range_max: + return False + + if self.protocol and rule.protocol: + if self.protocol.name != rule.protocol: + return False + + if self.remote_group_id and rule.remote_group_id: + if self.remote_group_id != rule.remote_group_id: + return False + + if self.remote_ip_prefix and rule.remote_ip_prefix: + if self.remote_ip_prefix != rule.remote_ip_prefix: + return False + + return True + + def __eq__(self, other): + return ( + self.description == other.description and + self.direction == other.direction and + self.port_range_min == other.port_range_min and + self.port_range_max == other.port_range_max and + self.ethertype == other.ethertype and + self.protocol == other.protocol and + self.sec_grp_name == other.sec_grp_name and + self.remote_group_id == other.remote_group_id and + self.remote_ip_prefix == other.remote_ip_prefix) + + def __hash__(self): + return hash((self.sec_grp_name, self.description, self.direction, + self.remote_group_id, + self.protocol, self.ethertype, self.port_range_min, + self.port_range_max, self.remote_ip_prefix)) + + +def map_direction(direction): + """ + Takes a the direction value maps it to the Direction enum. When None return + None + :param direction: the direction value + :return: the Direction enum object + :raise: Exception if value is invalid + """ + if not direction: + return None + if isinstance(direction, Direction): + return direction + elif (isinstance(direction, str) or isinstance(direction, unicode) + or isinstance(direction, unicode)): + dir_str = str(direction) + if dir_str == 'egress': + return Direction.egress + elif dir_str == 'ingress': + return Direction.ingress + else: + raise SecurityGroupRuleConfigError( + 'Invalid Direction - ' + dir_str) + else: + return map_direction(direction.value) + + +def map_protocol(protocol): + """ + Takes a the protocol value maps it to the Protocol enum. When None return + None + :param protocol: the protocol value + :return: the Protocol enum object + :raise: Exception if value is invalid + """ + if not protocol: + return None + elif isinstance(protocol, Protocol): + return protocol + elif (isinstance(protocol, str) or isinstance(protocol, unicode) + or isinstance(protocol, int)): + for proto_enum in Protocol: + if proto_enum.name == protocol or proto_enum.value == protocol: + if proto_enum == Protocol.any: + return Protocol.null + return proto_enum + raise SecurityGroupRuleConfigError( + 'Invalid Protocol - ' + protocol) + else: + return map_protocol(protocol.value) + + +def map_ethertype(ethertype): + """ + Takes a the ethertype value maps it to the Ethertype enum. When None return + None + :param ethertype: the ethertype value + :return: the Ethertype enum object + :raise: Exception if value is invalid + """ + if not ethertype: + return None + elif isinstance(ethertype, Ethertype): + return ethertype + elif (isinstance(ethertype, str) or isinstance(ethertype, unicode) + or isinstance(ethertype, int)): + eth_str = str(ethertype) + if eth_str == 'IPv6' or eth_str == '6': + return Ethertype.IPv6 + elif eth_str == 'IPv4' or eth_str == '4': + return Ethertype.IPv4 + else: + raise SecurityGroupRuleConfigError( + 'Invalid Ethertype - ' + eth_str) + else: + return map_ethertype(ethertype.value) + + +class SecurityGroupRuleConfigError(Exception): + """ + Exception to be thrown when security group rule settings attributes are + invalid + """ diff --git a/snaps/config/stack.py b/snaps/config/stack.py new file mode 100644 index 0000000..4d5db29 --- /dev/null +++ b/snaps/config/stack.py @@ -0,0 +1,76 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +STACK_DELETE_TIMEOUT = 1200 +STACK_COMPLETE_TIMEOUT = 1200 +POLL_INTERVAL = 3 +STATUS_CREATE_FAILED = 'CREATE_FAILED' +STATUS_CREATE_COMPLETE = 'CREATE_COMPLETE' +STATUS_DELETE_COMPLETE = 'DELETE_COMPLETE' +STATUS_DELETE_FAILED = 'DELETE_FAILED' + + +class StackConfig(object): + """ + Configuration for Heat stack + """ + + def __init__(self, **kwargs): + """ + Constructor + :param name: the stack's name (required) + :param template: the heat template in dict() format (required if + template_path attribute is None) + :param template_path: the location of the heat template file (required + if template attribute is None) + :param resource_files: List of file paths to the resources referred to + by the template + :param env_values: dict() of strings for substitution of template + default values (optional) + """ + + self.name = kwargs.get('name') + self.template = kwargs.get('template') + self.template_path = kwargs.get('template_path') + self.resource_files = kwargs.get('resource_files') + self.env_values = kwargs.get('env_values') + + if 'stack_create_timeout' in kwargs: + self.stack_create_timeout = kwargs['stack_create_timeout'] + else: + self.stack_create_timeout = STACK_COMPLETE_TIMEOUT + + if not self.name: + raise StackConfigError('name is required') + + if not self.template and not self.template_path: + raise StackConfigError('A Heat template is required') + + if self.resource_files and not isinstance(self.resource_files, list): + raise StackConfigError( + 'resource_files must be a list when not None') + + def __eq__(self, other): + return (self.name == other.name and + self.template == other.template and + self.template_path == other.template_path and + self.env_values == other.env_values and + self.stack_create_timeout == other.stack_create_timeout) + + +class StackConfigError(Exception): + """ + Exception to be thrown when an stack configuration are incorrect + """ diff --git a/snaps/config/tests/__init__.py b/snaps/config/tests/__init__.py new file mode 100644 index 0000000..271c742 --- /dev/null +++ b/snaps/config/tests/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +__author__ = 'spisarski' diff --git a/snaps/config/tests/cluster_template_tests.py b/snaps/config/tests/cluster_template_tests.py new file mode 100644 index 0000000..e06b783 --- /dev/null +++ b/snaps/config/tests/cluster_template_tests.py @@ -0,0 +1,180 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.cluster_template import ( + ClusterTemplateConfig, ClusterTemplateConfigError, ServerType, + DockerStorageDriver, ContainerOrchestrationEngine) + + +class ClusterTemplateConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the ClusterTemplateConfig class + """ + + def test_no_params(self): + with self.assertRaises(ClusterTemplateConfigError): + ClusterTemplateConfig() + + def test_empty_config(self): + with self.assertRaises(ClusterTemplateConfigError): + ClusterTemplateConfig(config=dict()) + + def test_name_only(self): + with self.assertRaises(ClusterTemplateConfigError): + ClusterTemplateConfig(name='foo') + + def test_minimal_named(self): + config = ClusterTemplateConfig( + name='foo', image='bar', keypair='keys', external_net='external') + self.assertIsNotNone(config) + self.assertEqual('foo', config.name) + self.assertEqual('bar', config.image) + self.assertEqual('keys', config.keypair) + self.assertIsNone(config.network_driver) + self.assertEqual('external', config.external_net) + self.assertTrue(config.floating_ip_enabled) + self.assertEqual(3, config.docker_volume_size) + self.assertEqual(ServerType.vm, config.server_type) + self.assertIsNone(config.flavor) + self.assertIsNone(config.master_flavor) + self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe) + self.assertIsNone(config.fixed_net) + self.assertIsNone(config.fixed_subnet) + self.assertTrue(config.registry_enabled) + self.assertIsNone(config.insecure_registry) + self.assertEqual(DockerStorageDriver.devicemapper, + config.docker_storage_driver) + self.assertIsNone(config.dns_nameserver) + self.assertFalse(config.public) + self.assertFalse(config.tls_disabled) + self.assertIsNone(config.http_proxy) + self.assertIsNone(config.https_proxy) + self.assertIsNone(config.no_proxy) + self.assertIsNone(config.volume_driver) + self.assertTrue(config.master_lb_enabled) + self.assertIsNone(config.labels) + + def test_minimal_config(self): + config = ClusterTemplateConfig( + **{'name': 'foo', 'image': 'bar', 'keypair': 'keys', + 'external_net': 'external'}) + self.assertIsNotNone(config) + self.assertEqual('foo', config.name) + self.assertEqual('bar', config.image) + self.assertEqual('keys', config.keypair) + self.assertIsNone(config.network_driver) + self.assertEqual('external', config.external_net) + self.assertTrue(config.floating_ip_enabled) + self.assertEqual(3, config.docker_volume_size) + self.assertEqual(ServerType.vm, config.server_type) + self.assertIsNone(config.flavor) + self.assertIsNone(config.master_flavor) + self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe) + self.assertIsNone(config.fixed_net) + self.assertIsNone(config.fixed_subnet) + self.assertTrue(config.registry_enabled) + self.assertIsNone(config.insecure_registry) + self.assertEqual(DockerStorageDriver.devicemapper, + config.docker_storage_driver) + self.assertIsNone(config.dns_nameserver) + self.assertFalse(config.public) + self.assertFalse(config.tls_disabled) + self.assertIsNone(config.http_proxy) + self.assertIsNone(config.https_proxy) + self.assertIsNone(config.no_proxy) + self.assertIsNone(config.volume_driver) + self.assertTrue(config.master_lb_enabled) + self.assertIsNone(config.labels) + + def test_all_named(self): + labels = {'foo': 'bar'} + config = ClusterTemplateConfig( + name='foo', image='bar', keypair='keys', network_driver='driver', + external_net='external', docker_volume_size=99, + server_type=ServerType.baremetal, flavor='testFlavor', + master_flavor='masterFlavor', + coe=ContainerOrchestrationEngine.kubernetes, fixed_net='fixedNet', + fixed_subnet='fixedSubnet', registry_enabled=False, + docker_storage_driver=DockerStorageDriver.overlay, + dns_nameserver='8.8.4.4', public=True, tls=False, + http_proxy='http://foo:8080', https_proxy='https://foo:443', + no_proxy='foo,bar', volume_driver='volDriver', + master_lb_enabled=False, labels=labels) + self.assertIsNotNone(config) + self.assertEqual('foo', config.name) + self.assertEqual('bar', config.image) + self.assertEqual('keys', config.keypair) + self.assertEqual('driver', config.network_driver) + self.assertEqual('external', config.external_net) + self.assertEqual(99, config.docker_volume_size) + self.assertEqual(ServerType.baremetal, config.server_type) + self.assertEqual('testFlavor', config.flavor) + self.assertEqual('masterFlavor', config.master_flavor) + self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe) + self.assertEqual('fixedNet', config.fixed_net) + self.assertEqual('fixedSubnet', config.fixed_subnet) + self.assertFalse(config.registry_enabled) + self.assertEqual(DockerStorageDriver.overlay, + config.docker_storage_driver) + self.assertEqual('8.8.4.4', config.dns_nameserver) + self.assertTrue(config.public) + self.assertFalse(config.tls_disabled) + self.assertEqual('http://foo:8080', config.http_proxy) + self.assertEqual('https://foo:443', config.https_proxy) + self.assertEqual('foo,bar', config.no_proxy) + self.assertEqual('volDriver', config.volume_driver) + self.assertFalse(config.master_lb_enabled) + self.assertEqual(labels, config.labels) + + def test_all_config(self): + labels = {'foo': 'bar'} + config = ClusterTemplateConfig(**{ + 'name': 'foo', 'image': 'bar', 'keypair': 'keys', + 'network_driver': 'driver', 'external_net': 'external', + 'docker_volume_size': '99', 'server_type': 'baremetal', + 'flavor': 'testFlavor', 'master_flavor': 'masterFlavor', + 'coe': 'kubernetes', 'fixed_net': 'fixedNet', + 'fixed_subnet': 'fixedSubnet', 'registry_enabled': 'false', + 'docker_storage_driver': 'overlay', 'dns_nameserver': '8.8.4.4', + 'public': 'true', 'tls': 'false', 'http_proxy': 'http://foo:8080', + 'https_proxy': 'https://foo:443', 'no_proxy': 'foo,bar', + 'volume_driver': 'volDriver', 'master_lb_enabled': 'false', + 'labels': labels}) + self.assertIsNotNone(config) + self.assertEqual('foo', config.name) + self.assertEqual('bar', config.image) + self.assertEqual('keys', config.keypair) + self.assertEqual('driver', config.network_driver) + self.assertEqual('external', config.external_net) + self.assertEqual(99, config.docker_volume_size) + self.assertEqual(ServerType.baremetal, config.server_type) + self.assertEqual('testFlavor', config.flavor) + self.assertEqual('masterFlavor', config.master_flavor) + self.assertEqual(ContainerOrchestrationEngine.kubernetes, config.coe) + self.assertEqual('fixedNet', config.fixed_net) + self.assertEqual('fixedSubnet', config.fixed_subnet) + self.assertFalse(config.registry_enabled) + self.assertEqual(DockerStorageDriver.overlay, + config.docker_storage_driver) + self.assertEqual('8.8.4.4', config.dns_nameserver) + self.assertTrue(config.public) + self.assertFalse(config.tls_disabled) + self.assertEqual('http://foo:8080', config.http_proxy) + self.assertEqual('https://foo:443', config.https_proxy) + self.assertEqual('foo,bar', config.no_proxy) + self.assertEqual('volDriver', config.volume_driver) + self.assertFalse(config.master_lb_enabled) + self.assertEqual(labels, config.labels) diff --git a/snaps/config/tests/flavor_tests.py b/snaps/config/tests/flavor_tests.py new file mode 100644 index 0000000..15cd99a --- /dev/null +++ b/snaps/config/tests/flavor_tests.py @@ -0,0 +1,254 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.flavor import FlavorConfig, FlavorConfigError + + +class FlavorConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the FlavorConfig class + """ + + def test_no_params(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig() + + def test_empty_config(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(config=dict()) + + def test_name_only(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(config={'name': 'foo'}) + + def test_name_ram_only(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1) + + def test_config_with_name_ram_only(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(config={'name': 'foo', 'ram': 1}) + + def test_name_ram_disk_only(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=1) + + def test_config_with_name_ram_disk_only(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(config={'name': 'foo', 'ram': 1, 'disk': 1}) + + def test_ram_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram='bar', disk=2, vcpus=3, ephemeral=4, + swap=5, rxtx_factor=6.0, + is_public=False) + + def test_config_ram_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 'bar', 'disk': 2, 'vcpus': 3, + 'ephemeral': 4, 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_ram_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1.5, disk=2, vcpus=3, ephemeral=4, + swap=5, rxtx_factor=6.0, is_public=False) + + def test_config_ram_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1.5, 'disk': 2, 'vcpus': 3, + 'ephemeral': 4, 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_disk_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk='bar', vcpus=3, ephemeral=4, + swap=5, rxtx_factor=6.0, + is_public=False) + + def test_config_disk_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 'bar', 'vcpus': 3, + 'ephemeral': 4, 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_disk_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2.5, vcpus=3, ephemeral=4, + swap=5, rxtx_factor=6.0, is_public=False) + + def test_config_disk_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2.5, 'vcpus': 3, + 'ephemeral': 4, 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_vcpus_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2, vcpus='bar', ephemeral=4, + swap=5, rxtx_factor=6.0, + is_public=False) + + def test_config_vcpus_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 'bar', + 'ephemeral': 4, 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_ephemeral_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral='bar', + swap=5, rxtx_factor=6.0, + is_public=False) + + def test_config_ephemeral_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, + 'ephemeral': 'bar', 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_ephemeral_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4.5, + swap=5, rxtx_factor=6.0, is_public=False) + + def test_config_ephemeral_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, + 'ephemeral': 4.5, 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_swap_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, + swap='bar', rxtx_factor=6.0, + is_public=False) + + def test_config_swap_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, + 'ephemeral': 4, 'swap': 'bar', + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_swap_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, + swap=5.5, rxtx_factor=6.0, is_public=False) + + def test_config_swap_float(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, + 'ephemeral': 4, 'swap': 5.5, + 'rxtx_factor': 6.0, 'is_public': False}) + + def test_rxtx_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, + swap=5, rxtx_factor='bar', is_public=False) + + def test_config_rxtx_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, + 'ephemeral': 4, 'swap': 5, + 'rxtx_factor': 'bar', 'is_public': False}) + + def test_is_pub_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, + swap=5, rxtx_factor=6.0, is_public='bar') + + def test_config_is_pub_string(self): + with self.assertRaises(FlavorConfigError): + FlavorConfig( + config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, + 'ephemeral': 4, 'swap': 5, + 'rxtx_factor': 6.0, 'is_public': 'bar'}) + + def test_name_ram_disk_vcpus_only(self): + settings = FlavorConfig(name='foo', ram=1, disk=2, vcpus=3) + self.assertEqual('foo', settings.name) + self.assertEqual('auto', settings.flavor_id) + self.assertEqual(1, settings.ram) + self.assertEqual(2, settings.disk) + self.assertEqual(3, settings.vcpus) + self.assertEqual(0, settings.ephemeral) + self.assertEqual(0, settings.swap) + self.assertEqual(1.0, settings.rxtx_factor) + self.assertEqual(True, settings.is_public) + self.assertEqual(None, settings.metadata) + + def test_config_with_name_ram_disk_vcpus_only(self): + settings = FlavorConfig( + **{'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3}) + self.assertEqual('foo', settings.name) + self.assertEqual('auto', settings.flavor_id) + self.assertEqual(1, settings.ram) + self.assertEqual(2, settings.disk) + self.assertEqual(3, settings.vcpus) + self.assertEqual(0, settings.ephemeral) + self.assertEqual(0, settings.swap) + self.assertEqual(1.0, settings.rxtx_factor) + self.assertEqual(True, settings.is_public) + self.assertEqual(None, settings.metadata) + + def test_all(self): + metadata = {'foo': 'bar'} + settings = FlavorConfig( + name='foo', flavor_id='bar', ram=1, disk=2, vcpus=3, ephemeral=4, + swap=5, rxtx_factor=6.0, is_public=False, metadata=metadata) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.flavor_id) + self.assertEqual(1, settings.ram) + self.assertEqual(2, settings.disk) + self.assertEqual(3, settings.vcpus) + self.assertEqual(4, settings.ephemeral) + self.assertEqual(5, settings.swap) + self.assertEqual(6.0, settings.rxtx_factor) + self.assertEqual(False, settings.is_public) + self.assertEqual(metadata, settings.metadata) + + def test_config_all(self): + metadata = {'foo': 'bar'} + settings = FlavorConfig( + **{'name': 'foo', 'flavor_id': 'bar', 'ram': 1, 'disk': 2, + 'vcpus': 3, + 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 6.0, + 'is_public': False, + 'metadata': metadata}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.flavor_id) + self.assertEqual(1, settings.ram) + self.assertEqual(2, settings.disk) + self.assertEqual(3, settings.vcpus) + self.assertEqual(4, settings.ephemeral) + self.assertEqual(5, settings.swap) + self.assertEqual(6.0, settings.rxtx_factor) + self.assertEqual(False, settings.is_public) + self.assertEqual(metadata, settings.metadata) diff --git a/snaps/config/tests/image_tests.py b/snaps/config/tests/image_tests.py new file mode 100644 index 0000000..8dcd2b4 --- /dev/null +++ b/snaps/config/tests/image_tests.py @@ -0,0 +1,226 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.image import ImageConfigError, ImageConfig + + +class ImageConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the ImageConfig class + """ + + def test_no_params(self): + with self.assertRaises(ImageConfigError): + ImageConfig() + + def test_empty_config(self): + with self.assertRaises(ImageConfigError): + ImageConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(ImageConfigError): + ImageConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(ImageConfigError): + ImageConfig(**{'name': 'foo'}) + + def test_name_user_only(self): + with self.assertRaises(ImageConfigError): + ImageConfig(name='foo', image_user='bar') + + def test_config_with_name_user_only(self): + with self.assertRaises(ImageConfigError): + ImageConfig(**{'name': 'foo', 'image_user': 'bar'}) + + def test_name_user_format_only(self): + with self.assertRaises(ImageConfigError): + ImageConfig(name='foo', image_user='bar', img_format='qcow2') + + def test_config_with_name_user_format_only(self): + with self.assertRaises(ImageConfigError): + ImageConfig( + **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2'}) + + def test_name_user_format_url_only(self): + settings = ImageConfig(name='foo', image_user='bar', + img_format='qcow2', url='http://foo.com') + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertEqual('http://foo.com', settings.url) + self.assertIsNone(settings.image_file) + self.assertFalse(settings.exists) + self.assertFalse(settings.public) + self.assertIsNone(settings.nic_config_pb_loc) + + def test_name_user_format_url_only_properties(self): + properties = {'hw_video_model': 'vga'} + settings = ImageConfig(name='foo', image_user='bar', + img_format='qcow2', url='http://foo.com', + extra_properties=properties) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertEqual('http://foo.com', settings.url) + self.assertEqual(properties, settings.extra_properties) + self.assertIsNone(settings.image_file) + self.assertFalse(settings.exists) + self.assertFalse(settings.public) + self.assertIsNone(settings.nic_config_pb_loc) + + def test_config_with_name_user_format_url_only(self): + settings = ImageConfig( + **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2', + 'download_url': 'http://foo.com'}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertEqual('http://foo.com', settings.url) + self.assertIsNone(settings.image_file) + self.assertFalse(settings.exists) + self.assertFalse(settings.public) + self.assertIsNone(settings.nic_config_pb_loc) + + def test_name_user_format_file_only(self): + settings = ImageConfig(name='foo', image_user='bar', + img_format='qcow2', + image_file='/foo/bar.qcow') + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertIsNone(settings.url) + self.assertEqual('/foo/bar.qcow', settings.image_file) + self.assertFalse(settings.exists) + self.assertFalse(settings.public) + self.assertIsNone(settings.nic_config_pb_loc) + + def test_config_with_name_user_format_file_only(self): + settings = ImageConfig( + **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2', + 'image_file': '/foo/bar.qcow'}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertIsNone(settings.url) + self.assertEqual('/foo/bar.qcow', settings.image_file) + self.assertFalse(settings.exists) + self.assertFalse(settings.public) + self.assertIsNone(settings.nic_config_pb_loc) + + def test_all_url(self): + properties = {'hw_video_model': 'vga'} + kernel_settings = ImageConfig(name='kernel', url='http://kernel.com', + image_user='bar', img_format='qcow2') + ramdisk_settings = ImageConfig(name='ramdisk', + url='http://ramdisk.com', + image_user='bar', img_format='qcow2') + settings = ImageConfig(name='foo', image_user='bar', + img_format='qcow2', url='http://foo.com', + extra_properties=properties, + nic_config_pb_loc='/foo/bar', + kernel_image_settings=kernel_settings, + ramdisk_image_settings=ramdisk_settings, + exists=True, public=True) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertEqual('http://foo.com', settings.url) + self.assertEqual(properties, settings.extra_properties) + self.assertIsNone(settings.image_file) + self.assertEqual('/foo/bar', settings.nic_config_pb_loc) + self.assertEqual('kernel', settings.kernel_image_settings.name) + self.assertEqual('http://kernel.com', + settings.kernel_image_settings.url) + self.assertEqual('bar', settings.kernel_image_settings.image_user) + self.assertEqual('qcow2', settings.kernel_image_settings.format) + self.assertEqual('ramdisk', settings.ramdisk_image_settings.name) + self.assertEqual('http://ramdisk.com', + settings.ramdisk_image_settings.url) + self.assertEqual('bar', settings.ramdisk_image_settings.image_user) + self.assertEqual('qcow2', settings.ramdisk_image_settings.format) + self.assertTrue(settings.exists) + self.assertTrue(settings.public) + + def test_config_all_url(self): + settings = ImageConfig( + **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2', + 'download_url': 'http://foo.com', + 'extra_properties': '{\'hw_video_model\': \'vga\'}', + 'nic_config_pb_loc': '/foo/bar', + 'kernel_image_settings': { + 'name': 'kernel', + 'download_url': 'http://kernel.com', + 'image_user': 'bar', + 'format': 'qcow2'}, + 'ramdisk_image_settings': { + 'name': 'ramdisk', + 'download_url': 'http://ramdisk.com', + 'image_user': 'bar', + 'format': 'qcow2'}, + 'exists': True, 'public': True}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertEqual('http://foo.com', settings.url) + self.assertEqual('{\'hw_video_model\': \'vga\'}', + settings.extra_properties) + self.assertIsNone(settings.image_file) + self.assertEqual('/foo/bar', settings.nic_config_pb_loc) + self.assertEqual('kernel', settings.kernel_image_settings.name) + self.assertEqual('http://kernel.com', + settings.kernel_image_settings.url) + self.assertEqual('ramdisk', settings.ramdisk_image_settings.name) + self.assertEqual('http://ramdisk.com', + settings.ramdisk_image_settings.url) + self.assertTrue(settings.exists) + self.assertTrue(settings.public) + + def test_all_file(self): + properties = {'hw_video_model': 'vga'} + settings = ImageConfig(name='foo', image_user='bar', + img_format='qcow2', + image_file='/foo/bar.qcow', + extra_properties=properties, + nic_config_pb_loc='/foo/bar', exists=True, + public=True) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertIsNone(settings.url) + self.assertEqual('/foo/bar.qcow', settings.image_file) + self.assertEqual(properties, settings.extra_properties) + self.assertEqual('/foo/bar', settings.nic_config_pb_loc) + self.assertTrue(settings.exists) + self.assertTrue(settings.public) + + def test_config_all_file(self): + settings = ImageConfig( + **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2', + 'image_file': '/foo/bar.qcow', + 'extra_properties': '{\'hw_video_model\' : \'vga\'}', + 'nic_config_pb_loc': '/foo/bar', 'exists': True, + 'public': True}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.image_user) + self.assertEqual('qcow2', settings.format) + self.assertIsNone(settings.url) + self.assertEqual('/foo/bar.qcow', settings.image_file) + self.assertEqual('{\'hw_video_model\' : \'vga\'}', + settings.extra_properties) + self.assertEqual('/foo/bar', settings.nic_config_pb_loc) + self.assertTrue(settings.exists) + self.assertTrue(settings.public) diff --git a/snaps/config/tests/keypair_tests.py b/snaps/config/tests/keypair_tests.py new file mode 100644 index 0000000..6d0fec4 --- /dev/null +++ b/snaps/config/tests/keypair_tests.py @@ -0,0 +1,179 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.keypair import KeypairConfigError, KeypairConfig + + +class KeypairConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the KeypairConfig class + """ + + def test_no_params(self): + with self.assertRaises(KeypairConfigError): + KeypairConfig() + + def test_empty_config(self): + with self.assertRaises(KeypairConfigError): + KeypairConfig(**dict()) + + def test_small_key_size(self): + with self.assertRaises(KeypairConfigError): + KeypairConfig(name='foo', key_size=511) + + def test_name_only(self): + settings = KeypairConfig(name='foo') + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertIsNone(settings.public_filepath) + self.assertIsNone(settings.private_filepath) + self.assertIsNone(settings.delete_on_clean) + + def test_config_with_name_only(self): + settings = KeypairConfig(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertIsNone(settings.public_filepath) + self.assertIsNone(settings.private_filepath) + self.assertIsNone(settings.delete_on_clean) + + def test_name_pub_only(self): + settings = KeypairConfig(name='foo', public_filepath='/foo/bar.pub') + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertIsNone(settings.private_filepath) + self.assertIsNone(settings.delete_on_clean) + + def test_config_with_name_pub_only(self): + settings = KeypairConfig( + **{'name': 'foo', 'public_filepath': '/foo/bar.pub'}) + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertIsNone(settings.private_filepath) + self.assertIsNone(settings.delete_on_clean) + + def test_name_priv_only(self): + settings = KeypairConfig(name='foo', private_filepath='/foo/bar') + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertIsNone(settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertIsNone(settings.delete_on_clean) + + def test_config_with_name_priv_only(self): + settings = KeypairConfig( + **{'name': 'foo', 'private_filepath': '/foo/bar'}) + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertIsNone(settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertIsNone(settings.delete_on_clean) + + def test_all_delete_bool(self): + settings = KeypairConfig( + name='foo', public_filepath='/foo/bar.pub', + private_filepath='/foo/bar', delete_on_clean=True, + key_size=999) + self.assertEqual('foo', settings.name) + self.assertEqual(999, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertTrue(settings.delete_on_clean) + + def test_all_delete_str_true_cap(self): + settings = KeypairConfig( + name='foo', public_filepath='/foo/bar.pub', + private_filepath='/foo/bar', delete_on_clean='True') + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertTrue(settings.delete_on_clean) + + def test_all_delete_str_true_lc(self): + settings = KeypairConfig( + name='foo', public_filepath='/foo/bar.pub', + private_filepath='/foo/bar', delete_on_clean='true') + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertTrue(settings.delete_on_clean) + + def test_all_delete_str_false_cap(self): + settings = KeypairConfig( + name='foo', public_filepath='/foo/bar.pub', + private_filepath='/foo/bar', delete_on_clean='False') + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertFalse(settings.delete_on_clean) + + def test_all_delete_str_false_lc(self): + settings = KeypairConfig( + name='foo', public_filepath='/foo/bar.pub', + private_filepath='/foo/bar', delete_on_clean='false', + key_size='999') + self.assertEqual('foo', settings.name) + self.assertEqual(999, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertFalse(settings.delete_on_clean) + + def test_config_all_delete_false_bool(self): + settings = KeypairConfig( + **{'name': 'foo', 'public_filepath': '/foo/bar.pub', + 'private_filepath': '/foo/bar', 'delete_on_clean': False, + 'key_size': 999}) + self.assertEqual('foo', settings.name) + self.assertEqual(999, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertFalse(settings.delete_on_clean) + + def test_config_all_delete_false_str_cap(self): + settings = KeypairConfig( + **{'name': 'foo', 'public_filepath': '/foo/bar.pub', + 'private_filepath': '/foo/bar', 'delete_on_clean': 'False'}) + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertFalse(settings.delete_on_clean) + + def test_config_all_delete_false_str_lc(self): + settings = KeypairConfig( + **{'name': 'foo', 'public_filepath': '/foo/bar.pub', + 'private_filepath': '/foo/bar', 'delete_on_clean': 'false'}) + self.assertEqual('foo', settings.name) + self.assertEqual(1024, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertFalse(settings.delete_on_clean) + + def test_config_all_delete_false_str_foo(self): + settings = KeypairConfig( + **{'name': 'foo', 'public_filepath': '/foo/bar.pub', + 'private_filepath': '/foo/bar', 'delete_on_clean': 'foo', + 'key_size': '999'}) + self.assertEqual('foo', settings.name) + self.assertEqual(999, settings.key_size) + self.assertEqual('/foo/bar.pub', settings.public_filepath) + self.assertEqual('/foo/bar', settings.private_filepath) + self.assertFalse(settings.delete_on_clean) diff --git a/snaps/config/tests/network_tests.py b/snaps/config/tests/network_tests.py new file mode 100644 index 0000000..43b69c7 --- /dev/null +++ b/snaps/config/tests/network_tests.py @@ -0,0 +1,336 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.network import ( + NetworkConfigError, NetworkConfig, SubnetConfig, SubnetConfigError, + IPv6Mode, PortConfig, PortConfigError) + + +class NetworkConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the NetworkConfig class + """ + + def test_no_params(self): + with self.assertRaises(NetworkConfigError): + NetworkConfig() + + def test_empty_config(self): + with self.assertRaises(NetworkConfigError): + NetworkConfig(**dict()) + + def test_name_only(self): + settings = NetworkConfig(name='foo') + self.assertEqual('foo', settings.name) + self.assertTrue(settings.admin_state_up) + self.assertIsNone(settings.shared) + self.assertIsNone(settings.project_name) + self.assertFalse(settings.external) + self.assertIsNone(settings.network_type) + self.assertIsNone(settings.segmentation_id) + self.assertEqual(0, len(settings.subnet_settings)) + + def test_config_with_name_only(self): + settings = NetworkConfig(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + self.assertTrue(settings.admin_state_up) + self.assertIsNone(settings.shared) + self.assertIsNone(settings.project_name) + self.assertFalse(settings.external) + self.assertIsNone(settings.network_type) + self.assertIsNone(settings.segmentation_id) + self.assertEqual(0, len(settings.subnet_settings)) + + def test_all(self): + sub_settings = SubnetConfig(name='foo-subnet', cidr='10.0.0.0/24') + settings = NetworkConfig( + name='foo', admin_state_up=False, shared=True, project_name='bar', + external=True, network_type='vlan', physical_network='phy', + segmentation_id=2366, subnet_settings=[sub_settings]) + self.assertEqual('foo', settings.name) + self.assertFalse(settings.admin_state_up) + self.assertTrue(settings.shared) + self.assertEqual('bar', settings.project_name) + self.assertTrue(settings.external) + self.assertEqual('vlan', settings.network_type) + self.assertEqual('phy', settings.physical_network) + self.assertEqual(2366, settings.segmentation_id) + self.assertEqual(1, len(settings.subnet_settings)) + self.assertEqual('foo-subnet', settings.subnet_settings[0].name) + + def test_config_all(self): + settings = NetworkConfig( + **{'name': 'foo', 'admin_state_up': False, 'shared': True, + 'project_name': 'bar', 'external': True, 'network_type': 'vlan', + 'physical_network': 'phy', + 'segmentation_id': 2366, + 'subnets': + [{'subnet': {'name': 'foo-subnet', + 'cidr': '10.0.0.0/24'}}]}) + self.assertEqual('foo', settings.name) + self.assertFalse(settings.admin_state_up) + self.assertTrue(settings.shared) + self.assertEqual('bar', settings.project_name) + self.assertTrue(settings.external) + self.assertEqual('vlan', settings.network_type) + self.assertEqual('phy', settings.physical_network) + self.assertEqual(2366, settings.segmentation_id) + self.assertEqual(1, len(settings.subnet_settings)) + self.assertEqual('foo-subnet', settings.subnet_settings[0].name) + + +class SubnetConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the SubnetConfig class + """ + + def test_no_params(self): + with self.assertRaises(SubnetConfigError): + SubnetConfig() + + def test_empty_config(self): + with self.assertRaises(SubnetConfigError): + SubnetConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(SubnetConfigError): + SubnetConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(SubnetConfigError): + SubnetConfig(**{'name': 'foo'}) + + def test_name_cidr_only(self): + settings = SubnetConfig(name='foo', cidr='10.0.0.0/24') + self.assertEqual('foo', settings.name) + self.assertEqual('10.0.0.0/24', settings.cidr) + self.assertEqual(4, settings.ip_version) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.start) + self.assertIsNone(settings.end) + self.assertIsNone(settings.enable_dhcp) + self.assertEqual(1, len(settings.dns_nameservers)) + self.assertEqual('8.8.8.8', settings.dns_nameservers[0]) + self.assertIsNone(settings.host_routes) + self.assertIsNone(settings.destination) + self.assertIsNone(settings.nexthop) + self.assertIsNone(settings.ipv6_ra_mode) + self.assertIsNone(settings.ipv6_address_mode) + + def test_config_with_name_cidr_only(self): + settings = SubnetConfig(**{'name': 'foo', 'cidr': '10.0.0.0/24'}) + self.assertEqual('foo', settings.name) + self.assertEqual('10.0.0.0/24', settings.cidr) + self.assertEqual(4, settings.ip_version) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.start) + self.assertIsNone(settings.end) + self.assertIsNone(settings.gateway_ip) + self.assertIsNone(settings.enable_dhcp) + self.assertEqual(1, len(settings.dns_nameservers)) + self.assertEqual('8.8.8.8', settings.dns_nameservers[0]) + self.assertIsNone(settings.host_routes) + self.assertIsNone(settings.destination) + self.assertIsNone(settings.nexthop) + self.assertIsNone(settings.ipv6_ra_mode) + self.assertIsNone(settings.ipv6_address_mode) + + def test_all_string_enums(self): + host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'} + settings = SubnetConfig( + name='foo', cidr='10.0.0.0/24', ip_version=6, + project_name='bar-project', start='10.0.0.2', end='10.0.0.101', + gateway_ip='10.0.0.1', enable_dhcp=False, + dns_nameservers=['8.8.8.8'], host_routes=[host_routes], + destination='dest', nexthop='hop', ipv6_ra_mode='dhcpv6-stateful', + ipv6_address_mode='slaac') + self.assertEqual('foo', settings.name) + self.assertEqual('10.0.0.0/24', settings.cidr) + self.assertEqual(6, settings.ip_version) + self.assertEqual('bar-project', settings.project_name) + self.assertEqual('10.0.0.2', settings.start) + self.assertEqual('10.0.0.101', settings.end) + self.assertEqual('10.0.0.1', settings.gateway_ip) + self.assertEqual(False, settings.enable_dhcp) + self.assertEqual(1, len(settings.dns_nameservers)) + self.assertEqual('8.8.8.8', settings.dns_nameservers[0]) + self.assertEqual(1, len(settings.host_routes)) + self.assertEqual(host_routes, settings.host_routes[0]) + self.assertEqual('dest', settings.destination) + self.assertEqual('hop', settings.nexthop) + self.assertEqual(IPv6Mode.stateful, settings.ipv6_ra_mode) + self.assertEqual(IPv6Mode.slaac, settings.ipv6_address_mode) + + def test_all_type_enums(self): + host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'} + settings = SubnetConfig( + name='foo', cidr='10.0.0.0/24', ip_version=6, + project_name='bar-project', start='10.0.0.2', end='10.0.0.101', + gateway_ip='10.0.0.1', enable_dhcp=False, + dns_nameservers=['8.8.8.8'], host_routes=[host_routes], + destination='dest', nexthop='hop', ipv6_ra_mode=IPv6Mode.stateful, + ipv6_address_mode=IPv6Mode.slaac) + self.assertEqual('foo', settings.name) + self.assertEqual('10.0.0.0/24', settings.cidr) + self.assertEqual(6, settings.ip_version) + self.assertEqual('bar-project', settings.project_name) + self.assertEqual('10.0.0.2', settings.start) + self.assertEqual('10.0.0.101', settings.end) + self.assertEqual('10.0.0.1', settings.gateway_ip) + self.assertEqual(False, settings.enable_dhcp) + self.assertEqual(1, len(settings.dns_nameservers)) + self.assertEqual('8.8.8.8', settings.dns_nameservers[0]) + self.assertEqual(1, len(settings.host_routes)) + self.assertEqual(host_routes, settings.host_routes[0]) + self.assertEqual('dest', settings.destination) + self.assertEqual('hop', settings.nexthop) + self.assertEqual(IPv6Mode.stateful, settings.ipv6_ra_mode) + self.assertEqual(IPv6Mode.slaac, settings.ipv6_address_mode) + + def test_config_all(self): + host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'} + settings = SubnetConfig( + **{'name': 'foo', 'cidr': '10.0.0.0/24', 'ip_version': 6, + 'project_name': 'bar-project', 'start': '10.0.0.2', + 'end': '10.0.0.101', + 'gateway_ip': '10.0.0.1', 'enable_dhcp': False, + 'dns_nameservers': ['8.8.8.8'], 'host_routes': [host_routes], + 'destination': 'dest', 'nexthop': 'hop', + 'ipv6_ra_mode': 'dhcpv6-stateless', + 'ipv6_address_mode': 'slaac'}) + self.assertEqual('foo', settings.name) + self.assertEqual('10.0.0.0/24', settings.cidr) + self.assertEqual(6, settings.ip_version) + self.assertEqual('bar-project', settings.project_name) + self.assertEqual('10.0.0.2', settings.start) + self.assertEqual('10.0.0.101', settings.end) + self.assertEqual('10.0.0.1', settings.gateway_ip) + self.assertEqual(False, settings.enable_dhcp) + self.assertEqual(1, len(settings.dns_nameservers)) + self.assertEqual('8.8.8.8', settings.dns_nameservers[0]) + self.assertEqual(1, len(settings.host_routes)) + self.assertEqual(host_routes, settings.host_routes[0]) + self.assertEqual('dest', settings.destination) + self.assertEqual('hop', settings.nexthop) + self.assertEqual(IPv6Mode.stateless, settings.ipv6_ra_mode) + self.assertEqual(IPv6Mode.slaac, settings.ipv6_address_mode) + + +class PortConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the PortConfig class + """ + + def test_no_params(self): + with self.assertRaises(PortConfigError): + PortConfig() + + def test_empty_config(self): + with self.assertRaises(PortConfigError): + PortConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(PortConfigError): + PortConfig(name='foo') + + def test_config_name_only(self): + with self.assertRaises(PortConfigError): + PortConfig(**{'name': 'foo'}) + + def test_name_netname_only(self): + settings = PortConfig(name='foo', network_name='bar') + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.network_name) + self.assertTrue(settings.admin_state_up) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.mac_address) + self.assertIsNone(settings.ip_addrs) + self.assertIsNone(settings.security_groups) + self.assertIsNone(settings.allowed_address_pairs) + self.assertIsNone(settings.opt_value) + self.assertIsNone(settings.opt_name) + self.assertIsNone(settings.device_owner) + self.assertIsNone(settings.device_id) + + def test_config_with_name_netname_only(self): + settings = PortConfig(**{'name': 'foo', 'network_name': 'bar'}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.network_name) + self.assertTrue(settings.admin_state_up) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.mac_address) + self.assertIsNone(settings.ip_addrs) + self.assertIsNone(settings.security_groups) + self.assertIsNone(settings.port_security_enabled) + self.assertIsNone(settings.allowed_address_pairs) + self.assertIsNone(settings.opt_value) + self.assertIsNone(settings.opt_name) + self.assertIsNone(settings.device_owner) + self.assertIsNone(settings.device_id) + + def test_all(self): + ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}] + allowed_address_pairs = {'10.0.0.101', '1234.5678'} + + settings = PortConfig( + name='foo', network_name='bar', admin_state_up=False, + project_name='foo-project', mac_address='1234', ip_addrs=ip_addrs, + security_groups=['foo_grp_id'], port_security_enabled=False, + allowed_address_pairs=allowed_address_pairs, opt_value='opt value', + opt_name='opt name', device_owner='owner', + device_id='device number') + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.network_name) + self.assertFalse(settings.admin_state_up) + self.assertEqual('foo-project', settings.project_name) + self.assertEqual('1234', settings.mac_address) + self.assertEqual(ip_addrs, settings.ip_addrs) + self.assertEqual(1, len(settings.security_groups)) + self.assertFalse(settings.port_security_enabled) + self.assertEqual('foo_grp_id', settings.security_groups[0]) + self.assertFalse(settings.port_security_enabled) + self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs) + self.assertEqual('opt value', settings.opt_value) + self.assertEqual('opt name', settings.opt_name) + self.assertEqual('owner', settings.device_owner) + self.assertEqual('device number', settings.device_id) + + def test_config_all(self): + ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}] + allowed_address_pairs = {'10.0.0.101', '1234.5678'} + + settings = PortConfig( + **{'name': 'foo', 'network_name': 'bar', 'admin_state_up': False, + 'project_name': 'foo-project', 'mac_address': '1234', + 'ip_addrs': ip_addrs, 'security_groups': ['foo_grp_id'], + 'port_security_enabled': 'false', + 'allowed_address_pairs': allowed_address_pairs, + 'opt_value': 'opt value', 'opt_name': 'opt name', + 'device_owner': 'owner', 'device_id': 'device number'}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.network_name) + self.assertFalse(settings.admin_state_up) + self.assertEqual('foo-project', settings.project_name) + self.assertEqual('1234', settings.mac_address) + self.assertEqual(ip_addrs, settings.ip_addrs) + self.assertEqual(1, len(settings.security_groups)) + self.assertFalse(settings.port_security_enabled) + self.assertEqual('foo_grp_id', settings.security_groups[0]) + self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs) + self.assertEqual('opt value', settings.opt_value) + self.assertEqual('opt name', settings.opt_name) + self.assertEqual('owner', settings.device_owner) + self.assertEqual('device number', settings.device_id) diff --git a/snaps/config/tests/project_tests.py b/snaps/config/tests/project_tests.py new file mode 100644 index 0000000..0470d83 --- /dev/null +++ b/snaps/config/tests/project_tests.py @@ -0,0 +1,69 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.project import ProjectConfig, ProjectConfigError + + +class ProjectConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the ProjectConfig class + """ + + def test_no_params(self): + with self.assertRaises(ProjectConfigError): + ProjectConfig() + + def test_empty_config(self): + with self.assertRaises(ProjectConfigError): + ProjectConfig(**dict()) + + def test_name_only(self): + settings = ProjectConfig(name='foo') + self.assertEqual('foo', settings.name) + self.assertEqual('Default', settings.domain_name) + self.assertIsNone(settings.description) + self.assertTrue(settings.enabled) + self.assertEqual(list(), settings.users) + + def test_config_with_name_only(self): + settings = ProjectConfig(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + self.assertEqual('Default', settings.domain_name) + self.assertIsNone(settings.description) + self.assertTrue(settings.enabled) + self.assertEqual(list(), settings.users) + + def test_all(self): + users = ['test1', 'test2'] + settings = ProjectConfig( + name='foo', domain='bar', description='foobar', enabled=False, + users=users) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.domain_name) + self.assertEqual('foobar', settings.description) + self.assertFalse(settings.enabled) + self.assertEqual(users, settings.users) + + def test_config_all(self): + users = ['test1', 'test2'] + settings = ProjectConfig( + **{'name': 'foo', 'domain': 'bar', 'description': 'foobar', + 'enabled': False, 'users': users}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.domain_name) + self.assertEqual('foobar', settings.description) + self.assertFalse(settings.enabled) + self.assertEqual(users, settings.users) diff --git a/snaps/config/tests/qos_tests.py b/snaps/config/tests/qos_tests.py new file mode 100644 index 0000000..7314c5b --- /dev/null +++ b/snaps/config/tests/qos_tests.py @@ -0,0 +1,91 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.qos import QoSConfig, QoSConfigError, Consumer + + +class QoSConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the QoSConfig class + """ + + def test_no_params(self): + with self.assertRaises(QoSConfigError): + QoSConfig() + + def test_empty_config(self): + with self.assertRaises(QoSConfigError): + QoSConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(QoSConfigError): + QoSConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(QoSConfigError): + QoSConfig(**{'name': 'foo'}) + + def test_invalid_consumer(self): + with self.assertRaises(QoSConfigError): + QoSConfig(name='foo', consumer='bar') + + def test_config_with_invalid_consumer(self): + with self.assertRaises(QoSConfigError): + QoSConfig(**{'name': 'foo', 'consumer': 'bar'}) + + def test_name_consumer(self): + settings = QoSConfig(name='foo', consumer=Consumer.front_end) + + self.assertEqual('foo', settings.name) + self.assertEqual(Consumer.front_end, settings.consumer) + self.assertEqual(dict(), settings.specs) + + def test_name_consumer_front_end_strings(self): + settings = QoSConfig(name='foo', consumer='front-end') + + self.assertEqual('foo', settings.name) + self.assertEqual(Consumer.front_end, settings.consumer) + self.assertEqual(dict(), settings.specs) + + def test_name_consumer_back_end_strings(self): + settings = QoSConfig(name='foo', consumer='back-end') + + self.assertEqual('foo', settings.name) + self.assertEqual(Consumer.back_end, settings.consumer) + self.assertEqual(dict(), settings.specs) + + def test_name_consumer_both_strings(self): + settings = QoSConfig(name='foo', consumer='both') + + self.assertEqual('foo', settings.name) + self.assertEqual(Consumer.both, settings.consumer) + self.assertEqual(dict(), settings.specs) + + def test_all(self): + specs = {'spec1': 'val1', 'spec2': 'val2'} + settings = QoSConfig(name='foo', consumer=Consumer.both, specs=specs) + + self.assertEqual('foo', settings.name) + self.assertEqual(Consumer.both, settings.consumer) + self.assertEqual(specs, settings.specs) + + def test_config_all(self): + settings = QoSConfig( + **{'name': 'foo', 'consumer': 'both', 'specs': {'spec1': 'val1'}}) + + self.assertEqual('foo', settings.name) + self.assertEqual(Consumer.both, settings.consumer) + self.assertEqual({'spec1': 'val1'}, settings.specs) diff --git a/snaps/config/tests/router_tests.py b/snaps/config/tests/router_tests.py new file mode 100644 index 0000000..2c8f91f --- /dev/null +++ b/snaps/config/tests/router_tests.py @@ -0,0 +1,98 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.network import PortConfig +from snaps.config.router import RouterConfig, RouterConfigError + + +class RouterConfigUnitTests(unittest.TestCase): + """ + Class for testing the RouterConfig class + """ + + def test_no_params(self): + with self.assertRaises(RouterConfigError): + RouterConfig() + + def test_empty_config(self): + with self.assertRaises(RouterConfigError): + RouterConfig(**dict()) + + def test_name_only(self): + settings = RouterConfig(name='foo') + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.external_gateway) + self.assertTrue(settings.admin_state_up) + self.assertIsNone(settings.enable_snat) + self.assertIsNotNone(settings.internal_subnets) + self.assertTrue(isinstance(settings.internal_subnets, list)) + self.assertEqual(0, len(settings.internal_subnets)) + self.assertIsNotNone(settings.port_settings) + self.assertTrue(isinstance(settings.port_settings, list)) + self.assertEqual(0, len(settings.port_settings)) + + def test_config_with_name_only(self): + settings = RouterConfig(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.external_gateway) + self.assertTrue(settings.admin_state_up) + self.assertIsNone(settings.enable_snat) + self.assertIsNotNone(settings.internal_subnets) + self.assertTrue(isinstance(settings.internal_subnets, list)) + self.assertEqual(0, len(settings.internal_subnets)) + self.assertIsNotNone(settings.port_settings) + self.assertTrue(isinstance(settings.port_settings, list)) + self.assertEqual(0, len(settings.port_settings)) + + def test_all(self): + port_settings = PortConfig(name='foo', network_name='bar') + settings = RouterConfig( + name='foo', project_name='bar', external_gateway='foo_gateway', + admin_state_up=True, enable_snat=False, + internal_subnets=['10.0.0.1/24'], interfaces=[port_settings]) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.project_name) + self.assertEqual('foo_gateway', settings.external_gateway) + self.assertTrue(settings.admin_state_up) + self.assertFalse(settings.enable_snat) + self.assertIsNotNone(settings.internal_subnets) + self.assertTrue(isinstance(settings.internal_subnets, list)) + self.assertEqual(1, len(settings.internal_subnets)) + self.assertEqual(['10.0.0.1/24'], settings.internal_subnets) + self.assertEqual([port_settings], settings.port_settings) + + def test_config_all(self): + settings = RouterConfig( + **{'name': 'foo', 'project_name': 'bar', + 'external_gateway': 'foo_gateway', 'admin_state_up': True, + 'enable_snat': False, 'internal_subnets': ['10.0.0.1/24'], + 'interfaces': + [{'port': {'name': 'foo-port', + 'network_name': 'bar-net'}}]}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.project_name) + self.assertEqual('foo_gateway', settings.external_gateway) + self.assertTrue(settings.admin_state_up) + self.assertFalse(settings.enable_snat) + self.assertIsNotNone(settings.internal_subnets) + self.assertTrue(isinstance(settings.internal_subnets, list)) + self.assertEqual(1, len(settings.internal_subnets)) + self.assertEqual(['10.0.0.1/24'], settings.internal_subnets) + self.assertEqual([PortConfig(**{'name': 'foo-port', + 'network_name': 'bar-net'})], + settings.port_settings) diff --git a/snaps/config/tests/security_group_tests.py b/snaps/config/tests/security_group_tests.py new file mode 100644 index 0000000..8834836 --- /dev/null +++ b/snaps/config/tests/security_group_tests.py @@ -0,0 +1,187 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.security_group import ( + Direction, SecurityGroupConfig, SecurityGroupRuleConfig, + SecurityGroupConfigError, Protocol, Ethertype, + SecurityGroupRuleConfigError) + + +class SecurityGroupRuleConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the SecurityGroupRuleConfig class + """ + + def test_no_params(self): + with self.assertRaises(SecurityGroupRuleConfigError): + SecurityGroupRuleConfig() + + def test_empty_config(self): + with self.assertRaises(SecurityGroupRuleConfigError): + SecurityGroupRuleConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(SecurityGroupRuleConfigError): + SecurityGroupRuleConfig(sec_grp_name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(SecurityGroupRuleConfigError): + SecurityGroupRuleConfig(**{'sec_grp_name': 'foo'}) + + def test_name_and_direction(self): + settings = SecurityGroupRuleConfig(sec_grp_name='foo', + direction=Direction.ingress) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress, settings.direction) + + def test_config_name_and_direction(self): + settings = SecurityGroupRuleConfig( + **{'sec_grp_name': 'foo', 'direction': 'ingress'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress, settings.direction) + + def test_proto_ah_str(self): + settings = SecurityGroupRuleConfig( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 'ah'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress, settings.direction) + self.assertEqual(Protocol.ah, settings.protocol) + + def test_proto_ah_value(self): + settings = SecurityGroupRuleConfig( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 51}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress, settings.direction) + self.assertEqual(Protocol.ah, settings.protocol) + + def test_proto_any(self): + settings = SecurityGroupRuleConfig( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 'any'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress, settings.direction) + self.assertEqual(Protocol.null, settings.protocol) + + def test_proto_null(self): + settings = SecurityGroupRuleConfig( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 'null'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress, settings.direction) + self.assertEqual(Protocol.null, settings.protocol) + + def test_all(self): + settings = SecurityGroupRuleConfig( + sec_grp_name='foo', description='fubar', + direction=Direction.egress, remote_group_id='rgi', + protocol=Protocol.icmp, ethertype=Ethertype.IPv6, port_range_min=1, + port_range_max=2, + remote_ip_prefix='prfx') + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual('fubar', settings.description) + self.assertEqual(Direction.egress, settings.direction) + self.assertEqual('rgi', settings.remote_group_id) + self.assertEqual(Protocol.icmp, settings.protocol) + self.assertEqual(Ethertype.IPv6, settings.ethertype) + self.assertEqual(1, settings.port_range_min) + self.assertEqual(2, settings.port_range_max) + self.assertEqual('prfx', settings.remote_ip_prefix) + + def test_config_all(self): + settings = SecurityGroupRuleConfig( + **{'sec_grp_name': 'foo', + 'description': 'fubar', + 'direction': 'egress', + 'remote_group_id': 'rgi', + 'protocol': 'tcp', + 'ethertype': 'IPv6', + 'port_range_min': 1, + 'port_range_max': 2, + 'remote_ip_prefix': 'prfx'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual('fubar', settings.description) + self.assertEqual(Direction.egress, settings.direction) + self.assertEqual('rgi', settings.remote_group_id) + self.assertEqual(Protocol.tcp, settings.protocol) + self.assertEqual(Ethertype.IPv6, settings.ethertype) + self.assertEqual(1, settings.port_range_min) + self.assertEqual(2, settings.port_range_max) + self.assertEqual('prfx', settings.remote_ip_prefix) + + +class SecurityGroupConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the SecurityGroupConfig class + """ + + def test_no_params(self): + with self.assertRaises(SecurityGroupConfigError): + SecurityGroupConfig() + + def test_empty_config(self): + with self.assertRaises(SecurityGroupConfigError): + SecurityGroupConfig(**dict()) + + def test_name_only(self): + settings = SecurityGroupConfig(name='foo') + self.assertEqual('foo', settings.name) + + def test_config_with_name_only(self): + settings = SecurityGroupConfig(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + + def test_invalid_rule(self): + rule_setting = SecurityGroupRuleConfig( + sec_grp_name='bar', direction=Direction.ingress, + description='test_rule_1') + with self.assertRaises(SecurityGroupConfigError): + SecurityGroupConfig(name='foo', rule_settings=[rule_setting]) + + def test_all(self): + rule_settings = list() + rule_settings.append(SecurityGroupRuleConfig( + sec_grp_name='bar', direction=Direction.egress, + description='test_rule_1')) + rule_settings.append(SecurityGroupRuleConfig( + sec_grp_name='bar', direction=Direction.ingress, + description='test_rule_2')) + settings = SecurityGroupConfig( + name='bar', description='fubar', project_name='foo', + rule_settings=rule_settings) + + self.assertEqual('bar', settings.name) + self.assertEqual('fubar', settings.description) + self.assertEqual('foo', settings.project_name) + self.assertEqual(rule_settings[0], settings.rule_settings[0]) + self.assertEqual(rule_settings[1], settings.rule_settings[1]) + + def test_config_all(self): + settings = SecurityGroupConfig( + **{'name': 'bar', + 'description': 'fubar', + 'project_name': 'foo', + 'rules': [ + {'sec_grp_name': 'bar', 'direction': 'ingress'}]}) + + self.assertEqual('bar', settings.name) + self.assertEqual('fubar', settings.description) + self.assertEqual('foo', settings.project_name) + self.assertEqual(1, len(settings.rule_settings)) + self.assertEqual('bar', settings.rule_settings[0].sec_grp_name) + self.assertEqual(Direction.ingress, + settings.rule_settings[0].direction) diff --git a/snaps/config/tests/stack_tests.py b/snaps/config/tests/stack_tests.py new file mode 100644 index 0000000..773e9c2 --- /dev/null +++ b/snaps/config/tests/stack_tests.py @@ -0,0 +1,117 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +import snaps +from snaps.config.stack import StackConfigError, StackConfig + + +class StackConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the StackConfig class + """ + + def test_no_params(self): + with self.assertRaises(StackConfigError): + StackConfig() + + def test_empty_config(self): + with self.assertRaises(StackConfigError): + StackConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(StackConfigError): + StackConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(StackConfigError): + StackConfig(**{'name': 'foo'}) + + def test_resource_not_list(self): + with self.assertRaises(StackConfigError): + StackConfig(**{'name': 'foo', 'resource_files': 'bar'}) + + def test_config_minimum_template(self): + settings = StackConfig(**{'name': 'stack', 'template': 'foo'}) + self.assertEqual('stack', settings.name) + self.assertEqual('foo', settings.template) + self.assertIsNone(settings.template_path) + self.assertIsNone(settings.resource_files) + self.assertIsNone(settings.env_values) + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, + settings.stack_create_timeout) + + def test_config_minimum_template_path(self): + settings = StackConfig(**{'name': 'stack', 'template_path': 'foo'}) + self.assertEqual('stack', settings.name) + self.assertIsNone(settings.template) + self.assertEqual('foo', settings.template_path) + self.assertIsNone(settings.resource_files) + self.assertIsNone(settings.env_values) + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, + settings.stack_create_timeout) + + def test_minimum_template(self): + settings = StackConfig(name='stack', template='foo') + self.assertEqual('stack', settings.name) + self.assertEqual('foo', settings.template) + self.assertIsNone(settings.template_path) + self.assertIsNone(settings.resource_files) + self.assertIsNone(settings.env_values) + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, + settings.stack_create_timeout) + + def test_minimum_template_path(self): + settings = StackConfig(name='stack', template_path='foo') + self.assertEqual('stack', settings.name) + self.assertEqual('foo', settings.template_path) + self.assertIsNone(settings.template) + self.assertIsNone(settings.resource_files) + self.assertIsNone(settings.env_values) + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, + settings.stack_create_timeout) + + def test_resource(self): + settings = StackConfig( + name='stack', template_path='foo', resource_files=['foo', 'bar']) + self.assertEqual('stack', settings.name) + self.assertEqual('foo', settings.template_path) + self.assertIsNone(settings.template) + self.assertEqual(['foo', 'bar'], settings.resource_files) + self.assertIsNone(settings.env_values) + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, + settings.stack_create_timeout) + + def test_all(self): + env_values = {'foo': 'bar'} + settings = StackConfig( + name='stack', template='bar', template_path='foo', + env_values=env_values, stack_create_timeout=999) + self.assertEqual('stack', settings.name) + self.assertEqual('bar', settings.template) + self.assertEqual('foo', settings.template_path) + self.assertEqual(env_values, settings.env_values) + self.assertEqual(999, settings.stack_create_timeout) + + def test_config_all(self): + env_values = {'foo': 'bar'} + settings = StackConfig( + **{'name': 'stack', 'template': 'bar', 'template_path': 'foo', + 'env_values': env_values, 'stack_create_timeout': 999}) + self.assertEqual('stack', settings.name) + self.assertEqual('bar', settings.template) + self.assertEqual('foo', settings.template_path) + self.assertEqual(env_values, settings.env_values) + self.assertEqual(999, settings.stack_create_timeout) diff --git a/snaps/config/tests/user_tests.py b/snaps/config/tests/user_tests.py new file mode 100644 index 0000000..d3d8feb --- /dev/null +++ b/snaps/config/tests/user_tests.py @@ -0,0 +1,84 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.user import UserConfig + + +class UserConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the UserConfig class + """ + + def test_no_params(self): + with self.assertRaises(Exception): + UserConfig() + + def test_empty_config(self): + with self.assertRaises(Exception): + UserConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(Exception): + UserConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(Exception): + UserConfig(**{'name': 'foo'}) + + def test_name_pass_enabled_str(self): + with self.assertRaises(Exception): + UserConfig(name='foo', password='bar', enabled='true') + + def test_config_with_name_pass_enabled_str(self): + with self.assertRaises(Exception): + UserConfig( + **{'name': 'foo', 'password': 'bar', 'enabled': 'true'}) + + def test_name_pass_only(self): + settings = UserConfig(name='foo', password='bar') + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.password) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.email) + self.assertTrue(settings.enabled) + + def test_config_with_name_pass_only(self): + settings = UserConfig(**{'name': 'foo', 'password': 'bar'}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.password) + self.assertIsNone(settings.project_name) + self.assertIsNone(settings.email) + self.assertTrue(settings.enabled) + + def test_all(self): + settings = UserConfig( + name='foo', password='bar', project_name='proj-foo', + email='foo@bar.com', enabled=False) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.password) + self.assertEqual('proj-foo', settings.project_name) + self.assertEqual('foo@bar.com', settings.email) + self.assertFalse(settings.enabled) + + def test_config_all(self): + settings = UserConfig( + **{'name': 'foo', 'password': 'bar', 'project_name': 'proj-foo', + 'email': 'foo@bar.com', 'enabled': False}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.password) + self.assertEqual('proj-foo', settings.project_name) + self.assertEqual('foo@bar.com', settings.email) + self.assertFalse(settings.enabled) diff --git a/snaps/config/tests/vm_inst_tests.py b/snaps/config/tests/vm_inst_tests.py new file mode 100644 index 0000000..d7fb287 --- /dev/null +++ b/snaps/config/tests/vm_inst_tests.py @@ -0,0 +1,246 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.network import PortConfig +from snaps.config.vm_inst import ( + FloatingIpConfig, VmInstanceConfig, FloatingIpConfigError, + VmInstanceConfigError) + + +class VmInstanceConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the VmInstanceConfig class + """ + + def test_no_params(self): + with self.assertRaises(VmInstanceConfigError): + VmInstanceConfig() + + def test_empty_config(self): + with self.assertRaises(VmInstanceConfigError): + VmInstanceConfig(config=dict()) + + def test_name_only(self): + with self.assertRaises(VmInstanceConfigError): + VmInstanceConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(VmInstanceConfigError): + VmInstanceConfig(config={'name': 'foo'}) + + def test_name_flavor_only(self): + with self.assertRaises(VmInstanceConfigError): + VmInstanceConfig(name='foo', flavor='bar') + + def test_config_with_name_flavor_only(self): + with self.assertRaises(VmInstanceConfigError): + VmInstanceConfig(config={'name': 'foo', 'flavor': 'bar'}) + + def test_name_flavor_port_only(self): + port_settings = PortConfig(name='foo-port', network_name='bar-net') + settings = VmInstanceConfig(name='foo', flavor='bar', + port_settings=[port_settings]) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.flavor) + self.assertEqual(1, len(settings.port_settings)) + self.assertEqual('foo-port', settings.port_settings[0].name) + self.assertEqual('bar-net', settings.port_settings[0].network_name) + self.assertEqual(0, len(settings.security_group_names)) + self.assertEqual(0, len(settings.floating_ip_settings)) + self.assertIsNone(settings.sudo_user) + self.assertEqual(900, settings.vm_boot_timeout) + self.assertEqual(300, settings.vm_delete_timeout) + self.assertEqual(180, settings.ssh_connect_timeout) + self.assertEqual(300, settings.cloud_init_timeout) + self.assertIsNone(settings.availability_zone) + self.assertIsNone(settings.volume_names) + + def test_config_with_name_flavor_port_only(self): + port_settings = PortConfig(name='foo-port', network_name='bar-net') + settings = VmInstanceConfig( + **{'name': 'foo', 'flavor': 'bar', 'ports': [port_settings]}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.flavor) + self.assertEqual(1, len(settings.port_settings)) + self.assertEqual('foo-port', settings.port_settings[0].name) + self.assertEqual('bar-net', settings.port_settings[0].network_name) + self.assertEqual(0, len(settings.security_group_names)) + self.assertEqual(0, len(settings.floating_ip_settings)) + self.assertIsNone(settings.sudo_user) + self.assertEqual(900, settings.vm_boot_timeout) + self.assertEqual(300, settings.vm_delete_timeout) + self.assertEqual(180, settings.ssh_connect_timeout) + self.assertEqual(300, settings.cloud_init_timeout) + self.assertIsNone(settings.availability_zone) + self.assertIsNone(settings.volume_names) + + def test_all(self): + port_settings = PortConfig(name='foo-port', network_name='bar-net') + fip_settings = FloatingIpConfig(name='foo-fip', port_name='bar-port', + router_name='foo-bar-router') + + settings = VmInstanceConfig( + name='foo', flavor='bar', port_settings=[port_settings], + security_group_names=['sec_grp_1'], + floating_ip_settings=[fip_settings], sudo_user='joe', + vm_boot_timeout=999, vm_delete_timeout=333, + ssh_connect_timeout=111, cloud_init_timeout=998, + availability_zone='server name', volume_names=['vol1']) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.flavor) + self.assertEqual(1, len(settings.port_settings)) + self.assertEqual('foo-port', settings.port_settings[0].name) + self.assertEqual('bar-net', settings.port_settings[0].network_name) + self.assertEqual(1, len(settings.security_group_names)) + self.assertEqual('sec_grp_1', settings.security_group_names[0]) + self.assertEqual(1, len(settings.floating_ip_settings)) + self.assertEqual('foo-fip', settings.floating_ip_settings[0].name) + self.assertEqual('bar-port', + settings.floating_ip_settings[0].port_name) + self.assertEqual('foo-bar-router', + settings.floating_ip_settings[0].router_name) + self.assertEqual('joe', settings.sudo_user) + self.assertEqual(999, settings.vm_boot_timeout) + self.assertEqual(333, settings.vm_delete_timeout) + self.assertEqual(111, settings.ssh_connect_timeout) + self.assertEqual(998, settings.cloud_init_timeout) + self.assertEqual('server name', settings.availability_zone) + self.assertEqual('vol1', settings.volume_names[0]) + + def test_config_all(self): + port_settings = PortConfig(name='foo-port', network_name='bar-net') + fip_settings = FloatingIpConfig(name='foo-fip', port_name='bar-port', + router_name='foo-bar-router') + + settings = VmInstanceConfig( + **{'name': 'foo', 'flavor': 'bar', 'ports': [port_settings], + 'security_group_names': ['sec_grp_1'], + 'floating_ips': [fip_settings], 'sudo_user': 'joe', + 'vm_boot_timeout': 999, 'vm_delete_timeout': 333, + 'ssh_connect_timeout': 111, 'cloud_init_timeout': 998, + 'availability_zone': 'server name', 'volume_names': ['vol2']}) + self.assertEqual('foo', settings.name) + self.assertEqual('bar', settings.flavor) + self.assertEqual(1, len(settings.port_settings)) + self.assertEqual('foo-port', settings.port_settings[0].name) + self.assertEqual('bar-net', settings.port_settings[0].network_name) + self.assertEqual(1, len(settings.security_group_names)) + self.assertEqual(1, len(settings.floating_ip_settings)) + self.assertEqual('foo-fip', settings.floating_ip_settings[0].name) + self.assertEqual('bar-port', + settings.floating_ip_settings[0].port_name) + self.assertEqual('foo-bar-router', + settings.floating_ip_settings[0].router_name) + self.assertEqual('joe', settings.sudo_user) + self.assertEqual(999, settings.vm_boot_timeout) + self.assertEqual(333, settings.vm_delete_timeout) + self.assertEqual(111, settings.ssh_connect_timeout) + self.assertEqual(998, settings.cloud_init_timeout) + self.assertEqual('server name', settings.availability_zone) + self.assertEqual('vol2', settings.volume_names[0]) + + +class FloatingIpConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the FloatingIpConfig class + """ + + def test_no_params(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig() + + def test_empty_config(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig(**dict()) + + def test_name_only(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig(name='foo') + + def test_config_with_name_only(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig(**{'name': 'foo'}) + + def test_name_port_only(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig(name='foo', port_name='bar') + + def test_config_with_name_port_only(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig(**{'name': 'foo', 'port_name': 'bar'}) + + def test_name_router_only(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig(name='foo', router_name='bar') + + def test_config_with_name_router_only(self): + with self.assertRaises(FloatingIpConfigError): + FloatingIpConfig(**{'name': 'foo', 'router_name': 'bar'}) + + def test_name_port_router_name_only(self): + settings = FloatingIpConfig(name='foo', port_name='foo-port', + router_name='bar-router') + self.assertEqual('foo', settings.name) + self.assertEqual('foo-port', settings.port_name) + self.assertIsNone(settings.port_id) + self.assertEqual('bar-router', settings.router_name) + self.assertIsNone(settings.subnet_name) + self.assertTrue(settings.provisioning) + + def test_name_port_router_id_only(self): + settings = FloatingIpConfig(name='foo', port_id='foo-port', + router_name='bar-router') + self.assertEqual('foo', settings.name) + self.assertEqual('foo-port', settings.port_id) + self.assertIsNone(settings.port_name) + self.assertEqual('bar-router', settings.router_name) + self.assertIsNone(settings.subnet_name) + self.assertTrue(settings.provisioning) + + def test_config_with_name_port_router_only(self): + settings = FloatingIpConfig( + **{'name': 'foo', 'port_name': 'foo-port', + 'router_name': 'bar-router'}) + self.assertEqual('foo', settings.name) + self.assertEqual('foo-port', settings.port_name) + self.assertIsNone(settings.port_id) + self.assertEqual('bar-router', settings.router_name) + self.assertIsNone(settings.subnet_name) + self.assertTrue(settings.provisioning) + + def test_all(self): + settings = FloatingIpConfig(name='foo', port_name='foo-port', + router_name='bar-router', + subnet_name='bar-subnet', + provisioning=False) + self.assertEqual('foo', settings.name) + self.assertEqual('foo-port', settings.port_name) + self.assertIsNone(settings.port_id) + self.assertEqual('bar-router', settings.router_name) + self.assertEqual('bar-subnet', settings.subnet_name) + self.assertFalse(settings.provisioning) + + def test_config_all(self): + settings = FloatingIpConfig( + **{'name': 'foo', 'port_name': 'foo-port', + 'router_name': 'bar-router', 'subnet_name': 'bar-subnet', + 'provisioning': False}) + self.assertEqual('foo', settings.name) + self.assertEqual('foo-port', settings.port_name) + self.assertIsNone(settings.port_id) + self.assertEqual('bar-router', settings.router_name) + self.assertEqual('bar-subnet', settings.subnet_name) + self.assertFalse(settings.provisioning) diff --git a/snaps/config/tests/volume_tests.py b/snaps/config/tests/volume_tests.py new file mode 100644 index 0000000..b4b54bd --- /dev/null +++ b/snaps/config/tests/volume_tests.py @@ -0,0 +1,91 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.volume import VolumeConfigError, VolumeConfig + + +class VolumeConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the VolumeConfig class + """ + + def test_no_params(self): + with self.assertRaises(VolumeConfigError): + VolumeConfig() + + def test_empty_config(self): + with self.assertRaises(VolumeConfigError): + VolumeConfig(**dict()) + + def test_name_only(self): + settings = VolumeConfig(name='foo') + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.description) + self.assertEquals(1, settings.size) + self.assertIsNone(settings.image_name) + self.assertIsNone(settings.type_name) + self.assertIsNone(settings.availability_zone) + self.assertFalse(settings.multi_attach) + + def test_config_with_name_only(self): + settings = VolumeConfig(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.description) + self.assertEquals(1, settings.size) + self.assertIsNone(settings.image_name) + self.assertIsNone(settings.type_name) + self.assertIsNone(settings.availability_zone) + self.assertFalse(settings.multi_attach) + + def test_all_strings(self): + settings = VolumeConfig( + name='foo', description='desc', size='2', image_name='image', + type_name='type', availability_zone='zone1', multi_attach='true') + + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual(2, settings.size) + self.assertEqual('image', settings.image_name) + self.assertEqual('type', settings.type_name) + self.assertEqual('zone1', settings.availability_zone) + self.assertTrue(settings.multi_attach) + + def test_all_correct_type(self): + settings = VolumeConfig( + name='foo', description='desc', size=2, image_name='image', + type_name='bar', availability_zone='zone1', multi_attach=True) + + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual(2, settings.size) + self.assertEqual('image', settings.image_name) + self.assertEqual('bar', settings.type_name) + self.assertEqual('zone1', settings.availability_zone) + self.assertTrue(settings.multi_attach) + + def test_config_all(self): + settings = VolumeConfig( + **{'name': 'foo', 'description': 'desc', 'size': '2', + 'image_name': 'foo', 'type_name': 'bar', + 'availability_zone': 'zone1', 'multi_attach': 'true'}) + + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual(2, settings.size) + self.assertEqual('foo', settings.image_name) + self.assertEqual('bar', settings.type_name) + self.assertEqual('zone1', settings.availability_zone) + self.assertTrue(settings.multi_attach) diff --git a/snaps/config/tests/volume_type_tests.py b/snaps/config/tests/volume_type_tests.py new file mode 100644 index 0000000..7b4fb1b --- /dev/null +++ b/snaps/config/tests/volume_type_tests.py @@ -0,0 +1,91 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.volume_type import ( + VolumeTypeConfig, VolumeTypeEncryptionConfig, ControlLocation, + VolumeTypeConfigError) + + +class VolumeTypeConfigUnitTests(unittest.TestCase): + """ + Tests the construction of the VolumeTypeConfig class + """ + + def test_no_params(self): + with self.assertRaises(VolumeTypeConfigError): + VolumeTypeConfig() + + def test_empty_config(self): + with self.assertRaises(VolumeTypeConfigError): + VolumeTypeConfig(**dict()) + + def test_name_only(self): + settings = VolumeTypeConfig(name='foo') + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.description) + self.assertIsNone(settings.qos_spec_name) + self.assertIsNone(settings.encryption) + self.assertFalse(settings.public) + + def test_config_with_name_only(self): + settings = VolumeTypeConfig(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.description) + self.assertIsNone(settings.qos_spec_name) + self.assertIsNone(settings.encryption) + self.assertFalse(settings.public) + + def test_all(self): + encryption_settings = VolumeTypeEncryptionConfig( + name='foo', provider_class='bar', + control_location=ControlLocation.back_end) + settings = VolumeTypeConfig( + name='foo', description='desc', encryption=encryption_settings, + qos_spec_name='spec_name', public=True) + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual('spec_name', settings.qos_spec_name) + self.assertEqual(encryption_settings, settings.encryption) + self.assertTrue(True, settings.public) + + def test_all_string(self): + encryption_settings = { + 'name': 'foo', 'provider_class': 'bar', + 'control_location': 'back-end'} + settings = VolumeTypeConfig( + name='foo', description='desc', encryption=encryption_settings, + qos_spec_name='spec_name', public='true') + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual('spec_name', settings.qos_spec_name) + self.assertEqual(VolumeTypeEncryptionConfig(**encryption_settings), + settings.encryption) + self.assertTrue(settings.public) + + def test_config_all(self): + encryption_settings = { + 'name': 'foo', 'provider_class': 'bar', + 'control_location': 'back-end'} + settings = VolumeTypeConfig( + **{'name': 'foo', 'description': 'desc', + 'encryption': encryption_settings, + 'qos_spec_name': 'spec_name', 'public': 'false'}) + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual('spec_name', settings.qos_spec_name) + self.assertEqual(VolumeTypeEncryptionConfig(**encryption_settings), + settings.encryption) + self.assertFalse(settings.public) diff --git a/snaps/config/user.py b/snaps/config/user.py new file mode 100644 index 0000000..fcc8fac --- /dev/null +++ b/snaps/config/user.py @@ -0,0 +1,59 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 UserConfig(object): + """ + Class for holding user configurations + """ + def __init__(self, **kwargs): + + """ + Constructor + :param name: the user's name (required) + :param password: the user's password (required) + :param project_name: the user's primary project name (optional) + :param domain_name: the user's domain name (default='Default'). For v3 + APIs + :param email: the user's email address (optional) + :param enabled: denotes whether or not the user is enabled + (default True) + :param roles: dict where key is the role's name and value is the name + of the project to associate with the role (optional) + """ + + self.name = kwargs.get('name') + self.password = kwargs.get('password') + self.project_name = kwargs.get('project_name') + self.email = kwargs.get('email') + self.domain_name = kwargs.get('domain_name', 'Default') + self.enabled = kwargs.get('enabled', True) + self.roles = kwargs.get('roles', dict()) + + if not self.name or not self.password: + raise UserConfigException( + 'The attributes name and password are required for ' + 'UserConfig') + + if not isinstance(self.enabled, bool): + raise UserConfigException( + 'The attribute enabled must be of type boolean') + + +class UserConfigException(Exception): + """ + Raised when there is a problem with the values set in the UserConfig + class + """ diff --git a/snaps/config/vm_inst.py b/snaps/config/vm_inst.py new file mode 100644 index 0000000..6a63e33 --- /dev/null +++ b/snaps/config/vm_inst.py @@ -0,0 +1,166 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from snaps.config.network import PortConfig + + +class VmInstanceConfig(object): + """ + Class responsible for holding configuration setting for a VM Instance + """ + + def __init__(self, **kwargs): + """ + Constructor + :param name: the name of the VM + :param flavor: the VM's flavor name + :param port_settings: the port configuration settings (required) + :param security_group_names: a set of names of the security groups to + add to the VM + :param floating_ip_settings: the floating IP configuration settings + :param sudo_user: the sudo user of the VM that will override the + instance_settings.image_user when trying to + connect to the VM + :param vm_boot_timeout: the amount of time a thread will wait + for an instance to boot + :param vm_delete_timeout: the amount of time a thread will wait + for an instance to be deleted + :param ssh_connect_timeout: the amount of time a thread will wait + to obtain an SSH connection to a VM + :param cloud_init_timeout: the amount of time a thread will wait for + cloud-init to complete + :param availability_zone: the name of the compute server on which to + deploy the VM (optional) + :param volume_names: a list of the names of the volume to attach + (optional) + :param userdata: the string contents of any optional cloud-init script + to execute after the VM has been activated. + This value may also contain a dict who's key value + must contain the key 'cloud-init_file' which denotes + the location of some file containing the cloud-init + script + """ + self.name = kwargs.get('name') + self.flavor = kwargs.get('flavor') + self.sudo_user = kwargs.get('sudo_user') + self.userdata = kwargs.get('userdata') + + self.port_settings = list() + port_settings = kwargs.get('ports') + if not port_settings: + port_settings = kwargs.get('port_settings') + if port_settings: + for port_setting in port_settings: + if isinstance(port_setting, dict): + self.port_settings.append(PortConfig(**port_setting)) + elif isinstance(port_setting, PortConfig): + self.port_settings.append(port_setting) + + if kwargs.get('security_group_names'): + if isinstance(kwargs['security_group_names'], list): + self.security_group_names = kwargs['security_group_names'] + elif isinstance(kwargs['security_group_names'], set): + self.security_group_names = kwargs['security_group_names'] + elif isinstance(kwargs['security_group_names'], str): + self.security_group_names = [kwargs['security_group_names']] + else: + raise VmInstanceConfigError( + 'Invalid data type for security_group_names attribute') + else: + self.security_group_names = set() + + self.floating_ip_settings = list() + floating_ip_settings = kwargs.get('floating_ips') + if not floating_ip_settings: + floating_ip_settings = kwargs.get('floating_ip_settings') + if floating_ip_settings: + for floating_ip_config in floating_ip_settings: + if isinstance(floating_ip_config, FloatingIpConfig): + self.floating_ip_settings.append(floating_ip_config) + else: + self.floating_ip_settings.append(FloatingIpConfig( + **floating_ip_config['floating_ip'])) + + self.vm_boot_timeout = kwargs.get('vm_boot_timeout', 900) + self.vm_delete_timeout = kwargs.get('vm_delete_timeout', 300) + self.ssh_connect_timeout = kwargs.get('ssh_connect_timeout', 180) + self.cloud_init_timeout = kwargs.get('cloud_init_timeout', 300) + self.availability_zone = kwargs.get('availability_zone') + self.volume_names = kwargs.get('volume_names') + + if self.volume_names and not isinstance(self.volume_names, list): + raise VmInstanceConfigError('volume_names must be a list') + + if not self.name or not self.flavor: + raise VmInstanceConfigError( + 'Instance configuration requires the attributes: name, flavor') + + if len(self.port_settings) == 0: + raise VmInstanceConfigError( + 'Instance configuration requires port settings (aka. NICS)') + + +class FloatingIpConfig(object): + """ + Class responsible for holding configuration settings for a floating IP + """ + + def __init__(self, **kwargs): + """ + Constructor + :param name: the name of the floating IP + :param port_name: the name of the router to the external network + :param router_name: the name of the router to the external network + :param subnet_name: the name of the subnet on which to attach the + floating IP + :param provisioning: when true, this floating IP can be used for + provisioning + + TODO - provisioning flag is a hack as I have only observed a single + Floating IPs that actually works on an instance. Multiple floating IPs + placed on different subnets from the same port are especially + troublesome as you cannot predict which one will actually connect. + For now, it is recommended not to setup multiple floating IPs on an + instance unless absolutely necessary. + """ + self.name = kwargs.get('name') + self.port_name = kwargs.get('port_name') + self.port_id = kwargs.get('port_id') + self.router_name = kwargs.get('router_name') + self.subnet_name = kwargs.get('subnet_name') + if kwargs.get('provisioning') is not None: + self.provisioning = kwargs['provisioning'] + else: + self.provisioning = True + + # if not self.name or not self.port_name or not self.router_name: + if not self.name or not self.router_name: + raise FloatingIpConfigError( + 'The attributes name, port_name and router_name are required') + + if not self.port_name and not self.port_id: + raise FloatingIpConfigError( + 'The attributes port_name or port_id are required') + + +class VmInstanceConfigError(Exception): + """ + Exception to be thrown when an VM instance settings are incorrect + """ + + +class FloatingIpConfigError(Exception): + """ + Exception to be thrown when an VM instance settings are incorrect + """ diff --git a/snaps/config/volume.py b/snaps/config/volume.py new file mode 100644 index 0000000..a31e8f5 --- /dev/null +++ b/snaps/config/volume.py @@ -0,0 +1,56 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from neutronclient.common.utils import str2bool + + +class VolumeConfig(object): + def __init__(self, **kwargs): + """ + Constructor + :param name: the volume's name (required) + :param description: the volume's name (optional) + :param size: the volume's size in GB (default 1) + :param image_name: when a glance image is used for the image source + (optional) + :param type_name: the associated volume's type name (optional) + :param availability_zone: the name of the compute server on which to + deploy the volume (optional) + :param multi_attach: when true, volume can be attached to more than one + server (default False) + """ + + self.name = kwargs.get('name') + self.description = kwargs.get('description') + self.size = int(kwargs.get('size', 1)) + self.image_name = kwargs.get('image_name') + self.type_name = kwargs.get('type_name') + self.availability_zone = kwargs.get('availability_zone') + + if kwargs.get('multi_attach'): + self.multi_attach = str2bool(str(kwargs.get('multi_attach'))) + else: + self.multi_attach = False + + if not self.name: + raise VolumeConfigError("The attribute name is required") + + +class VolumeConfigError(Exception): + """ + Exception to be thrown when an volume settings are incorrect + """ + + def __init__(self, message): + Exception.__init__(self, message) diff --git a/snaps/config/volume_type.py b/snaps/config/volume_type.py new file mode 100644 index 0000000..35ca1d4 --- /dev/null +++ b/snaps/config/volume_type.py @@ -0,0 +1,146 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import enum +from neutronclient.common.utils import str2bool + + +class VolumeTypeConfig(object): + def __init__(self, **kwargs): + """ + Constructor + :param name: the volume's name (required) + :param description: the volume's name (optional) + :param encryption: VolumeTypeEncryptionConfig (optional) + :param qos_spec_name: name of the QoS Spec to associate (optional) + :param public: volume visibility where True denotes global + (default - False) + + TODO - Implement project_access parameter that will associate this + VolumeType to a list of project names + """ + + self.name = kwargs.get('name') + self.description = kwargs.get('description') + self.qos_spec_name = kwargs.get('qos_spec_name') + + if 'encryption' in kwargs: + if isinstance(kwargs['encryption'], dict): + self.encryption = VolumeTypeEncryptionConfig( + **kwargs['encryption']) + elif isinstance(kwargs['encryption'], + VolumeTypeEncryptionConfig): + self.encryption = kwargs['encryption'] + else: + self.encryption = None + + if 'public' in kwargs: + if isinstance(kwargs['public'], str): + self.public = str2bool(kwargs['public']) + else: + self.public = kwargs['public'] + else: + self.public = False + + if not self.name: + raise VolumeTypeConfigError("The attribute name is required") + + def __eq__(self, other): + return (self.name == other.name + and self.description == other.description + and self.qos_spec_name == other.qos_spec_name + and self.encryption == other.encryption + and self.public == other.public) + + +class ControlLocation(enum.Enum): + """ + QoS Specification consumer types + """ + front_end = 'front-end' + back_end = 'back-end' + + +class VolumeTypeEncryptionConfig(object): + def __init__(self, **kwargs): + """ + Constructor + :param name: the volume's name (required) + :param provider_class: the volume's provider class (e.g. LuksEncryptor) + :param control_location: the notional service where encryption is + performed (e.g., front-end=Nova). The default + value is 'front-end.' + :param cipher: the encryption algorithm/mode to use + (e.g., aes-xts-plain64). If the field is left empty, + the provider default will be used + :param key_size: the size of the encryption key, in bits + (e.g., 128, 256). If the field is left empty, the + provider default will be used + """ + + self.name = kwargs.get('name') + self.provider_class = kwargs.get('provider_class') + self.control_location = kwargs.get('control_location') + if kwargs.get('control_location'): + self.control_location = map_control_location( + kwargs['control_location']) + else: + self.control_location = None + + self.cipher = kwargs.get('cipher') + self.key_size = kwargs.get('key_size') + + if (not self.name or not self.provider_class + or not self.control_location): + raise VolumeTypeConfigError( + 'The attributes name, provider_class, and control_location ' + 'are required') + + def __eq__(self, other): + return (self.name == other.name + and self.provider_class == other.provider_class + and self.control_location == other.control_location + and self.cipher == other.cipher + and self.key_size == other.key_size) + + +def map_control_location(control_location): + """ + Takes a the protocol value maps it to the Consumer enum. When None return + None + :param control_location: the value to map to the Enum + :return: a ControlLocation enum object + :raise: Exception if control_location parameter is invalid + """ + if not control_location: + return None + elif isinstance(control_location, ControlLocation): + return control_location + else: + proto_str = str(control_location) + if proto_str == 'front-end': + return ControlLocation.front_end + elif proto_str == 'back-end': + return ControlLocation.back_end + else: + raise VolumeTypeConfigError('Invalid Consumer - ' + proto_str) + + +class VolumeTypeConfigError(Exception): + """ + Exception to be thrown when an volume settings are incorrect + """ + + def __init__(self, message): + Exception.__init__(self, message) diff --git a/snaps/domain/cluster_template.py b/snaps/domain/cluster_template.py new file mode 100644 index 0000000..83892b2 --- /dev/null +++ b/snaps/domain/cluster_template.py @@ -0,0 +1,175 @@ +# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 ClusterTemplate(object): + """ + Class for OpenStack cluster template domain object + """ + + def __init__(self, **kwargs): + """ + Constructor + :param id: the cluster template's UUID + :param name: the cluster template's name + :param image: name or ID of the base image in Glance used to boot the + cluster's servers. The image must have the attribute + 'os-distro' defined as appropriate for the cluster + driver + :param keypair: name or ID of the keypair to gain cluster machine + access + :param network_driver: The name of a network driver for providing the + networks for the containers. Note that this is + different and separate from the Neutron network + for the bay/cluster. The operation and + networking model are specific to the particular + driver + :param external_net: name or IDof the external Neutron network to + provide connectivity to the cluster + :param floating_ip_enabled: Whether enable or not using the floating IP + of cloud provider. Some cloud providers + used floating IP, some used public IP, + thus Magnum provide this option for + specifying the choice of using floating IP + :param docker_volume_size: The size in GB for the local storage on each + server for the Docker daemon to cache the + images and host the containers. Cinder + volumes provide the storage. The default is + 25 GB. For the devicemapper storage driver, + the minimum value is 3GB. For the overlay + storage driver, the minimum value is 1GB. + :param server_type: server type string + :param flavor: name or ID of the nova flavor for booting the node + servers + :param master_flavor: name or ID of the nova flavor of the master node + for this cluster + :param coe: ContainerOrchestrationEngine enum instance + :param fixed_net: name of a Neutron network to provide connectivity + to the internal network for the cluster + :param fixed_subnet: Fixed subnet that are using to allocate network + address for nodes in bay/cluster + :param registry_enabled: Docker images by default are pulled from the + public Docker registry, but in some cases, + users may want to use a private registry. + This option provides an alternative registry + based on the Registry V2: Magnum will create a + local registry in the bay/cluster backed by + swift to host the images + :param insecure_registry: The URL pointing to the user's own private + insecure docker registry to deploy and run + docker containers + :param docker_storage_driver: DockerStorageDriver enum instance to + manage storage for the images and + container's writable layer + :param dns_nameserver: The DNS nameserver for the servers and + containers in the bay/cluster to use. + This is configured in the private Neutron + network for the bay/cluster. + :param public: denotes whether or not the cluster type is public + :param tls_disabled: denotes whether or not TLS should be enabled + :param http_proxy: host:port for a proxy to use when direct HTTP + access from the servers to sites on the external + internet is blocked + :param https_proxy: host:port for a proxy to use when direct HTTPS + access from the servers to sites on the external + internet is blocked + :param no_proxy: comma separated list of IPs that should not be + redirected through the proxy + :param volume_driver: The name of a volume driver for managing the + persistent storage for the containers. The + functionality supported are specific to the + driver + :param master_lb_enabled: Since multiple masters may exist in a + bay/cluster, a Neutron load balancer is + created to provide the API endpoint for the + bay/cluster and to direct requests to the + masters. In some cases, such as when the + LBaaS service is not available, this option + can be set to false to create a bay/cluster + without the load balancer. In this case, one + of the masters will serve as the API endpoint + :param labels: Arbitrary labels in the form of a dict. The accepted + keys and valid values are defined in the bay/cluster + drivers. They are used as a way to pass additional + parameters that are specific to a bay/cluster driver. + """ + self.id = kwargs.get('id') + self.name = kwargs.get('name') + self.image = kwargs.get('image') + self.keypair = kwargs.get('keypair') + self.network_driver = kwargs.get('network_driver') + self.external_net = kwargs.get('external_net') + self.floating_ip_enabled = kwargs.get('floating_ip_enabled') + self.docker_volume_size = int(kwargs.get('docker_volume_size', 3)) + self.server_type = kwargs.get('server_type') + self.flavor = kwargs.get('flavor') + self.master_flavor = kwargs.get('master_flavor') + self.coe = kwargs.get('coe') + self.fixed_net = kwargs.get('fixed_net') + self.fixed_subnet = kwargs.get('fixed_subnet') + self.registry_enabled = kwargs.get('registry_enabled') + self.insecure_registry = kwargs.get('insecure_registry') + self.docker_storage_driver = kwargs.get('docker_storage_driver') + self.dns_nameserver = kwargs.get('dns_nameserver') + self.public = kwargs.get('public', False) + self.tls_disabled = kwargs.get('tls_disabled') + self.http_proxy = kwargs.get('http_proxy') + self.https_proxy = kwargs.get('https_proxy') + self.no_proxy = kwargs.get('no_proxy') + self.volume_driver = kwargs.get('volume_driver') + self.master_lb_enabled = kwargs.get('master_lb_enabled', True) + self.labels = kwargs.get('labels') + + def __eq__(self, other): + labels_eq = False + if (self.labels and isinstance(self.labels, dict) + and len(self.labels) == 0): + if (other.labels and isinstance(other.labels, dict) + and len(other.labels) == 0): + labels_eq = True + elif not self.labels: + if (not other.labels or + (isinstance(other.labels, dict) + and len(other.labels) == 0)): + labels_eq = True + else: + labels_eq = self.labels == other.labels + + return (self.name == other.name + and self.id == other.id + and self.image == other.image + and self.keypair == other.keypair + and self.network_driver == other.network_driver + and self.external_net == other.external_net + and self.floating_ip_enabled == other.floating_ip_enabled + and self.docker_volume_size == other.docker_volume_size + and self.server_type == other.server_type + and self.flavor == other.flavor + and self.master_flavor == other.master_flavor + and self.coe == other.coe + and self.fixed_net == other.fixed_net + and self.fixed_subnet == other.fixed_subnet + and self.registry_enabled == other.registry_enabled + and self.insecure_registry == other.insecure_registry + and self.docker_storage_driver == other.docker_storage_driver + and self.dns_nameserver == other.dns_nameserver + and self.public == other.public + and self.tls_disabled == other.tls_disabled + and self.http_proxy == other.http_proxy + and self.https_proxy == other.https_proxy + and self.no_proxy == other.no_proxy + and self.volume_driver == other.volume_driver + and self.master_lb_enabled == other.master_lb_enabled + and labels_eq) diff --git a/snaps/domain/flavor.py b/snaps/domain/flavor.py index 035ca64..bf84cf4 100644 --- a/snaps/domain/flavor.py +++ b/snaps/domain/flavor.py @@ -23,7 +23,7 @@ class Flavor: """ Constructor :param name: the flavor's name - :param flavor_id: the flavor's id + :param flavor_id or id: the flavor's id :param ram: the flavor's RAM in MB :param disk: the flavor's disk size in GB :param vcpus: the flavor's number of virtual CPUs @@ -33,11 +33,16 @@ class Flavor: :param is_public: denotes if flavor can be used by other projects """ self.name = kwargs.get('name') - self.id = kwargs.get('id') + self.id = kwargs.get('flavor_id', kwargs.get('id')) self.ram = kwargs.get('ram') self.disk = kwargs.get('disk') self.vcpus = kwargs.get('vcpus') self.ephemeral = kwargs.get('ephemeral') - self.swap = kwargs.get('swap') + + if kwargs.get('swap'): + self.swap = int(kwargs.get('swap')) + else: + self.swap = None + self.rxtx_factor = kwargs.get('rxtx_factor') self.is_public = kwargs.get('is_public') diff --git a/snaps/domain/network.py b/snaps/domain/network.py index 9cc1dd1..2d02966 100644 --- a/snaps/domain/network.py +++ b/snaps/domain/network.py @@ -22,6 +22,13 @@ class Network: def __init__(self, **kwargs): """ Constructor + :param name: the network's name + :param id: the network's ID + :param admin_state_up: T/F - network is up when True + :param shared: T/F - network can be shared amongst other project's + :param external: T/F - network is deemed to be external + :param type: vlan, vxlan, flat, etc. + :param subnets: list of Subnet objects """ self.name = kwargs.get('name') self.id = kwargs.get('id') @@ -29,12 +36,15 @@ class Network: self.shared = kwargs.get('shared') self.external = kwargs.get('router:external', kwargs.get('external')) self.type = kwargs.get('provider:network_type', kwargs.get('type')) + self.subnets = kwargs.get('subnets', list()) def __eq__(self, other): return (self.name == other.name and self.id == other.id and self.admin_state_up == other.admin_state_up and self.shared == other.shared and - self.external == other.external and self.type == other.type) + self.external == other.external and + self.type == other.type and + self.subnets == other.subnets) class Subnet: @@ -45,9 +55,23 @@ class Subnet: def __init__(self, **kwargs): """ Constructor + :param name: the network's name + :param id: the subnet's ID + :param network_id: the network's ID + :param cidr: the CIDR + :param ip_version: the IP version + :param gateway_ip: the IP of the gateway + :param enable_dhcp: T/F if DHCP is enabled + :param dns_nameservers: list of DNS server IPs + :param host_routes: routes as returned in a dict by Neutron + :param ipv6_ra_mode: IPv6 RA Mode + :param ipv6_address_mode: IPv6 Address Mode + :param start: start IP address pool + :param end: end IP address pool """ self.name = kwargs.get('name') self.id = kwargs.get('id') + self.network_id = kwargs.get('network_id') self.cidr = kwargs.get('cidr') self.ip_version = kwargs.get('ip_version') self.gateway_ip = kwargs.get('gateway_ip') @@ -71,6 +95,7 @@ class Subnet: def __eq__(self, other): return (self.name == other.name and self.id == other.id and + self.network_id == other.network_id and self.cidr == other.cidr and self.ip_version == other.ip_version and self.gateway_ip == other.gateway_ip and @@ -134,20 +159,46 @@ class Router: Constructor :param name: the router's name :param id: the router's id + :param status: the router's status + :param tenant_id: the router's project/tenant ID + :param admin_state_up: Router is up when True + :param external_gateway_info: dict() for populating external_network_id + and external_fixed_ips + external_network_id: ID of the external network to route + in dict under key 'external_fixed_ips' + external_fixed_ips: List IP addresses associated with the + external_network_id found in dict under + key 'network_id' + :param port_subnets: list of tuples where #1 is the Port domain object + and #2 is a list of associated Subnet domain + objects """ self.name = kwargs.get('name') self.id = kwargs.get('id') self.status = kwargs.get('status') self.tenant_id = kwargs.get('tenant_id') self.admin_state_up = kwargs.get('admin_state_up') - self.external_gateway_info = kwargs.get('external_gateway_info') + self.port_subnets = kwargs.get('port_subnets') + + if (kwargs.get('external_gateway_info') and + isinstance(kwargs.get('external_gateway_info'), dict) and + kwargs.get('external_gateway_info').get('external_fixed_ips')): + gateway_info = kwargs.get('external_gateway_info') + + self.external_network_id = gateway_info.get('network_id') + self.external_fixed_ips = gateway_info.get('external_fixed_ips') + else: + self.external_fixed_ips = kwargs.get('external_fixed_ips', None) + self.external_network_id = kwargs.get('external_network_id', None) def __eq__(self, other): return (self.name == other.name and self.id == other.id and self.status == other.status and self.tenant_id == other.tenant_id and self.admin_state_up == other.admin_state_up and - self.external_gateway_info == other.external_gateway_info) + self.external_network_id == other.external_network_id and + self.external_fixed_ips == other.external_fixed_ips and + self.port_subnets == other.port_subnets) class InterfaceRouter: @@ -178,15 +229,29 @@ class SecurityGroup: Constructor :param name: the security group's name :param id: the security group's id + :param description: the security group's description + :param project_id: the security group's project_id + :param rules: list of SecurityGroupRule objects associated to this """ self.name = kwargs.get('name') self.id = kwargs.get('id') self.description = kwargs.get('description') self.project_id = kwargs.get('project_id', kwargs.get('tenant_id')) + self.rules = list() + if kwargs.get('rules') and isinstance(kwargs.get('rules'), list): + for rule in kwargs.get('rules'): + if isinstance(rule, SecurityGroupRule): + self.rules.append(rule) + else: + self.rules.append(SecurityGroupRule(**rule)) + def __eq__(self, other): - return (self.name == other.name and self.id == other.id and - self.project_id == other.project_id) + return (self.name == other.name and + self.id == other.id and + self.description == other.description and + self.project_id == other.project_id and + self.rules == other.rules) class SecurityGroupRule: @@ -198,7 +263,7 @@ class SecurityGroupRule: """ Constructor :param id: the security group rule's id - :param sec_grp_id: the ID of the associated security group + :param security_group_id: the ID of the associated security group :param description: the security group rule's description :param direction: the security group rule's direction :param ethertype: the security group rule's ethertype diff --git a/snaps/domain/stack.py b/snaps/domain/stack.py index 543c78b..080ab17 100644 --- a/snaps/domain/stack.py +++ b/snaps/domain/stack.py @@ -37,14 +37,21 @@ class Resource: """ SNAPS domain object for a resource created by a heat template """ - def __init__(self, resource_type, resource_id): + def __init__(self, name, resource_type, resource_id, status, + status_reason): """ Constructor - :param resource_type: the type + :param name: the resource's name + :param resource_type: the resource's type :param resource_id: the ID attached to the resource of the given type + :param status: the resource's status code + :param status_reason: the resource's status code reason """ + self.name = name self.type = resource_type self.id = resource_id + self.status = status + self.status_reason = status_reason class Output: diff --git a/snaps/domain/test/cluster_template_tests.py b/snaps/domain/test/cluster_template_tests.py new file mode 100644 index 0000000..76e5663 --- /dev/null +++ b/snaps/domain/test/cluster_template_tests.py @@ -0,0 +1,109 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +from snaps.config.cluster_template import ( + ContainerOrchestrationEngine, ServerType, DockerStorageDriver) +from snaps.domain.cluster_template import ClusterTemplate + + +class ClusterTemplateUnitTests(unittest.TestCase): + """ + Tests the construction of the ClusterTypeConfig class + """ + def test_all_named(self): + labels = {'foo': 'bar'} + config = ClusterTemplate( + id='tmplt-id', name='foo', image='bar', keypair='keys', + network_driver='driver', external_net='external', + docker_volume_size=99, server_type=ServerType.baremetal.value, + flavor='testFlavor', master_flavor='masterFlavor', + coe=ContainerOrchestrationEngine.kubernetes.value, + fixed_net='fixedNet', fixed_subnet='fixedSubnet', + registry_enabled=False, + docker_storage_driver=DockerStorageDriver.overlay.value, + dns_nameserver='8.8.4.4', public=True, tls=False, + http_proxy='http://foo:8080', https_proxy='https://foo:443', + no_proxy='foo,bar', volume_driver='volDriver', + master_lb_enabled=False, labels=labels) + self.assertIsNotNone(config) + self.assertEqual('tmplt-id', config.id) + self.assertEqual('foo', config.name) + self.assertEqual('bar', config.image) + self.assertEqual('keys', config.keypair) + self.assertEqual('driver', config.network_driver) + self.assertEqual('external', config.external_net) + self.assertEqual(99, config.docker_volume_size) + self.assertEqual(ServerType.baremetal.value, config.server_type) + self.assertEqual('testFlavor', config.flavor) + self.assertEqual('masterFlavor', config.master_flavor) + self.assertEqual(ContainerOrchestrationEngine.kubernetes.value, + config.coe) + self.assertEqual('fixedNet', config.fixed_net) + self.assertEqual('fixedSubnet', config.fixed_subnet) + self.assertFalse(config.registry_enabled) + self.assertEqual(DockerStorageDriver.overlay.value, + config.docker_storage_driver) + self.assertEqual('8.8.4.4', config.dns_nameserver) + self.assertTrue(config.public) + self.assertFalse(config.tls_disabled) + self.assertEqual('http://foo:8080', config.http_proxy) + self.assertEqual('https://foo:443', config.https_proxy) + self.assertEqual('foo,bar', config.no_proxy) + self.assertEqual('volDriver', config.volume_driver) + self.assertFalse(config.master_lb_enabled) + self.assertEqual(labels, config.labels) + + def test_all_config(self): + labels = {'foo': 'bar'} + config = ClusterTemplate(**{ + 'id': 'tmplt-id', 'name': 'foo', 'image': 'bar', 'keypair': 'keys', + 'network_driver': 'driver', 'external_net': 'external', + 'docker_volume_size': '99', 'server_type': 'baremetal', + 'flavor': 'testFlavor', 'master_flavor': 'masterFlavor', + 'coe': 'kubernetes', 'fixed_net': 'fixedNet', + 'fixed_subnet': 'fixedSubnet', 'registry_enabled': False, + 'docker_storage_driver': 'overlay', 'dns_nameserver': '8.8.4.4', + 'public': 'true', 'tls': 'false', 'http_proxy': 'http://foo:8080', + 'https_proxy': 'https://foo:443', 'no_proxy': 'foo,bar', + 'volume_driver': 'volDriver', 'master_lb_enabled': False, + 'labels': labels}) + self.assertIsNotNone(config) + self.assertEqual('tmplt-id', config.id) + self.assertEqual('foo', config.name) + self.assertEqual('bar', config.image) + self.assertEqual('keys', config.keypair) + self.assertEqual('driver', config.network_driver) + self.assertEqual('external', config.external_net) + self.assertEqual(99, config.docker_volume_size) + self.assertEqual(ServerType.baremetal.value, config.server_type) + self.assertEqual('testFlavor', config.flavor) + self.assertEqual('masterFlavor', config.master_flavor) + self.assertEqual(ContainerOrchestrationEngine.kubernetes.value, + config.coe) + self.assertEqual('fixedNet', config.fixed_net) + self.assertEqual('fixedSubnet', config.fixed_subnet) + self.assertFalse(config.registry_enabled) + self.assertEqual(DockerStorageDriver.overlay.value, + config.docker_storage_driver) + self.assertEqual('8.8.4.4', config.dns_nameserver) + self.assertTrue(config.public) + self.assertFalse(config.tls_disabled) + self.assertEqual('http://foo:8080', config.http_proxy) + self.assertEqual('https://foo:443', config.https_proxy) + self.assertEqual('foo,bar', config.no_proxy) + self.assertEqual('volDriver', config.volume_driver) + self.assertFalse(config.master_lb_enabled) + self.assertEqual(labels, config.labels) diff --git a/snaps/domain/test/network_tests.py b/snaps/domain/test/network_tests.py index 24a60c9..3003326 100644 --- a/snaps/domain/test/network_tests.py +++ b/snaps/domain/test/network_tests.py @@ -25,39 +25,48 @@ class NetworkObjectTests(unittest.TestCase): """ def test_construction_kwargs_1(self): + subnet = Subnet( + **{'name': 'foo', 'id': 'bar', 'network_id': 'foo-bar'}) network = Network( **{'name': 'foo', 'id': 'bar', 'provider:network_type': 'flat', 'admin_state_up': False, 'shared': True, - 'router:external': False}) + 'router:external': False, 'subnets': [subnet]}) self.assertEqual('foo', network.name) self.assertEqual('bar', network.id) self.assertEqual('flat', network.type) self.assertFalse(network.admin_state_up) self.assertFalse(network.external) self.assertTrue(network.shared) + self.assertEqual([subnet], network.subnets) def test_construction_kwargs_2(self): + subnet = Subnet( + **{'name': 'foo', 'id': 'bar', 'network_id': 'foo-bar'}) network = Network( **{'name': 'foo', 'id': 'bar', 'type': 'flat', - 'admin_state_up': False, 'shared': True, - 'external': False}) + 'admin_state_up': False, 'shared': True, 'external': False, + 'subnets': [subnet]}) self.assertEqual('foo', network.name) self.assertEqual('bar', network.id) self.assertEqual('flat', network.type) self.assertFalse(network.admin_state_up) self.assertFalse(network.external) self.assertTrue(network.shared) + self.assertEqual([subnet], network.subnets) def test_construction_named(self): + subnet = Subnet( + **{'name': 'foo', 'id': 'bar', 'network_id': 'foo-bar'}) network = Network( name='foo', id='bar', type='flat', admin_state_up=False, - shared=True, external=False) + shared=True, external=False, subnets=[subnet]) self.assertEqual('foo', network.name) self.assertEqual('bar', network.id) self.assertEqual('flat', network.type) self.assertFalse(network.admin_state_up) self.assertFalse(network.external) self.assertTrue(network.shared) + self.assertEqual([subnet], network.subnets) class SubnetObjectTests(unittest.TestCase): @@ -213,7 +222,8 @@ class RouterDomainObjectTests(unittest.TestCase): self.assertEqual('hello', router.status) self.assertEqual('1234', router.tenant_id) self.assertEqual('yes', router.admin_state_up) - self.assertEqual('no', router.external_gateway_info) + self.assertIsNone(router.external_fixed_ips) + self.assertIsNone(router.external_network_id) def test_construction_named(self): router = Router( @@ -224,7 +234,35 @@ class RouterDomainObjectTests(unittest.TestCase): self.assertEqual('hello', router.status) self.assertEqual('1234', router.tenant_id) self.assertEqual('yes', router.admin_state_up) - self.assertEqual('no', router.external_gateway_info) + self.assertIsNone(router.external_fixed_ips) + self.assertIsNone(router.external_network_id) + + def test_ext_gateway_named(self): + router = Router( + external_fixed_ips=['456', '789'], external_network_id='123', + admin_state_up='yes', tenant_id='1234', status='hello', id='id', + name='name') + self.assertEqual('name', router.name) + self.assertEqual('id', router.id) + self.assertEqual('hello', router.status) + self.assertEqual('1234', router.tenant_id) + self.assertEqual('yes', router.admin_state_up) + self.assertEqual(['456', '789'], router.external_fixed_ips) + self.assertEqual('123', router.external_network_id) + + def test_ext_net_ips_named(self): + ext_gateway = {'network_id': '123', + 'external_fixed_ips': ['456', '789']} + router = Router( + external_gateway_info=ext_gateway, admin_state_up='yes', + tenant_id='1234', status='hello', id='id', name='name') + self.assertEqual('name', router.name) + self.assertEqual('id', router.id) + self.assertEqual('hello', router.status) + self.assertEqual('1234', router.tenant_id) + self.assertEqual('yes', router.admin_state_up) + self.assertEqual(['456', '789'], router.external_fixed_ips) + self.assertEqual('123', router.external_network_id) class InterfaceRouterDomainObjectTests(unittest.TestCase): @@ -254,12 +292,33 @@ class SecurityGroupDomainObjectTests(unittest.TestCase): def test_construction_proj_id_kwargs(self): sec_grp = SecurityGroup( **{'name': 'name', 'id': 'id', 'project_id': 'foo', - 'description': 'test desc'}) + 'description': 'test desc', + 'rules': [ + {'id': 'id', 'security_group_id': 'grp_id', + 'description': 'desc', 'direction': 'dir', + 'ethertype': 'eType', 'port_range_min': '10.0.0.100', + 'port_range_max': '10.0.0.200', 'protocol': 'proto', + 'remote_group_id': 'group_id', + 'remote_ip_prefix': 'ip_prefix'} + ]}) self.assertEqual('name', sec_grp.name) self.assertEqual('id', sec_grp.id) self.assertEqual('test desc', sec_grp.description) self.assertEqual('foo', sec_grp.project_id) + self.assertEqual(1, len(sec_grp.rules)) + rule = sec_grp.rules[0] + self.assertEqual('id', rule.id) + self.assertEqual('grp_id', rule.security_group_id) + self.assertEqual('desc', rule.description) + self.assertEqual('dir', rule.direction) + self.assertEqual('eType', rule.ethertype) + self.assertEqual('10.0.0.100', rule.port_range_min) + self.assertEqual('10.0.0.200', rule.port_range_max) + self.assertEqual('proto', rule.protocol) + self.assertEqual('group_id', rule.remote_group_id) + self.assertEqual('ip_prefix', rule.remote_ip_prefix) + def test_construction_tenant_id_kwargs(self): sec_grp = SecurityGroup( **{'name': 'name', 'id': 'id', @@ -268,15 +327,37 @@ class SecurityGroupDomainObjectTests(unittest.TestCase): self.assertEqual('id', sec_grp.id) self.assertEqual('foo', sec_grp.project_id) self.assertIsNone(sec_grp.description) + self.assertEqual(0, len(sec_grp.rules)) def test_construction_named(self): + rules = [SecurityGroupRule( + **{'id': 'id', 'security_group_id': 'grp_id', + 'description': 'desc', 'direction': 'dir', + 'ethertype': 'eType', 'port_range_min': '10.0.0.100', + 'port_range_max': '10.0.0.200', 'protocol': 'proto', + 'remote_group_id': 'group_id', + 'remote_ip_prefix': 'ip_prefix'} + )] sec_grp = SecurityGroup(description='test desc', tenant_id='foo', - id='id', name='name') + id='id', name='name', rules=rules) self.assertEqual('name', sec_grp.name) self.assertEqual('id', sec_grp.id) self.assertEqual('test desc', sec_grp.description) self.assertEqual('foo', sec_grp.project_id) + self.assertEqual(1, len(sec_grp.rules)) + rule = sec_grp.rules[0] + self.assertEqual('id', rule.id) + self.assertEqual('grp_id', rule.security_group_id) + self.assertEqual('desc', rule.description) + self.assertEqual('dir', rule.direction) + self.assertEqual('eType', rule.ethertype) + self.assertEqual('10.0.0.100', rule.port_range_min) + self.assertEqual('10.0.0.200', rule.port_range_max) + self.assertEqual('proto', rule.protocol) + self.assertEqual('group_id', rule.remote_group_id) + self.assertEqual('ip_prefix', rule.remote_ip_prefix) + class SecurityGroupRuleDomainObjectTests(unittest.TestCase): """ diff --git a/snaps/domain/test/stack_tests.py b/snaps/domain/test/stack_tests.py index f816ef8..21e31d2 100644 --- a/snaps/domain/test/stack_tests.py +++ b/snaps/domain/test/stack_tests.py @@ -39,14 +39,22 @@ class ResourceDomainObjectTests(unittest.TestCase): """ def test_construction_positional(self): - resource = Resource('foo', 'bar') + resource = Resource('res_name', 'foo', 'bar', 'status', 'reason') + self.assertEqual('res_name', resource.name) self.assertEqual('foo', resource.type) self.assertEqual('bar', resource.id) + self.assertEqual('status', resource.status) + self.assertEqual('reason', resource.status_reason) def test_construction_named(self): - resource = Resource(resource_id='bar', resource_type='foo') + resource = Resource( + status_reason=None, status=None, resource_id='bar', + resource_type='foo', name='res_name') + self.assertEqual('res_name', resource.name) self.assertEqual('foo', resource.type) self.assertEqual('bar', resource.id) + self.assertIsNone(resource.status) + self.assertIsNone(resource.status_reason) class OutputDomainObjectTests(unittest.TestCase): diff --git a/snaps/domain/test/vm_inst_tests.py b/snaps/domain/test/vm_inst_tests.py index d293373..ad7a9ce 100644 --- a/snaps/domain/test/vm_inst_tests.py +++ b/snaps/domain/test/vm_inst_tests.py @@ -23,26 +23,30 @@ class VmInstDomainObjectTests(unittest.TestCase): """ def test_construction_positional(self): - vm_inst = VmInst('name', 'id', '456', '123', dict(), 'kp-name', list()) + vm_inst = VmInst('name', 'id', '456', '123', list(), 'kp-name', + ['foo', 'bar'], ['123', '456']) self.assertEqual('name', vm_inst.name) self.assertEqual('id', vm_inst.id) self.assertEqual('456', vm_inst.image_id) self.assertEqual('123', vm_inst.flavor_id) - self.assertEqual(dict(), vm_inst.networks) + self.assertEqual(list(), vm_inst.ports) self.assertEqual('kp-name', vm_inst.keypair_name) - self.assertEqual(list(), vm_inst.sec_grp_names) + self.assertEqual(['foo', 'bar'], vm_inst.sec_grp_names) + self.assertEqual(['123', '456'], vm_inst.volume_ids) def test_construction_named(self): - vm_inst = VmInst(sec_grp_names=list(), networks=dict(), inst_id='id', - name='name', flavor_id='123', image_id='456', - keypair_name='kp-name') + vm_inst = VmInst( + volume_ids=['123', '456'], sec_grp_names=['foo', 'bar'], + ports=list(), inst_id='id', name='name', flavor_id='123', + image_id='456', keypair_name='kp-name') self.assertEqual('name', vm_inst.name) self.assertEqual('id', vm_inst.id) self.assertEqual('456', vm_inst.image_id) self.assertEqual('123', vm_inst.flavor_id) - self.assertEqual(dict(), vm_inst.networks) + self.assertEqual(list(), vm_inst.ports) self.assertEqual('kp-name', vm_inst.keypair_name) - self.assertEqual(list(), vm_inst.sec_grp_names) + self.assertEqual(['foo', 'bar'], vm_inst.sec_grp_names) + self.assertEqual(['123', '456'], vm_inst.volume_ids) class FloatingIpDomainObjectTests(unittest.TestCase): diff --git a/snaps/domain/test/volume_tests.py b/snaps/domain/test/volume_tests.py index ec5f7b7..6feadc9 100644 --- a/snaps/domain/test/volume_tests.py +++ b/snaps/domain/test/volume_tests.py @@ -14,7 +14,44 @@ # limitations under the License. import unittest -from snaps.domain.volume import QoSSpec, VolumeType, VolumeTypeEncryption +from snaps.domain.volume import ( + QoSSpec, VolumeType, VolumeTypeEncryption, Volume) + + +class VolumeDomainObjectTests(unittest.TestCase): + """ + Tests the construction of the snaps.domain.volume.Volume class + """ + + def test_construction_positional(self): + volume = Volume('name1', 'id1', 'desc_val1', 2, 'type_val1', + 'avail_zone1', False, [{'attached_at': 'foo'}]) + self.assertEqual('name1', volume.name) + self.assertEqual('id1', volume.id) + self.assertEqual('desc_val1', volume.description) + self.assertEqual(2, volume.size) + self.assertEqual('type_val1', volume.type) + self.assertEqual('avail_zone1', volume.availability_zone) + self.assertFalse(volume.multi_attach) + self.assertIsNotNone(volume.attachments) + self.assertTrue(isinstance(volume.attachments[0], dict)) + self.assertEqual(1, len(volume.attachments)) + + def test_construction_named(self): + volume = Volume(attachments=[{'attached_at': 'foo'}], + multi_attach=True, availability_zone='avail_zone2', + vol_type='type_val2', size=3, description='desc_val2', + volume_id='id2', name='name2') + self.assertEqual('name2', volume.name) + self.assertEqual('id2', volume.id) + self.assertEqual('desc_val2', volume.description) + self.assertEqual(3, volume.size) + self.assertEqual('type_val2', volume.type) + self.assertEqual('avail_zone2', volume.availability_zone) + self.assertTrue(volume.multi_attach) + self.assertIsNotNone(volume.attachments) + self.assertTrue(isinstance(volume.attachments[0], dict)) + self.assertEqual(1, len(volume.attachments)) class VolumeTypeDomainObjectTests(unittest.TestCase): diff --git a/snaps/domain/vm_inst.py b/snaps/domain/vm_inst.py index ca38143..c49b03e 100644 --- a/snaps/domain/vm_inst.py +++ b/snaps/domain/vm_inst.py @@ -19,35 +19,38 @@ class VmInst: SNAPS domain object for Images. Should contain attributes that are shared amongst cloud providers """ - def __init__(self, name, inst_id, image_id, flavor_id, networks, - keypair_name, sec_grp_names): + def __init__(self, name, inst_id, image_id, flavor_id, ports, + keypair_name, sec_grp_names, volume_ids): """ Constructor :param name: the image's name :param inst_id: the instance's id :param image_id: the instance's image id :param flavor_id: the ID used to spawn this instance - :param networks: dict of networks where the key is the network name and - value is a list of associated IPs + :param ports: list of SNAPS-OO Port domain objects associated with this + server instance :param keypair_name: the name of the associated keypair :param sec_grp_names: list of security group names + :param volume_ids: list of attached volume IDs """ self.name = name self.id = inst_id self.image_id = image_id self.flavor_id = flavor_id - self.networks = networks + self.ports = ports self.keypair_name = keypair_name self.sec_grp_names = sec_grp_names + self.volume_ids = volume_ids def __eq__(self, other): return (self.name == other.name and self.id == other.id and self.image_id == other.image_id and self.flavor_id == other.flavor_id and - self.networks == other.networks and + self.ports == other.ports and self.keypair_name == other.keypair_name and - self.sec_grp_names == other.sec_grp_names) + self.sec_grp_names == other.sec_grp_names and + self.volume_ids == other.volume_ids) class FloatingIp: diff --git a/snaps/domain/volume.py b/snaps/domain/volume.py index e82a60a..0042d71 100644 --- a/snaps/domain/volume.py +++ b/snaps/domain/volume.py @@ -14,6 +14,43 @@ # limitations under the License. +class Volume: + """ + SNAPS domain object for Volumes. Should contain attributes that + are shared amongst cloud providers + """ + def __init__(self, name, volume_id, description, size, vol_type, + availability_zone, multi_attach, attachments=list()): + """ + Constructor + :param name: the volume's name + :param volume_id: the volume's id + :param description: the volume's description + :param size: the volume's size in GB + :param vol_type: the volume's type + :param availability_zone: the volume's availability zone + :param multi_attach: When true, volume can be attached to multiple VMs + :param attachments: List of dict objects containing the info on where + this volume is attached + """ + self.name = name + self.id = volume_id + self.description = description + self.size = size + self.type = vol_type + self.availability_zone = availability_zone + self.multi_attach = multi_attach + self.attachments = attachments + + def __eq__(self, other): + return (self.name == other.name and self.id == other.id + and self.description == other.description + and self.size == other.size + and self.type == other.type + and self.availability_zone == other.availability_zone + and self.multi_attach == other.multi_attach) + + class VolumeType: """ SNAPS domain object for Volume Types. Should contain attributes that diff --git a/snaps/openstack/cluster_template.py b/snaps/openstack/cluster_template.py new file mode 100644 index 0000000..c4ba76d --- /dev/null +++ b/snaps/openstack/cluster_template.py @@ -0,0 +1,94 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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 magnumclient.common.apiclient.exceptions import NotFound + +from snaps.openstack.openstack_creator import OpenStackMagnumObject +from snaps.openstack.utils import magnum_utils + +__author__ = 'spisarski' + +logger = logging.getLogger('cluster_template') + + +class OpenStackClusterTemplate(OpenStackMagnumObject): + """ + Class responsible for managing an volume in OpenStack + """ + + def __init__(self, os_creds, cluster_template_config): + """ + Constructor + :param os_creds: The OpenStack connection credentials + :param cluster_template_config: The volume type settings + :return: + """ + super(self.__class__, self).__init__(os_creds) + + self.cluster_template_config = cluster_template_config + self.__cluster_template = None + + def initialize(self): + """ + Loads the existing Volume + :return: The Volume domain object or None + """ + super(self.__class__, self).initialize() + + self.__cluster_template = magnum_utils.get_cluster_template( + self._magnum, template_config=self.cluster_template_config) + + return self.__cluster_template + + def create(self): + """ + Creates the volume in OpenStack if it does not already exist and + returns the domain Volume object + :return: The Volume domain object or None + """ + self.initialize() + + if not self.__cluster_template: + self.__cluster_template = magnum_utils.create_cluster_template( + self._magnum, self.cluster_template_config) + logger.info( + 'Created volume type with name - %s', + self.cluster_template_config.name) + + return self.__cluster_template + + def clean(self): + """ + Cleanse environment of all artifacts + :return: void + """ + if self.__cluster_template: + try: + magnum_utils.delete_cluster_template( + self._magnum, self.__cluster_template.id) + except NotFound: + pass + + self.__cluster_template = None + + def get_cluster_template(self): + """ + Returns the domain Volume object as it was populated when create() was + called + :return: the object + """ + return self.__cluster_template diff --git a/snaps/openstack/create_flavor.py b/snaps/openstack/create_flavor.py index 1479bb0..65b9059 100644 --- a/snaps/openstack/create_flavor.py +++ b/snaps/openstack/create_flavor.py @@ -16,6 +16,7 @@ import logging from novaclient.exceptions import NotFound +from snaps.config.flavor import FlavorConfig from snaps.openstack.openstack_creator import OpenStackComputeObject from snaps.openstack.utils import nova_utils @@ -36,7 +37,7 @@ class OpenStackFlavor(OpenStackComputeObject): """ Constructor :param os_creds: The OpenStack connection credentials - :param flavor_settings: The flavor settings + :param flavor_settings: a FlavorConfig instance :return: """ super(self.__class__, self).__init__(os_creds) @@ -70,8 +71,6 @@ class OpenStackFlavor(OpenStackComputeObject): if self.flavor_settings.metadata: nova_utils.set_flavor_keys(self._nova, self.__flavor, self.flavor_settings.metadata) - else: - logger.info('Did not create flavor due to cleanup mode') return self.__flavor @@ -96,97 +95,13 @@ class OpenStackFlavor(OpenStackComputeObject): return self.__flavor -class FlavorSettings: +class FlavorSettings(FlavorConfig): """ Configuration settings for OpenStack flavor creation """ def __init__(self, **kwargs): - """ - Constructor - :param config: dict() object containing the configuration settings - using the attribute names below as each member's the - key and overrides any of the other parameters. - :param name: the flavor's name (required) - :param flavor_id: the string ID (default 'auto') - :param ram: the required RAM in MB (required) - :param disk: the size of the root disk in GB (required) - :param vcpus: the number of virtual CPUs (required) - :param ephemeral: the size of the ephemeral disk in GB (default 0) - :param swap: the size of the dedicated swap disk in GB (default 0) - :param rxtx_factor: the receive/transmit factor to be set on ports if - backend supports QoS extension (default 1.0) - :param is_public: denotes whether or not the flavor is public - (default True) - :param metadata: freeform dict() for special metadata - """ - self.name = kwargs.get('name') - - if kwargs.get('flavor_id'): - self.flavor_id = kwargs['flavor_id'] - else: - self.flavor_id = 'auto' - - self.ram = kwargs.get('ram') - self.disk = kwargs.get('disk') - self.vcpus = kwargs.get('vcpus') - - if kwargs.get('ephemeral'): - self.ephemeral = kwargs['ephemeral'] - else: - self.ephemeral = 0 - - if kwargs.get('swap'): - self.swap = kwargs['swap'] - else: - self.swap = 0 - - if kwargs.get('rxtx_factor'): - self.rxtx_factor = kwargs['rxtx_factor'] - else: - self.rxtx_factor = 1.0 - - if kwargs.get('is_public') is not None: - self.is_public = kwargs['is_public'] - else: - self.is_public = True - - if kwargs.get('metadata'): - self.metadata = kwargs['metadata'] - else: - self.metadata = None - - if not self.name or not self.ram or not self.disk or not self.vcpus: - raise FlavorSettingsError( - 'The attributes name, ram, disk, and vcpus are required for' - 'FlavorSettings') - - if not isinstance(self.ram, int): - raise FlavorSettingsError('The ram attribute must be a integer') - - if not isinstance(self.disk, int): - raise FlavorSettingsError('The ram attribute must be a integer') - - if not isinstance(self.vcpus, int): - raise FlavorSettingsError('The vcpus attribute must be a integer') - - if self.ephemeral and not isinstance(self.ephemeral, int): - raise FlavorSettingsError( - 'The ephemeral attribute must be an integer') - - if self.swap and not isinstance(self.swap, int): - raise FlavorSettingsError('The swap attribute must be an integer') - - if self.rxtx_factor and not isinstance(self.rxtx_factor, (int, float)): - raise FlavorSettingsError( - 'The is_public attribute must be an integer or float') - - if self.is_public and not isinstance(self.is_public, bool): - raise FlavorSettingsError( - 'The is_public attribute must be a boolean') - - -class FlavorSettingsError(Exception): - """ - Exception to be thrown when an flavor settings are incorrect - """ + from warnings import warn + warn('Use snaps.config.flavor.FlavorConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) diff --git a/snaps/openstack/create_image.py b/snaps/openstack/create_image.py index 3024717..a5520e3 100644 --- a/snaps/openstack/create_image.py +++ b/snaps/openstack/create_image.py @@ -19,6 +19,7 @@ import time from snaps.openstack.openstack_creator import OpenStackCloudObject from snaps.openstack.utils import glance_utils +from snaps.config import image __author__ = 'spisarski' @@ -38,8 +39,7 @@ class OpenStackImage(OpenStackCloudObject): """ Constructor :param os_creds: The OpenStack connection credentials - :param image_settings: The image settings - :return: + :param image_settings: An snaps.config.image.ImageConfig object """ super(self.__class__, self).__init__(os_creds) @@ -57,6 +57,7 @@ class OpenStackImage(OpenStackCloudObject): self.__glance = glance_utils.glance_client(self._os_creds) self.__image = glance_utils.get_image( self.__glance, image_settings=self.image_settings) + if self.__image: logger.info('Found image with name - ' + self.image_settings.name) return self.__image @@ -70,6 +71,7 @@ class OpenStackImage(OpenStackCloudObject): self.__kernel_image = glance_utils.get_image( self.__glance, image_settings=self.image_settings.kernel_image_settings) + if self.image_settings.ramdisk_image_settings: self.__ramdisk_image = glance_utils.get_image( self.__glance, @@ -97,6 +99,7 @@ class OpenStackImage(OpenStackCloudObject): self.__glance, self.image_settings.kernel_image_settings) extra_properties['kernel_id'] = self.__kernel_image.id + if self.image_settings.ramdisk_image_settings: if not self.__ramdisk_image: logger.info( @@ -113,6 +116,7 @@ class OpenStackImage(OpenStackCloudObject): logger.info( 'Created image with name - %s', self.image_settings.name) + if self.__image and self.image_active(block=True): logger.info( 'Image is now active with name - %s', @@ -122,8 +126,6 @@ class OpenStackImage(OpenStackCloudObject): raise ImageCreationError( 'Image was not created or activated in the alloted amount' 'of time') - else: - logger.info('Did not create image due to cleanup mode') return self.__image @@ -132,10 +134,10 @@ class OpenStackImage(OpenStackCloudObject): Cleanse environment of all artifacts :return: void """ - for image in [self.__image, self.__kernel_image, self.__ramdisk_image]: - if image: + for img in [self.__image, self.__kernel_image, self.__ramdisk_image]: + if img: try: - glance_utils.delete_image(self.__glance, image) + glance_utils.delete_image(self.__glance, img) except HTTPNotFound: pass @@ -236,100 +238,14 @@ class OpenStackImage(OpenStackCloudObject): return status == expected_status_code -class ImageSettings: - def __init__(self, **kwargs): - """ - Constructor - :param name: the image's name (required) - :param image_user: the image's default sudo user (required) - :param format or img_format: the image type (required) - :param url or download_url: the image download location (requires url - or img_file) - :param image_file: the image file location (requires url or img_file) - :param extra_properties: dict() object containing extra parameters to - pass when loading the image; - can be ids of kernel and initramfs images for - a 3-part image - :param nic_config_pb_loc: the file location to the Ansible Playbook - that can configure multiple NICs - :param kernel_image_settings: the settings for a kernel image - :param ramdisk_image_settings: the settings for a kernel image - :param exists: When True, an image with the given name must exist - :param public: When True, an image will be created with public - visibility - """ - - self.name = kwargs.get('name') - self.image_user = kwargs.get('image_user') - self.format = kwargs.get('format') - if not self.format: - self.format = kwargs.get('img_format') - - self.url = kwargs.get('url') - if not self.url: - self.url = kwargs.get('download_url') - if self.url == 'None': - self.url = None - - self.image_file = kwargs.get('image_file') - if self.image_file == 'None': - self.image_file = None - - self.extra_properties = kwargs.get('extra_properties') - self.nic_config_pb_loc = kwargs.get('nic_config_pb_loc') - - kernel_image_settings = kwargs.get('kernel_image_settings') - if kernel_image_settings: - if isinstance(kernel_image_settings, dict): - self.kernel_image_settings = ImageSettings( - **kernel_image_settings) - else: - self.kernel_image_settings = kernel_image_settings - else: - self.kernel_image_settings = None - - ramdisk_image_settings = kwargs.get('ramdisk_image_settings') - if ramdisk_image_settings: - if isinstance(ramdisk_image_settings, dict): - self.ramdisk_image_settings = ImageSettings( - **ramdisk_image_settings) - else: - self.ramdisk_image_settings = ramdisk_image_settings - else: - self.ramdisk_image_settings = None - - if 'exists' in kwargs and kwargs['exists'] is True: - self.exists = True - else: - self.exists = False - - if 'public' in kwargs and kwargs['public'] is True: - self.public = True - else: - self.public = False - - if not self.name: - raise ImageSettingsError("The attribute name is required") - - if not (self.url or self.image_file) and not self.exists: - raise ImageSettingsError( - 'URL or image file must be set or image must already exist') - - if not self.image_user: - raise ImageSettingsError('Image user is required') - - if not self.format and not self.exists: - raise ImageSettingsError( - 'Format is required when the image should not already exist') - - -class ImageSettingsError(Exception): +class ImageSettings(image.ImageConfig): """ - Exception to be thrown when an image settings are incorrect + Deprecated, use snaps.config.image.ImageSettings instead """ - - def __init__(self, message): - Exception.__init__(self, message) + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.image.ImageConfig instead', DeprecationWarning) + super(ImageSettings, self).__init__(**kwargs) class ImageCreationError(Exception): diff --git a/snaps/openstack/create_instance.py b/snaps/openstack/create_instance.py index 3d55f42..d91e360 100644 --- a/snaps/openstack/create_instance.py +++ b/snaps/openstack/create_instance.py @@ -15,14 +15,14 @@ import logging import time -from neutronclient.common.exceptions import PortNotFoundClient -from novaclient.exceptions import NotFound +from novaclient.exceptions import NotFound, BadRequest -from snaps.openstack.create_network import PortSettings +from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig from snaps.openstack.openstack_creator import OpenStackComputeObject -from snaps.openstack.utils import glance_utils +from snaps.openstack.utils import glance_utils, cinder_utils, settings_utils from snaps.openstack.utils import neutron_utils from snaps.openstack.utils import nova_utils +from snaps.openstack.utils.nova_utils import RebootType from snaps.provisioning import ansible_utils __author__ = 'spisarski' @@ -89,7 +89,8 @@ class OpenStackVmInstance(OpenStackComputeObject): self.initialize() if len(self.__ports) == 0: - self.__ports = self.__create_ports(self.instance_settings.port_settings) + self.__ports = self.__create_ports( + self.instance_settings.port_settings) if not self.__vm: self.__create_vm(block) @@ -102,7 +103,8 @@ class OpenStackVmInstance(OpenStackComputeObject): within the project """ server = nova_utils.get_server( - self._nova, vm_inst_settings=self.instance_settings) + self._nova, self.__neutron, + vm_inst_settings=self.instance_settings) if server: if server.name == self.instance_settings.name: self.__vm = server @@ -155,6 +157,26 @@ class OpenStackVmInstance(OpenStackComputeObject): ' to VM that did not activate with name - ' + self.instance_settings.name) + if self.instance_settings.volume_names: + for volume_name in self.instance_settings.volume_names: + cinder = cinder_utils.cinder_client(self._os_creds) + volume = cinder_utils.get_volume( + cinder, volume_name=volume_name) + + if volume and self.vm_active(block=True): + timeout = 30 + vm = nova_utils.attach_volume( + self._nova, self.__neutron, self.__vm, volume, timeout) + + if vm: + self.__vm = vm + else: + logger.warn('Volume [%s] not attached within timeout ' + 'of [%s]', volume.name, timeout) + else: + logger.warn('Unable to attach volume named [%s]', + volume_name) + self.__apply_floating_ips() def __apply_floating_ips(self): @@ -167,33 +189,47 @@ class OpenStackVmInstance(OpenStackComputeObject): # Apply floating IPs for floating_ip_setting in self.instance_settings.floating_ip_settings: - port = port_dict.get(floating_ip_setting.port_name) + self.add_floating_ip(floating_ip_setting) - if not port: - raise VmInstanceCreationError( - 'Cannot find port object with name - ' + - floating_ip_setting.port_name) + def add_floating_ip(self, floating_ip_setting): + """ + Adds a floating IP to a running instance + :param floating_ip_setting - the floating IP configuration + :return: the floating ip object + """ + port_dict = dict() + for key, port in self.__ports: + port_dict[key] = port - # Setup Floating IP only if there is a router with an external - # gateway - ext_gateway = self.__ext_gateway_by_router( - floating_ip_setting.router_name) - if ext_gateway: - subnet = neutron_utils.get_subnet( - self.__neutron, - subnet_name=floating_ip_setting.subnet_name) - floating_ip = neutron_utils.create_floating_ip( - self.__neutron, ext_gateway) - self.__floating_ip_dict[floating_ip_setting.name] = floating_ip + # Apply floating IP + port = port_dict.get(floating_ip_setting.port_name) - logger.info( - 'Created floating IP %s via router - %s', floating_ip.ip, - floating_ip_setting.router_name) - self.__add_floating_ip(floating_ip, port, subnet) - else: - raise VmInstanceCreationError( - 'Unable to add floating IP to port, cannot locate router ' - 'with an external gateway ') + if not port: + raise VmInstanceCreationError( + 'Cannot find port object with name - ' + + floating_ip_setting.port_name) + + # Setup Floating IP only if there is a router with an external + # gateway + ext_gateway = self.__ext_gateway_by_router( + floating_ip_setting.router_name) + if ext_gateway: + subnet = neutron_utils.get_subnet( + self.__neutron, + subnet_name=floating_ip_setting.subnet_name) + floating_ip = neutron_utils.create_floating_ip( + self.__neutron, ext_gateway) + self.__floating_ip_dict[floating_ip_setting.name] = floating_ip + + logger.info( + 'Created floating IP %s via router - %s', floating_ip.ip, + floating_ip_setting.router_name) + self.__add_floating_ip(floating_ip, port, subnet) + return floating_ip + else: + raise VmInstanceCreationError( + 'Unable to add floating IP to port, cannot locate router ' + 'with an external gateway ') def __ext_gateway_by_router(self, router_name): """ @@ -204,10 +240,9 @@ class OpenStackVmInstance(OpenStackComputeObject): """ router = neutron_utils.get_router( self.__neutron, router_name=router_name) - if router and router.external_gateway_info: + if router and router.external_network_id: network = neutron_utils.get_network_by_id( - self.__neutron, - router.external_gateway_info['network_id']) + self.__neutron, router.external_network_id) if network: return network.name return None @@ -219,50 +254,58 @@ class OpenStackVmInstance(OpenStackComputeObject): # Cleanup floating IPs for name, floating_ip in self.__floating_ip_dict.items(): - try: - logger.info('Deleting Floating IP - ' + floating_ip.ip) - neutron_utils.delete_floating_ip(self.__neutron, floating_ip) - except Exception as e: - logger.error('Error deleting Floating IP - ' + str(e)) + logger.info('Deleting Floating IP - ' + floating_ip.ip) + neutron_utils.delete_floating_ip(self.__neutron, floating_ip) + self.__floating_ip_dict = dict() # Cleanup ports for name, port in self.__ports: - logger.info('Deleting Port with ID - %S ' + port.id) - try: - neutron_utils.delete_port(self.__neutron, port) - except PortNotFoundClient as e: - logger.warning('Unexpected error deleting port - %s', e) - pass + logger.info('Deleting Port with ID - %s ', port.id) + neutron_utils.delete_port(self.__neutron, port) + self.__ports = list() - # Cleanup VM if self.__vm: + # Detach Volume + for volume_rec in self.__vm.volume_ids: + cinder = cinder_utils.cinder_client(self._os_creds) + volume = cinder_utils.get_volume_by_id( + cinder, volume_rec['id']) + if volume: + vm = nova_utils.detach_volume( + self._nova, self.__neutron, self.__vm, volume, 30) + if vm: + self.__vm = vm + else: + logger.warn( + 'Timeout waiting to detach volume %s', volume.name) + else: + logger.warn('Unable to detach volume with ID - [%s]', + volume_rec['id']) + + # Cleanup VM + logger.info( + 'Deleting VM instance - ' + self.instance_settings.name) + try: - logger.info( - 'Deleting VM instance - ' + self.instance_settings.name) nova_utils.delete_vm_instance(self._nova, self.__vm) - except Exception as e: - logger.error('Error deleting VM - %s', e) + except NotFound as e: + logger.warn('Instance already deleted - %s', e) # Block until instance cannot be found or returns the status of # DELETED logger.info('Checking deletion status') - try: - if self.vm_deleted(block=True): - logger.info( - 'VM has been properly deleted VM with name - %s', - self.instance_settings.name) - self.__vm = None - else: - logger.error( - 'VM not deleted within the timeout period of %s ' - 'seconds', self.instance_settings.vm_delete_timeout) - except Exception as e: + if self.vm_deleted(block=True): + logger.info( + 'VM has been properly deleted VM with name - %s', + self.instance_settings.name) + self.__vm = None + else: logger.error( - 'Unexpected error while checking VM instance status - %s', - e) + 'VM not deleted within the timeout period of %s ' + 'seconds', self.instance_settings.vm_delete_timeout) def __query_ports(self, port_settings): """ @@ -296,7 +339,8 @@ class OpenStackVmInstance(OpenStackComputeObject): port = neutron_utils.get_port( self.__neutron, port_settings=port_setting) if not port: - port = neutron_utils.create_port(self.__neutron, self._os_creds, port_setting) + port = neutron_utils.create_port( + self.__neutron, self._os_creds, port_setting) if port: ports.append((port_setting.name, port)) @@ -332,6 +376,9 @@ class OpenStackVmInstance(OpenStackComputeObject): 'Added floating IP %s to port IP %s on instance %s', floating_ip.ip, ip, self.instance_settings.name) return + except BadRequest as bre: + logger.error('Cannot add floating IP [%s]', bre) + raise except Exception as e: logger.debug( 'Retry adding floating IP to instance. Last attempt ' @@ -359,7 +406,8 @@ class OpenStackVmInstance(OpenStackComputeObject): Returns the latest version of this server object from OpenStack :return: Server object """ - return nova_utils.get_server_object_by_id(self._nova, self.__vm.id) + return nova_utils.get_server_object_by_id( + self._nova, self.__neutron, self.__vm.id) def get_console_output(self): """ @@ -428,25 +476,6 @@ class OpenStackVmInstance(OpenStackComputeObject): """ return nova_utils.get_server_info(self._nova, self.__vm) - def config_nics(self): - """ - Responsible for configuring NICs on RPM systems where the instance has - more than one configured port - :return: the value returned by ansible_utils.apply_ansible_playbook() - """ - if len(self.__ports) > 1 and len(self.__floating_ip_dict) > 0: - if self.vm_active(block=True) and self.vm_ssh_active(block=True): - for key, port in self.__ports: - port_index = self.__ports.index((key, port)) - if port_index > 0: - nic_name = 'eth' + repr(port_index) - retval = self.__config_nic( - nic_name, port, - self.__get_first_provisioning_floating_ip().ip) - logger.info('Configured NIC - %s on VM - %s', - nic_name, self.instance_settings.name) - return retval - def __get_first_provisioning_floating_ip(self): """ Returns the first floating IP tagged with the Floating IP name if @@ -462,30 +491,10 @@ class OpenStackVmInstance(OpenStackComputeObject): for key, fip in self.__floating_ip_dict.items(): return fip - def __config_nic(self, nic_name, port, ip): - """ - Although ports/NICs can contain multiple IPs, this code currently only - supports the first. - - :param nic_name: Name of the interface - :param port: The port information containing the expected IP values. - :param ip: The IP on which to apply the playbook. - :return: the return value from ansible - """ - port_ip = port.ips[0]['ip_address'] - variables = { - 'floating_ip': ip, - 'nic_name': nic_name, - 'nic_ip': port_ip - } - - if self.image_settings.nic_config_pb_loc and self.keypair_settings: - return self.apply_ansible_playbook( - self.image_settings.nic_config_pb_loc, variables) - else: - logger.warning( - 'VM %s cannot self configure NICs eth1++. No playbook or ' - 'keypairs found.', self.instance_settings.name) + # When cannot be found above + if len(self.__floating_ip_dict) > 0: + for key, fip in self.__floating_ip_dict.items(): + return fip def apply_ansible_playbook(self, pb_file_loc, variables=None, fip_name=None): @@ -541,9 +550,13 @@ class OpenStackVmInstance(OpenStackComputeObject): :param poll_interval: The polling interval in seconds :return: T/F """ - return self.__vm_status_check(STATUS_ACTIVE, block, - self.instance_settings.vm_boot_timeout, - poll_interval) + if self.__vm_status_check( + STATUS_ACTIVE, block, self.instance_settings.vm_boot_timeout, + poll_interval): + self.__vm = nova_utils.get_server_object_by_id( + self._nova, self.__neutron, self.__vm.id) + return True + return False def __vm_status_check(self, expected_status_code, block, timeout, poll_interval): @@ -652,6 +665,56 @@ class OpenStackVmInstance(OpenStackComputeObject): return True return False + def cloud_init_complete(self, block=False, poll_interval=POLL_INTERVAL): + """ + Returns true when the VM's cloud-init routine has completed. + Note: this is currently done via SSH, therefore, if this instance does + not have a Floating IP or a running SSH server, this routine + will always return False or raise an Exception + :param block: When true, thread will block until active or timeout + value in seconds has been exceeded (False) + :param poll_interval: The polling interval + :return: T/F + """ + # sleep and wait for VM status change + logger.info('Checking if cloud-init has completed') + + timeout = self.instance_settings.cloud_init_timeout + + if self.vm_active(block=True) and self.vm_ssh_active(block=True): + if block: + start = time.time() + else: + start = time.time() - timeout + + while timeout > time.time() - start: + status = self.__cloud_init_complete() + if status: + logger.info('cloud-init complete for VM instance') + return True + + logger.debug('Retry cloud-init query in ' + str( + poll_interval) + ' seconds') + time.sleep(poll_interval) + logger.debug('cloud-init complete timeout in ' + str( + timeout - (time.time() - start))) + + logger.error('Timeout waiting for cloud-init to complete') + return False + + def __cloud_init_complete(self): + """ + Returns True when can create a SSH session else False + :return: T/F + """ + if len(self.__floating_ip_dict) > 0: + ssh = self.ssh_client() + if ssh: + stdin1, stdout1, sterr1 = ssh.exec_command( + 'ls -l /var/lib/cloud/instance/boot-finished') + return stdout1.channel.recv_exit_status() == 0 + return False + def get_floating_ip(self, fip_name=None): """ Returns the floating IP object byt name if found, else the first known, @@ -659,10 +722,9 @@ class OpenStackVmInstance(OpenStackComputeObject): :param fip_name: the name of the floating IP to return :return: the SSH client or None """ - fip = None if fip_name and self.__floating_ip_dict.get(fip_name): return self.__floating_ip_dict.get(fip_name) - if not fip: + else: return self.__get_first_provisioning_floating_ip() def ssh_client(self, fip_name=None): @@ -680,7 +742,7 @@ class OpenStackVmInstance(OpenStackComputeObject): self.keypair_settings.private_filepath, proxy_settings=self._os_creds.proxy_settings) else: - logger.warning( + FloatingIPAllocationError( 'Cannot return an SSH client. No Floating IP configured') def add_security_group(self, security_group): @@ -723,165 +785,67 @@ class OpenStackVmInstance(OpenStackComputeObject): logger.warning('Security group not removed - ' + str(e)) return False - -class VmInstanceSettings: - """ - Class responsible for holding configuration setting for a VM Instance - """ - - def __init__(self, **kwargs): + def reboot(self, reboot_type=RebootType.soft): """ - Constructor - :param name: the name of the VM - :param flavor: the VM's flavor name - :param port_settings: the port configuration settings (required) - :param security_group_names: a set of names of the security groups to - add to the VM - :param floating_ip_settings: the floating IP configuration settings - :param sudo_user: the sudo user of the VM that will override the - instance_settings.image_user when trying to - connect to the VM - :param vm_boot_timeout: the amount of time a thread will sleep waiting - for an instance to boot - :param vm_delete_timeout: the amount of time a thread will sleep - waiting for an instance to be deleted - :param ssh_connect_timeout: the amount of time a thread will sleep - waiting obtaining an SSH connection to a VM - :param availability_zone: the name of the compute server on which to - deploy the VM (optional) - :param userdata: the string contents of any optional cloud-init script - to execute after the VM has been activated. - This value may also contain a dict who's key value - must contain the key 'cloud-init_file' which denotes - the location of some file containing the cloud-init - script - """ - self.name = kwargs.get('name') - self.flavor = kwargs.get('flavor') - self.sudo_user = kwargs.get('sudo_user') - self.userdata = kwargs.get('userdata') - - self.port_settings = list() - port_settings = kwargs.get('ports') - if not port_settings: - port_settings = kwargs.get('port_settings') - if port_settings: - for port_setting in port_settings: - if isinstance(port_setting, dict): - self.port_settings.append(PortSettings(**port_setting)) - elif isinstance(port_setting, PortSettings): - self.port_settings.append(port_setting) - - if kwargs.get('security_group_names'): - if isinstance(kwargs['security_group_names'], list): - self.security_group_names = kwargs['security_group_names'] - elif isinstance(kwargs['security_group_names'], set): - self.security_group_names = kwargs['security_group_names'] - elif isinstance(kwargs['security_group_names'], str): - self.security_group_names = [kwargs['security_group_names']] - else: - raise VmInstanceSettingsError( - 'Invalid data type for security_group_names attribute') - else: - self.security_group_names = set() - - self.floating_ip_settings = list() - floating_ip_settings = kwargs.get('floating_ips') - if not floating_ip_settings: - floating_ip_settings = kwargs.get('floating_ip_settings') - if floating_ip_settings: - for floating_ip_config in floating_ip_settings: - if isinstance(floating_ip_config, FloatingIpSettings): - self.floating_ip_settings.append(floating_ip_config) - else: - self.floating_ip_settings.append(FloatingIpSettings( - **floating_ip_config['floating_ip'])) - - if kwargs.get('vm_boot_timeout'): - self.vm_boot_timeout = kwargs['vm_boot_timeout'] - else: - self.vm_boot_timeout = 900 - - if kwargs.get('vm_delete_timeout'): - self.vm_delete_timeout = kwargs['vm_delete_timeout'] - else: - self.vm_delete_timeout = 300 - - if kwargs.get('ssh_connect_timeout'): - self.ssh_connect_timeout = kwargs['ssh_connect_timeout'] - else: - self.ssh_connect_timeout = 180 + Issues a reboot call + :param reboot_type: instance of + snaps.openstack.utils.nova_utils.RebootType + enumeration + :return: + """ + nova_utils.reboot_server( + self._nova, self.__vm, reboot_type=reboot_type) - if kwargs.get('availability_zone'): - self.availability_zone = kwargs['availability_zone'] - else: - self.availability_zone = None - if not self.name or not self.flavor: - raise VmInstanceSettingsError( - 'Instance configuration requires the attributes: name, flavor') +def generate_creator(os_creds, vm_inst, image_config, keypair_config=None): + """ + Initializes an OpenStackVmInstance object + :param os_creds: the OpenStack credentials + :param vm_inst: the SNAPS-OO VmInst domain object + :param image_config: the associated ImageConfig object + :param keypair_config: the associated KeypairConfig object (optional) + :return: an initialized OpenStackVmInstance object + """ + nova = nova_utils.nova_client(os_creds) + neutron = neutron_utils.neutron_client(os_creds) + derived_inst_config = settings_utils.create_vm_inst_config( + nova, neutron, vm_inst) - if len(self.port_settings) == 0: - raise VmInstanceSettingsError( - 'Instance configuration requires port settings (aka. NICS)') + derived_inst_creator = OpenStackVmInstance( + os_creds, derived_inst_config, image_config, keypair_config) + derived_inst_creator.initialize() + return derived_inst_creator -class FloatingIpSettings: +class VmInstanceSettings(VmInstanceConfig): """ - Class responsible for holding configuration settings for a floating IP + Deprecated, use snaps.config.vm_inst.VmInstanceConfig instead """ - def __init__(self, **kwargs): - """ - Constructor - :param name: the name of the floating IP - :param port_name: the name of the router to the external network - :param router_name: the name of the router to the external network - :param subnet_name: the name of the subnet on which to attach the - floating IP - :param provisioning: when true, this floating IP can be used for - provisioning - - TODO - provisioning flag is a hack as I have only observed a single - Floating IPs that actually works on an instance. Multiple floating IPs - placed on different subnets from the same port are especially - troublesome as you cannot predict which one will actually connect. - For now, it is recommended not to setup multiple floating IPs on an - instance unless absolutely necessary. - """ - self.name = kwargs.get('name') - self.port_name = kwargs.get('port_name') - self.port_id = kwargs.get('port_id') - self.router_name = kwargs.get('router_name') - self.subnet_name = kwargs.get('subnet_name') - if kwargs.get('provisioning') is not None: - self.provisioning = kwargs['provisioning'] - else: - self.provisioning = True - - # if not self.name or not self.port_name or not self.router_name: - if not self.name or not self.router_name: - raise FloatingIpSettingsError( - 'The attributes name, port_name and router_name are required') + from warnings import warn + warn('Use snaps.config.vm_inst.VmInstanceConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) - if not self.port_name and not self.port_id: - raise FloatingIpSettingsError( - 'The attributes port_name or port_id are required') - -class VmInstanceSettingsError(Exception): +class FloatingIpSettings(FloatingIpConfig): """ - Exception to be thrown when an VM instance settings are incorrect + Deprecated, use snaps.config.vm_inst.FloatingIpConfig instead """ + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.vm_inst.FloatingIpConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) -class FloatingIpSettingsError(Exception): +class VmInstanceCreationError(Exception): """ - Exception to be thrown when an VM instance settings are incorrect + Exception to be thrown when an VM instance cannot be created """ -class VmInstanceCreationError(Exception): +class FloatingIPAllocationError(Exception): """ - Exception to be thrown when an VM instance cannot be created + Exception to be thrown when an VM instance cannot allocate a floating IP """ diff --git a/snaps/openstack/create_keypairs.py b/snaps/openstack/create_keypairs.py index 3869afc..a181a7b 100644 --- a/snaps/openstack/create_keypairs.py +++ b/snaps/openstack/create_keypairs.py @@ -15,10 +15,10 @@ import logging import os -from neutronclient.common.utils import str2bool from novaclient.exceptions import NotFound from snaps import file_utils +from snaps.config.keypair import KeypairConfig from snaps.openstack.openstack_creator import OpenStackComputeObject from snaps.openstack.utils import nova_utils @@ -36,7 +36,7 @@ class OpenStackKeypair(OpenStackComputeObject): """ Constructor - all parameters are required :param os_creds: The credentials to connect with OpenStack - :param keypair_settings: The settings used to create a keypair + :param keypair_settings: a KeypairConfig object """ super(self.__class__, self).__init__(os_creds) @@ -123,6 +123,7 @@ class OpenStackKeypair(OpenStackComputeObject): self.keypair_settings.public_filepath) os.chmod(expanded_path, 0o755) os.remove(expanded_path) + logger.info('Deleted public key file [%s]', expanded_path) if (self.keypair_settings.private_filepath and file_utils.file_exists( self.keypair_settings.private_filepath)): @@ -130,6 +131,7 @@ class OpenStackKeypair(OpenStackComputeObject): self.keypair_settings.private_filepath) os.chmod(expanded_path, 0o755) os.remove(expanded_path) + logger.info('Deleted private key file [%s]', expanded_path) def get_keypair(self): """ @@ -139,47 +141,13 @@ class OpenStackKeypair(OpenStackComputeObject): return self.__keypair -class KeypairSettings: +class KeypairSettings(KeypairConfig): """ Class representing a keypair configuration """ def __init__(self, **kwargs): - """ - Constructor - all parameters are optional - :param name: The keypair name. - :param public_filepath: The path to/from the filesystem where the - public key file is or will be stored - :param private_filepath: The path where the generated private key file - will be stored - :param key_size: The number of bytes for the key size when it needs to - be generated (Must be >=512 default 1024) - :param delete_on_clean: when True, the key files will be deleted when - OpenStackKeypair#clean() is called - :return: - """ - - self.name = kwargs.get('name') - self.public_filepath = kwargs.get('public_filepath') - self.private_filepath = kwargs.get('private_filepath') - self.key_size = int(kwargs.get('key_size', 1024)) - - if kwargs.get('delete_on_clean') is not None: - if isinstance(kwargs.get('delete_on_clean'), bool): - self.delete_on_clean = kwargs.get('delete_on_clean') - else: - self.delete_on_clean = str2bool(kwargs.get('delete_on_clean')) - else: - self.delete_on_clean = None - - if not self.name: - raise KeypairSettingsError('Name is a required attribute') - - if self.key_size < 512: - raise KeypairSettingsError('key_size must be >=512') - - -class KeypairSettingsError(Exception): - """ - Exception to be thrown when keypair settings are incorrect - """ + from warnings import warn + warn('Use snaps.config.keypair.KeypairConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) diff --git a/snaps/openstack/create_network.py b/snaps/openstack/create_network.py index 437f294..c9c58e8 100644 --- a/snaps/openstack/create_network.py +++ b/snaps/openstack/create_network.py @@ -14,10 +14,12 @@ # limitations under the License. import logging -from neutronclient.common.exceptions import NotFound +import enum +from neutronclient.common.exceptions import NetworkNotFoundClient +from snaps.config.network import NetworkConfig, SubnetConfig, PortConfig from snaps.openstack.openstack_creator import OpenStackNetworkObject -from snaps.openstack.utils import keystone_utils, neutron_utils +from snaps.openstack.utils import neutron_utils __author__ = 'spisarski' @@ -41,7 +43,6 @@ class OpenStackNetwork(OpenStackNetworkObject): # Attributes instantiated on create() self.__network = None - self.__subnets = list() def initialize(self): """ @@ -54,15 +55,6 @@ class OpenStackNetwork(OpenStackNetworkObject): self._neutron, network_settings=self.network_settings, project_id=self.network_settings.get_project_id(self._os_creds)) - if self.__network: - for subnet_setting in self.network_settings.subnet_settings: - sub_inst = neutron_utils.get_subnet( - self._neutron, subnet_settings=subnet_setting) - if sub_inst: - self.__subnets.append(sub_inst) - logger.debug( - "Subnet '%s' created successfully" % sub_inst.id) - return self.__network def create(self): @@ -77,19 +69,7 @@ class OpenStackNetwork(OpenStackNetworkObject): self.__network = neutron_utils.create_network( self._neutron, self._os_creds, self.network_settings) logger.debug( - "Network '%s' created successfully" % self.__network.id) - - for subnet_setting in self.network_settings.subnet_settings: - sub_inst = neutron_utils.get_subnet( - self._neutron, subnet_settings=subnet_setting) - if not sub_inst: - sub_inst = neutron_utils.create_subnet( - self._neutron, subnet_setting, self._os_creds, - self.__network) - if sub_inst: - self.__subnets.append(sub_inst) - logger.debug( - "Subnet '%s' created successfully" % sub_inst.id) + 'Network [%s] created successfully' % self.__network.id) return self.__network @@ -97,23 +77,11 @@ class OpenStackNetwork(OpenStackNetworkObject): """ Removes and deletes all items created in reverse order. """ - for subnet in self.__subnets: - try: - logger.info( - 'Deleting subnet with name ' + subnet.name) - neutron_utils.delete_subnet(self._neutron, subnet) - except NotFound as e: - logger.warning( - 'Error deleting subnet with message - ' + str(e)) - pass - self.__subnets = list() - if self.__network: try: neutron_utils.delete_network(self._neutron, self.__network) - except NotFound: + except NetworkNotFoundClient: pass - self.__network = None def get_network(self): @@ -123,445 +91,54 @@ class OpenStackNetwork(OpenStackNetworkObject): """ return self.__network - def get_subnets(self): - """ - Returns the OpenStack subnet objects - :return: - """ - return self.__subnets - -class NetworkSettings: +class NetworkSettings(NetworkConfig): """ - Class representing a network configuration + Class to hold the configuration settings required for creating OpenStack + Network objects + deprecated """ def __init__(self, **kwargs): - """ - Constructor - all parameters are optional - :param name: The network name. - :param admin_state_up: The administrative status of the network. - True = up / False = down (default True) - :param shared: Boolean value indicating whether this network is shared - across all projects/tenants. By default, only - administrative users can change this value. - :param project_name: Admin-only. The name of the project that will own - the network. This project can be different from - the project that makes the create network request. - However, only administrative users can specify a - project ID other than their own. You cannot change - this value through authorization policies. - :param external: when true, will setup an external network - (default False). - :param network_type: the type of network (i.e. vlan|flat). - :param physical_network: the name of the physical network - (this is required when network_type is 'flat') - :param segmentation_id: the id of the segmentation - (this is required when network_type is 'vlan') - :param subnets or subnet_settings: List of SubnetSettings objects. - :return: - """ - - self.project_id = None - - self.name = kwargs.get('name') - if kwargs.get('admin_state_up') is not None: - self.admin_state_up = bool(kwargs['admin_state_up']) - else: - self.admin_state_up = True - - if kwargs.get('shared') is not None: - self.shared = bool(kwargs['shared']) - else: - self.shared = None - - self.project_name = kwargs.get('project_name') - - if kwargs.get('external') is not None: - self.external = bool(kwargs.get('external')) - else: - self.external = False + from warnings import warn + warn('Use snaps.config.network.NetworkConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) - self.network_type = kwargs.get('network_type') - self.physical_network = kwargs.get('physical_network') - self.segmentation_id = kwargs.get('segmentation_id') - self.subnet_settings = list() - subnet_settings = kwargs.get('subnets') - if not subnet_settings: - subnet_settings = kwargs.get('subnet_settings') - if subnet_settings: - for subnet_config in subnet_settings: - if isinstance(subnet_config, SubnetSettings): - self.subnet_settings.append(subnet_config) - else: - self.subnet_settings.append( - SubnetSettings(**subnet_config['subnet'])) - - if not self.name or len(self.name) < 1: - raise NetworkSettingsError('Name required for networks') - - def get_project_id(self, os_creds): - """ - Returns the project ID for a given project_name or None - :param os_creds: the credentials required for keystone client retrieval - :return: the ID or None - """ - if self.project_id: - return self.project_id - else: - if self.project_name: - keystone = keystone_utils.keystone_client(os_creds) - project = keystone_utils.get_project( - keystone=keystone, project_name=self.project_name) - if project: - return project.id - - return None - - def dict_for_neutron(self, os_creds): - """ - Returns a dictionary object representing this object. - This is meant to be converted into JSON designed for use by the Neutron - API - TODO - expand automated testing to exercise all parameters - - :param os_creds: the OpenStack credentials - :return: the dictionary object - """ - out = dict() - - if self.name: - out['name'] = self.name - if self.admin_state_up is not None: - out['admin_state_up'] = self.admin_state_up - if self.shared: - out['shared'] = self.shared - if self.project_name: - project_id = self.get_project_id(os_creds) - if project_id: - out['tenant_id'] = project_id - else: - raise NetworkSettingsError( - 'Could not find project ID for project named - ' + - self.project_name) - if self.network_type: - out['provider:network_type'] = self.network_type - if self.physical_network: - out['provider:physical_network'] = self.physical_network - if self.segmentation_id: - out['provider:segmentation_id'] = self.segmentation_id - if self.external: - out['router:external'] = self.external - return {'network': out} - - -class NetworkSettingsError(Exception): +class IPv6Mode(enum.Enum): """ - Exception to be thrown when networks settings attributes are incorrect + A rule's direction + deprecated - use snaps.config.network.IPv6Mode """ + slaac = 'slaac' + stateful = 'dhcpv6-stateful' + stateless = 'dhcpv6-stateless' -class SubnetSettings: +class SubnetSettings(SubnetConfig): """ - Class representing a subnet configuration + Class to hold the configuration settings required for creating OpenStack + Subnet objects + deprecated """ def __init__(self, **kwargs): - """ - Constructor - all parameters are optional except cidr (subnet mask) - :param cidr: The CIDR. REQUIRED if config parameter is None - :param ip_version: The IP version, which is 4 or 6. - :param name: The subnet name. - :param project_name: The name of the project who owns the network. - Only administrative users can specify a project ID - other than their own. You cannot change this value - through authorization policies. - :param start: The start address for the allocation pools. - :param end: The end address for the allocation pools. - :param gateway_ip: The gateway IP address. - :param enable_dhcp: Set to true if DHCP is enabled and false if DHCP is - disabled. - :param dns_nameservers: A list of DNS name servers for the subnet. - Specify each name server as an IP address - and separate multiple entries with a space. - For example [8.8.8.7 8.8.8.8]. - :param host_routes: A list of host route dictionaries for the subnet. - For example: - "host_routes":[ - { - "destination":"0.0.0.0/0", - "nexthop":"123.456.78.9" - }, - { - "destination":"192.168.0.0/24", - "nexthop":"192.168.0.1" - } - ] - :param destination: The destination for static route - :param nexthop: The next hop for the destination. - :param ipv6_ra_mode: A valid value is dhcpv6-stateful, - dhcpv6-stateless, or slaac. - :param ipv6_address_mode: A valid value is dhcpv6-stateful, - dhcpv6-stateless, or slaac. - :raise: SubnetSettingsError when config does not have or cidr values - are None - """ - self.cidr = kwargs.get('cidr') - if kwargs.get('ip_version'): - self.ip_version = kwargs['ip_version'] - else: - self.ip_version = 4 - - # Optional attributes that can be set after instantiation - self.name = kwargs.get('name') - self.project_name = kwargs.get('project_name') - self.start = kwargs.get('start') - self.end = kwargs.get('end') - self.gateway_ip = kwargs.get('gateway_ip') - self.enable_dhcp = kwargs.get('enable_dhcp') - - if kwargs.get('dns_nameservers'): - self.dns_nameservers = kwargs.get('dns_nameservers') - else: - self.dns_nameservers = ['8.8.8.8'] - - self.host_routes = kwargs.get('host_routes') - self.destination = kwargs.get('destination') - self.nexthop = kwargs.get('nexthop') - self.ipv6_ra_mode = kwargs.get('ipv6_ra_mode') - self.ipv6_address_mode = kwargs.get('ipv6_address_mode') + from warnings import warn + warn('Use snaps.config.network.SubnetConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) - if not self.name or not self.cidr: - raise SubnetSettingsError('Name and cidr required for subnets') - def dict_for_neutron(self, os_creds, network=None): - """ - Returns a dictionary object representing this object. - This is meant to be converted into JSON designed for use by the Neutron - API - :param os_creds: the OpenStack credentials - :param network: The network object on which the subnet will be created - (optional) - :return: the dictionary object - """ - out = { - 'cidr': self.cidr, - 'ip_version': self.ip_version, - } - - if network: - out['network_id'] = network.id - if self.name: - out['name'] = self.name - if self.project_name: - keystone = keystone_utils.keystone_client(os_creds) - project = keystone_utils.get_project( - keystone=keystone, project_name=self.project_name) - project_id = None - if project: - project_id = project.id - if project_id: - out['tenant_id'] = project_id - else: - raise SubnetSettingsError( - 'Could not find project ID for project named - ' + - self.project_name) - if self.start and self.end: - out['allocation_pools'] = [{'start': self.start, 'end': self.end}] - if self.gateway_ip: - out['gateway_ip'] = self.gateway_ip - if self.enable_dhcp is not None: - out['enable_dhcp'] = self.enable_dhcp - if self.dns_nameservers and len(self.dns_nameservers) > 0: - out['dns_nameservers'] = self.dns_nameservers - if self.host_routes and len(self.host_routes) > 0: - out['host_routes'] = self.host_routes - if self.destination: - out['destination'] = self.destination - if self.nexthop: - out['nexthop'] = self.nexthop - if self.ipv6_ra_mode: - out['ipv6_ra_mode'] = self.ipv6_ra_mode - if self.ipv6_address_mode: - out['ipv6_address_mode'] = self.ipv6_address_mode - return out - - -class SubnetSettingsError(Exception): - """ - Exception to be thrown when subnet settings attributes are incorrect - """ - - -class PortSettings: +class PortSettings(PortConfig): """ - Class representing a port configuration + Class to hold the configuration settings required for creating OpenStack + Subnet objects + deprecated """ def __init__(self, **kwargs): - """ - Constructor - :param name: A symbolic name for the port (optional). - :param network_name: The name of the network on which to create the - port (required). - :param admin_state_up: A boolean value denoting the administrative - status of the port. True = up / False = down - :param project_name: The name of the project who owns the network. - Only administrative users can specify a project ID - other than their own. You cannot change this value - through authorization policies. - :param mac_address: The MAC address. If you specify an address that is - not valid, a Bad Request (400) status code is - returned. If you do not specify a MAC address, - OpenStack Networking tries to allocate one. If a - failure occurs, a Service Unavailable (503) status - code is returned. - :param ip_addrs: A list of dict objects where each contains two keys - 'subnet_name' and 'ip' values which will get mapped to - self.fixed_ips. These values will be directly - translated into the fixed_ips dict - :param fixed_ips: A dict where the key is the subnet IDs and value is - the IP address to assign to the port - :param security_groups: One or more security group IDs. - :param allowed_address_pairs: A dictionary containing a set of zero or - more allowed address pairs. An address - pair contains an IP address and MAC - address. - :param opt_value: The extra DHCP option value. - :param opt_name: The extra DHCP option name. - :param device_owner: The ID of the entity that uses this port. - For example, a DHCP agent. - :param device_id: The ID of the device that uses this port. - For example, a virtual server. - :return: - """ - if 'port' in kwargs: - kwargs = kwargs['port'] - - self.network = None - - self.name = kwargs.get('name') - self.network_name = kwargs.get('network_name') - - if kwargs.get('admin_state_up') is not None: - self.admin_state_up = bool(kwargs['admin_state_up']) - else: - self.admin_state_up = True - - self.project_name = kwargs.get('project_name') - self.mac_address = kwargs.get('mac_address') - self.ip_addrs = kwargs.get('ip_addrs') - self.fixed_ips = kwargs.get('fixed_ips') - self.security_groups = kwargs.get('security_groups') - self.allowed_address_pairs = kwargs.get('allowed_address_pairs') - self.opt_value = kwargs.get('opt_value') - self.opt_name = kwargs.get('opt_name') - self.device_owner = kwargs.get('device_owner') - self.device_id = kwargs.get('device_id') - - if not self.network_name: - raise PortSettingsError( - 'The attribute network_name is required') - - def __set_fixed_ips(self, neutron): - """ - Sets the self.fixed_ips value - :param neutron: the Neutron client - :return: None - """ - if not self.fixed_ips and self.ip_addrs: - self.fixed_ips = list() - - for ip_addr_dict in self.ip_addrs: - subnet = neutron_utils.get_subnet( - neutron, subnet_name=ip_addr_dict['subnet_name']) - if subnet and 'ip' in ip_addr_dict: - self.fixed_ips.append({'ip_address': ip_addr_dict['ip'], - 'subnet_id': subnet.id}) - else: - raise PortSettingsError( - 'Invalid port configuration, subnet does not exist ' - 'with name - ' + ip_addr_dict['subnet_name']) - - def dict_for_neutron(self, neutron, os_creds): - """ - Returns a dictionary object representing this object. - This is meant to be converted into JSON designed for use by the Neutron - API - - TODO - expand automated testing to exercise all parameters - :param neutron: the Neutron client - :param os_creds: the OpenStack credentials - :return: the dictionary object - """ - self.__set_fixed_ips(neutron) - - out = dict() - - project_id = None - if self.project_name: - keystone = keystone_utils.keystone_client(os_creds) - project = keystone_utils.get_project( - keystone=keystone, project_name=self.project_name) - if project: - project_id = project.id - - if not self.network: - self.network = neutron_utils.get_network( - neutron, network_name=self.network_name, project_id=project_id) - if not self.network: - raise PortSettingsError( - 'Cannot locate network with name - ' + self.network_name) - - out['network_id'] = self.network.id - - if self.admin_state_up is not None: - out['admin_state_up'] = self.admin_state_up - if self.name: - out['name'] = self.name - if self.project_name: - if project_id: - out['tenant_id'] = project_id - else: - raise PortSettingsError( - 'Could not find project ID for project named - ' + - self.project_name) - if self.mac_address: - out['mac_address'] = self.mac_address - if self.fixed_ips and len(self.fixed_ips) > 0: - out['fixed_ips'] = self.fixed_ips - if self.security_groups: - out['security_groups'] = self.security_groups - if self.allowed_address_pairs and len(self.allowed_address_pairs) > 0: - out['allowed_address_pairs'] = self.allowed_address_pairs - if self.opt_value: - out['opt_value'] = self.opt_value - if self.opt_name: - out['opt_name'] = self.opt_name - if self.device_owner: - out['device_owner'] = self.device_owner - if self.device_id: - out['device_id'] = self.device_id - return {'port': out} - - def __eq__(self, other): - return (self.name == other.name and - self.network_name == other.network_name and - self.admin_state_up == other.admin_state_up and - self.project_name == other.project_name and - self.mac_address == other.mac_address and - self.ip_addrs == other.ip_addrs and - self.fixed_ips == other.fixed_ips and - self.security_groups == other.security_groups and - self.allowed_address_pairs == other.allowed_address_pairs and - self.opt_value == other.opt_value and - self.opt_name == other.opt_name and - self.device_owner == other.device_owner and - self.device_id == other.device_id) - - -class PortSettingsError(Exception): - """ - Exception to be thrown when port settings attributes are incorrect - """ + from warnings import warn + warn('Use snaps.config.network.PortConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) diff --git a/snaps/openstack/create_project.py b/snaps/openstack/create_project.py index 0cf6d4a..0349890 100644 --- a/snaps/openstack/create_project.py +++ b/snaps/openstack/create_project.py @@ -16,6 +16,7 @@ import logging from keystoneclient.exceptions import NotFound, Conflict +from snaps.config.project import ProjectConfig from snaps.openstack.openstack_creator import OpenStackIdentityObject from snaps.openstack.utils import keystone_utils, neutron_utils, nova_utils @@ -169,44 +170,15 @@ class OpenStackProject(OpenStackIdentityObject): neutron_utils.update_quotas(neutron, self.__project.id, network_quotas) -class ProjectSettings: +class ProjectSettings(ProjectConfig): """ Class to hold the configuration settings required for creating OpenStack project objects + deprecated """ def __init__(self, **kwargs): - - """ - Constructor - :param name: the project's name (required) - :param domain or domain_name: the project's domain name - (default = 'Default'). - Field is used for v3 clients - :param description: the description (optional) - :param users: list of users to associat project to (optional) - :param enabled: denotes whether or not the user is enabled - (default True) - """ - - self.name = kwargs.get('name') - self.domain_name = kwargs.get( - 'domain', kwargs.get('domain', 'Default')) - - self.description = kwargs.get('description') - if kwargs.get('enabled') is not None: - self.enabled = kwargs['enabled'] - else: - self.enabled = True - - self.users = kwargs.get('users', list()) - - if not self.name: - raise ProjectSettingsError( - "The attribute name is required for ProjectSettings") - - -class ProjectSettingsError(Exception): - """ - Exception to be thrown when project settings attributes are incorrect - """ + from warnings import warn + warn('Use snaps.config.project.ProjectConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs)
\ No newline at end of file diff --git a/snaps/openstack/create_qos.py b/snaps/openstack/create_qos.py index ea96609..44e35a3 100644 --- a/snaps/openstack/create_qos.py +++ b/snaps/openstack/create_qos.py @@ -18,6 +18,7 @@ import logging import enum from cinderclient.exceptions import NotFound +from snaps.config.qos import QoSConfig from snaps.openstack.openstack_creator import OpenStackVolumeObject from snaps.openstack.utils import cinder_utils @@ -101,68 +102,25 @@ class OpenStackQoS(OpenStackVolumeObject): class Consumer(enum.Enum): """ QoS Specification consumer types + deprecated - use snaps.config.qos.Consumer """ front_end = 'front-end' back_end = 'back-end' both = 'both' -class QoSSettings: - def __init__(self, **kwargs): - """ - Constructor - :param name: the qos's name (required) - :param consumer: the qos's consumer type (required) - :param specs: dict of key/values - """ - - self.name = kwargs.get('name') - - if kwargs.get('consumer'): - self.consumer = map_consumer(kwargs['consumer']) - else: - self.consumer = None - - self.specs = kwargs.get('specs') - if not self.specs: - self.specs = dict() - - if not self.name or not self.consumer: - raise QoSSettingsError( - "The attributes name and consumer are required") - - -def map_consumer(consumer): - """ - Takes a the protocol value maps it to the Consumer enum. When None return - None - :param consumer: the value to map to the Enum - :return: the Protocol enum object - :raise: Exception if value is invalid - """ - if not consumer: - return None - elif isinstance(consumer, Consumer): - return consumer - else: - proto_str = str(consumer) - if proto_str == 'front-end': - return Consumer.front_end - elif proto_str == 'back-end': - return Consumer.back_end - elif proto_str == 'both': - return Consumer.both - else: - raise QoSSettingsError('Invalid Consumer - ' + proto_str) - - -class QoSSettingsError(Exception): +class QoSSettings(QoSConfig): """ - Exception to be thrown when an qos settings are incorrect + Class to hold the configuration settings required for creating OpenStack + QoS objects + deprecated """ - def __init__(self, message): - Exception.__init__(self, message) + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.qos.QoSConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) class QoSCreationError(Exception): diff --git a/snaps/openstack/create_router.py b/snaps/openstack/create_router.py index 98e3e14..4f95c3b 100644 --- a/snaps/openstack/create_router.py +++ b/snaps/openstack/create_router.py @@ -15,9 +15,10 @@ import logging from neutronclient.common.exceptions import NotFound -from snaps.openstack.create_network import PortSettings + +from snaps.config.router import RouterConfig from snaps.openstack.openstack_creator import OpenStackNetworkObject -from snaps.openstack.utils import neutron_utils, keystone_utils +from snaps.openstack.utils import neutron_utils __author__ = 'spisarski' @@ -34,7 +35,7 @@ class OpenStackRouter(OpenStackNetworkObject): Constructor - all parameters are required :param os_creds: The credentials to connect with OpenStack :param router_settings: The settings used to create a router object - (must be an instance of the RouterSettings + (must be an instance of the RouterConfig class) """ super(self.__class__, self).__init__(os_creds) @@ -63,20 +64,21 @@ class OpenStackRouter(OpenStackNetworkObject): self.__router = neutron_utils.get_router( self._neutron, router_settings=self.router_settings) - for internal_subnet_name in self.router_settings.internal_subnets: - internal_subnet = neutron_utils.get_subnet( - self._neutron, subnet_name=internal_subnet_name) - if internal_subnet: - self.__internal_subnets.append(internal_subnet) - else: - raise RouterCreationError( - 'Subnet not found with name ' + internal_subnet_name) - - for port_setting in self.router_settings.port_settings: - port = neutron_utils.get_port( - self._neutron, port_settings=port_setting) - if port: - self.__ports.append(port) + if self.__router: + for internal_subnet_name in self.router_settings.internal_subnets: + internal_subnet = neutron_utils.get_subnet( + self._neutron, subnet_name=internal_subnet_name) + if internal_subnet: + self.__internal_subnets.append(internal_subnet) + else: + raise RouterCreationError( + 'Subnet not found with name ' + internal_subnet_name) + + for port_setting in self.router_settings.port_settings: + port = neutron_utils.get_port( + self._neutron, port_settings=port_setting) + if port: + self.__ports.append(port) return self.__router @@ -132,6 +134,8 @@ class OpenStackRouter(OpenStackNetworkObject): 'Error creating port with name - ' + port_setting.name) + self.__router = neutron_utils.get_router_by_id( + self._neutron, self.__router.id) return self.__router def clean(self): @@ -190,101 +194,15 @@ class RouterCreationError(Exception): """ -class RouterSettings: +class RouterSettings(RouterConfig): """ - Class representing a router configuration + Class to hold the configuration settings required for creating OpenStack + router objects + deprecated """ def __init__(self, **kwargs): - """ - Constructor - all parameters are optional - :param name: The router name. - :param project_name: The name of the project who owns the network. Only - administrative users can specify a project ID - other than their own. You cannot change this value - through authorization policies. - :param external_gateway: Name of the external network to which to route - :param admin_state_up: The administrative status of the router. - True = up / False = down (default True) - :param external_fixed_ips: Dictionary containing the IP address - parameters. - :param internal_subnets: List of subnet names to which to connect this - router for Floating IP purposes - :param port_settings: List of PortSettings objects - :return: - """ - self.name = kwargs.get('name') - self.project_name = kwargs.get('project_name') - self.external_gateway = kwargs.get('external_gateway') - - self.admin_state_up = kwargs.get('admin_state_up') - self.enable_snat = kwargs.get('enable_snat') - self.external_fixed_ips = kwargs.get('external_fixed_ips') - if kwargs.get('internal_subnets'): - self.internal_subnets = kwargs['internal_subnets'] - else: - self.internal_subnets = list() - - self.port_settings = list() - if kwargs.get('interfaces', kwargs.get('port_settings')): - interfaces = kwargs.get('interfaces', kwargs.get('port_settings')) - for interface in interfaces: - if isinstance(interface, PortSettings): - self.port_settings.append(interface) - else: - self.port_settings.append( - PortSettings(**interface['port'])) - - if not self.name: - raise RouterSettingsError('Name is required') - - def dict_for_neutron(self, neutron, os_creds): - """ - Returns a dictionary object representing this object. - This is meant to be converted into JSON designed for use by the Neutron - API - - TODO - expand automated testing to exercise all parameters - :param neutron: The neutron client to retrieve external network - information if necessary - :param os_creds: The OpenStack credentials - :return: the dictionary object - """ - out = dict() - ext_gw = dict() - - if self.name: - out['name'] = self.name - if self.project_name: - keystone = keystone_utils.keystone_client(os_creds) - project = keystone_utils.get_project( - keystone=keystone, project_name=self.project_name) - project_id = None - if project: - project_id = project.id - if project_id: - out['tenant_id'] = project_id - else: - raise RouterSettingsError( - 'Could not find project ID for project named - ' + - self.project_name) - if self.admin_state_up is not None: - out['admin_state_up'] = self.admin_state_up - if self.external_gateway: - ext_net = neutron_utils.get_network( - neutron, network_name=self.external_gateway) - if ext_net: - ext_gw['network_id'] = ext_net.id - out['external_gateway_info'] = ext_gw - else: - raise RouterSettingsError( - 'Could not find the external network named - ' + - self.external_gateway) - - return {'router': out} - - -class RouterSettingsError(Exception): - """ - Exception to be thrown when router settings attributes are incorrect - """ + from warnings import warn + warn('Use snaps.config.router.RouterConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) diff --git a/snaps/openstack/create_security_group.py b/snaps/openstack/create_security_group.py index 8218c83..7a20fe1 100644 --- a/snaps/openstack/create_security_group.py +++ b/snaps/openstack/create_security_group.py @@ -17,6 +17,8 @@ import logging import enum from neutronclient.common.exceptions import NotFound, Conflict +from snaps.config.security_group import ( + SecurityGroupConfig, SecurityGroupRuleConfig) from snaps.openstack.openstack_creator import OpenStackNetworkObject from snaps.openstack.utils import keystone_utils from snaps.openstack.utils import neutron_utils @@ -66,6 +68,9 @@ class OpenStackSecurityGroup(OpenStackNetworkObject): rule_setting = self.__get_setting_from_rule(existing_rule) self.__rules[rule_setting] = existing_rule + self.__security_group = neutron_utils.get_security_group_by_id( + self._neutron, self.__security_group.id) + return self.__security_group def create(self): @@ -112,15 +117,15 @@ class OpenStackSecurityGroup(OpenStackNetworkObject): def __generate_rule_setting(self, rule): """ - Creates a SecurityGroupRuleSettings object for a given rule + Creates a SecurityGroupRuleConfig object for a given rule :param rule: the rule from which to create the - SecurityGroupRuleSettings object - :return: the newly instantiated SecurityGroupRuleSettings object + SecurityGroupRuleConfig object + :return: the newly instantiated SecurityGroupRuleConfig object """ sec_grp = neutron_utils.get_security_group_by_id( self._neutron, rule.security_group_id) - setting = SecurityGroupRuleSettings( + setting = SecurityGroupRuleConfig( description=rule.description, direction=rule.direction, ethertype=rule.ethertype, @@ -215,82 +220,24 @@ class OpenStackSecurityGroup(OpenStackNetworkObject): return None -class SecurityGroupSettings: +class SecurityGroupSettings(SecurityGroupConfig): """ - Class representing a keypair configuration + Class to hold the configuration settings required for creating OpenStack + SecurityGroup objects + deprecated - use snaps.config.security_group.SecurityGroupConfig instead """ def __init__(self, **kwargs): - """ - Constructor - all parameters are optional - :param name: The keypair name. - :param description: The security group's description - :param project_name: The name of the project under which the security - group will be created - :return: - """ - self.name = kwargs.get('name') - self.description = kwargs.get('description') - self.project_name = kwargs.get('project_name') - self.rule_settings = list() - - rule_settings = kwargs.get('rules') - if not rule_settings: - rule_settings = kwargs.get('rule_settings') - - if rule_settings: - for rule_setting in rule_settings: - if isinstance(rule_setting, SecurityGroupRuleSettings): - self.rule_settings.append(rule_setting) - else: - rule_setting['sec_grp_name'] = self.name - self.rule_settings.append(SecurityGroupRuleSettings( - **rule_setting)) - - if not self.name: - raise SecurityGroupSettingsError('The attribute name is required') - - for rule_setting in self.rule_settings: - if rule_setting.sec_grp_name is not self.name: - raise SecurityGroupSettingsError( - 'Rule settings must correspond with the name of this ' - 'security group') - - def dict_for_neutron(self, keystone): - """ - Returns a dictionary object representing this object. - This is meant to be converted into JSON designed for use by the Neutron - API - - TODO - expand automated testing to exercise all parameters - :param keystone: the Keystone client - :return: the dictionary object - """ - out = dict() - - if self.name: - out['name'] = self.name - if self.description: - out['description'] = self.description - if self.project_name: - project = keystone_utils.get_project( - keystone=keystone, project_name=self.project_name) - project_id = None - if project: - project_id = project.id - if project_id: - out['tenant_id'] = project_id - else: - raise SecurityGroupSettingsError( - 'Could not find project ID for project named - ' + - self.project_name) - - return {'security_group': out} + from warnings import warn + warn('Use snaps.config.security_group.SecurityGroupConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) class Direction(enum.Enum): """ A rule's direction + deprecated - use snaps.config.security_group.Direction """ ingress = 'ingress' egress = 'egress' @@ -299,270 +246,53 @@ class Direction(enum.Enum): class Protocol(enum.Enum): """ A rule's protocol + deprecated - use snaps.config.security_group.Protocol """ - icmp = 'icmp' - tcp = 'tcp' - udp = 'udp' + ah = 51 + dccp = 33 + egp = 8 + esp = 50 + gre = 47 + icmp = 1 + icmpv6 = 58 + igmp = 2 + ipv6_encap = 41 + ipv6_frag = 44 + ipv6_icmp = 58 + ipv6_nonxt = 59 + ipv6_opts = 60 + ipv6_route = 43 + ospf = 89 + pgm = 113 + rsvp = 46 + sctp = 132 + tcp = 6 + udp = 17 + udplite = 136 + vrrp = 112 + any = 'any' null = 'null' class Ethertype(enum.Enum): """ A rule's ethertype + deprecated - use snaps.config.security_group.Ethertype """ IPv4 = 4 IPv6 = 6 -class SecurityGroupSettingsError(Exception): - """ - Exception to be thrown when security group settings attributes are - invalid - """ - - -class SecurityGroupRuleSettings: +class SecurityGroupRuleSettings(SecurityGroupRuleConfig): """ - Class representing a keypair configuration + Class to hold the configuration settings required for creating OpenStack + SecurityGroupRule objects + deprecated - use snaps.config.security_group.SecurityGroupRuleConfig + instead """ def __init__(self, **kwargs): - """ - Constructor - all parameters are optional - :param sec_grp_name: The security group's name on which to add the - rule. (required) - :param description: The rule's description - :param direction: An enumeration of type - create_security_group.RULE_DIRECTION (required) - :param remote_group_id: The group ID to associate with this rule - (this should be changed to group name once - snaps support Groups) (optional) - :param protocol: An enumeration of type - create_security_group.RULE_PROTOCOL or a string value - that will be mapped accordingly (optional) - :param ethertype: An enumeration of type - create_security_group.RULE_ETHERTYPE (optional) - :param port_range_min: The minimum port number in the range that is - matched by the security group rule. When the - protocol is TCP or UDP, this value must be <= - port_range_max. When the protocol is ICMP, this - value must be an ICMP type. - :param port_range_max: The maximum port number in the range that is - matched by the security group rule. When the - protocol is TCP or UDP, this value must be <= - port_range_max. When the protocol is ICMP, this - value must be an ICMP type. - :param sec_grp_rule: The OpenStack rule object to a security group rule - object to associate - (note: Cannot be set using the config object nor - can I see any real uses for this parameter) - :param remote_ip_prefix: The remote IP prefix to associate with this - metering rule packet (optional) - - TODO - Need to support the tenant... - """ - - self.description = kwargs.get('description') - self.sec_grp_name = kwargs.get('sec_grp_name') - self.remote_group_id = kwargs.get('remote_group_id') - self.direction = None - if kwargs.get('direction'): - self.direction = map_direction(kwargs['direction']) - - self.protocol = None - if kwargs.get('protocol'): - self.protocol = map_protocol(kwargs['protocol']) - else: - self.protocol = Protocol.null - - self.ethertype = None - if kwargs.get('ethertype'): - self.ethertype = map_ethertype(kwargs['ethertype']) - - self.port_range_min = kwargs.get('port_range_min') - self.port_range_max = kwargs.get('port_range_max') - self.remote_ip_prefix = kwargs.get('remote_ip_prefix') - - if not self.direction or not self.sec_grp_name: - raise SecurityGroupRuleSettingsError( - 'direction and sec_grp_name are required') - - def dict_for_neutron(self, neutron): - """ - Returns a dictionary object representing this object. - This is meant to be converted into JSON designed for use by the Neutron - API - - :param neutron: the neutron client for performing lookups - :return: the dictionary object - """ - out = dict() - - if self.description: - out['description'] = self.description - if self.direction: - out['direction'] = self.direction.name - if self.port_range_min: - out['port_range_min'] = self.port_range_min - if self.port_range_max: - out['port_range_max'] = self.port_range_max - if self.ethertype: - out['ethertype'] = self.ethertype.name - if self.protocol and self.protocol.name != 'null': - out['protocol'] = self.protocol.name - if self.sec_grp_name: - sec_grp = neutron_utils.get_security_group( - neutron, sec_grp_name=self.sec_grp_name) - if sec_grp: - out['security_group_id'] = sec_grp.id - else: - raise SecurityGroupRuleSettingsError( - 'Cannot locate security group with name - ' + - self.sec_grp_name) - if self.remote_group_id: - out['remote_group_id'] = self.remote_group_id - if self.remote_ip_prefix: - out['remote_ip_prefix'] = self.remote_ip_prefix - - return {'security_group_rule': out} - - def rule_eq(self, rule): - """ - Returns True if this setting created the rule - :param rule: the rule to evaluate - :return: T/F - """ - if self.description is not None: - if (rule.description is not None and - rule.description != ''): - return False - elif self.description != rule.description: - if rule.description != '': - return False - - if self.direction.name != rule.direction: - return False - - if self.ethertype and rule.ethertype: - if self.ethertype.name != rule.ethertype: - return False - - if self.port_range_min and rule.port_range_min: - if self.port_range_min != rule.port_range_min: - return False - - if self.port_range_max and rule.port_range_max: - if self.port_range_max != rule.port_range_max: - return False - - if self.protocol and rule.protocol: - if self.protocol.name != rule.protocol: - return False - - if self.remote_group_id and rule.remote_group_id: - if self.remote_group_id != rule.remote_group_id: - return False - - if self.remote_ip_prefix and rule.remote_ip_prefix: - if self.remote_ip_prefix != rule.remote_ip_prefix: - return False - - return True - - def __eq__(self, other): - return ( - self.description == other.description and - self.direction == other.direction and - self.port_range_min == other.port_range_min and - self.port_range_max == other.port_range_max and - self.ethertype == other.ethertype and - self.protocol == other.protocol and - self.sec_grp_name == other.sec_grp_name and - self.remote_group_id == other.remote_group_id and - self.remote_ip_prefix == other.remote_ip_prefix) - - def __hash__(self): - return hash((self.sec_grp_name, self.description, self.direction, - self.remote_group_id, - self.protocol, self.ethertype, self.port_range_min, - self.port_range_max, self.remote_ip_prefix)) - - -def map_direction(direction): - """ - Takes a the direction value maps it to the Direction enum. When None return - None - :param direction: the direction value - :return: the Direction enum object - :raise: Exception if value is invalid - """ - if not direction: - return None - if isinstance(direction, Direction): - return direction - else: - dir_str = str(direction) - if dir_str == 'egress': - return Direction.egress - elif dir_str == 'ingress': - return Direction.ingress - else: - raise SecurityGroupRuleSettingsError( - 'Invalid Direction - ' + dir_str) - - -def map_protocol(protocol): - """ - Takes a the protocol value maps it to the Protocol enum. When None return - None - :param protocol: the protocol value - :return: the Protocol enum object - :raise: Exception if value is invalid - """ - if not protocol: - return None - elif isinstance(protocol, Protocol): - return protocol - else: - proto_str = str(protocol) - if proto_str == 'icmp': - return Protocol.icmp - elif proto_str == 'tcp': - return Protocol.tcp - elif proto_str == 'udp': - return Protocol.udp - elif proto_str == 'null': - return Protocol.null - else: - raise SecurityGroupRuleSettingsError( - 'Invalid Protocol - ' + proto_str) - - -def map_ethertype(ethertype): - """ - Takes a the ethertype value maps it to the Ethertype enum. When None return - None - :param ethertype: the ethertype value - :return: the Ethertype enum object - :raise: Exception if value is invalid - """ - if not ethertype: - return None - elif isinstance(ethertype, Ethertype): - return ethertype - else: - eth_str = str(ethertype) - if eth_str == 'IPv6': - return Ethertype.IPv6 - elif eth_str == 'IPv4': - return Ethertype.IPv4 - else: - raise SecurityGroupRuleSettingsError( - 'Invalid Ethertype - ' + eth_str) - - -class SecurityGroupRuleSettingsError(Exception): - """ - Exception to be thrown when security group rule settings attributes are - invalid - """ + from warnings import warn + warn('Use snaps.config.security_group.SecurityGroupRuleConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) diff --git a/snaps/openstack/create_stack.py b/snaps/openstack/create_stack.py index c161973..d4a6b10 100644 --- a/snaps/openstack/create_stack.py +++ b/snaps/openstack/create_stack.py @@ -18,9 +18,18 @@ import time from heatclient.exc import HTTPNotFound +import snaps +from snaps.config.stack import StackConfig +from snaps.openstack.create_flavor import OpenStackFlavor from snaps.openstack.create_instance import OpenStackVmInstance +from snaps.openstack.create_keypairs import OpenStackKeypair +from snaps.openstack.create_security_group import OpenStackSecurityGroup +from snaps.openstack.create_router import OpenStackRouter +from snaps.openstack.create_volume import OpenStackVolume +from snaps.openstack.create_volume_type import OpenStackVolumeType from snaps.openstack.openstack_creator import OpenStackCloudObject -from snaps.openstack.utils import nova_utils, settings_utils, glance_utils +from snaps.openstack.utils import ( + nova_utils, settings_utils, glance_utils, cinder_utils) from snaps.openstack.create_network import OpenStackNetwork from snaps.openstack.utils import heat_utils, neutron_utils @@ -29,14 +38,6 @@ __author__ = 'spisarski' logger = logging.getLogger('create_stack') -STACK_DELETE_TIMEOUT = 1200 -STACK_COMPLETE_TIMEOUT = 1200 -POLL_INTERVAL = 3 -STATUS_CREATE_FAILED = 'CREATE_FAILED' -STATUS_CREATE_COMPLETE = 'CREATE_COMPLETE' -STATUS_DELETE_COMPLETE = 'DELETE_COMPLETE' -STATUS_DELETE_FAILED = 'DELETE_FAILED' - class OpenStackHeatStack(OpenStackCloudObject, object): """ @@ -49,11 +50,9 @@ class OpenStackHeatStack(OpenStackCloudObject, object): Constructor :param os_creds: The OpenStack connection credentials :param stack_settings: The stack settings - :param image_settings: A list of ImageSettings objects that were used - for spawning this stack - :param image_settings: A list of ImageSettings objects that were used + :param image_settings: A list of ImageConfig objects that were used for spawning this stack - :param keypair_settings: A list of KeypairSettings objects that were + :param keypair_settings: A list of KeypairConfig objects that were used for spawning this stack :return: """ @@ -95,25 +94,22 @@ class OpenStackHeatStack(OpenStackCloudObject, object): self.initialize() if self.__stack: - logger.info('Found stack with name - ' + self.stack_settings.name) + logger.info('Found stack with name - %s', self.stack_settings.name) return self.__stack else: self.__stack = heat_utils.create_stack(self.__heat_cli, self.stack_settings) logger.info( - 'Created stack with name - ' + self.stack_settings.name) + 'Created stack with name - %s', self.stack_settings.name) if self.__stack and self.stack_complete(block=True): - logger.info( - 'Stack is now active with name - ' + - self.stack_settings.name) + logger.info('Stack is now active with name - %s', + self.stack_settings.name) return self.__stack else: status = heat_utils.get_stack_status_reason(self.__heat_cli, self.__stack.id) - logger.error( - 'ERROR: STACK CREATION FAILED: ' + status) - raise StackCreationError( - 'Failure while creating stack') + logger.error('ERROR: STACK CREATION FAILED: %s', status) + raise StackCreationError('Failure while creating stack') def clean(self): """ @@ -122,7 +118,7 @@ class OpenStackHeatStack(OpenStackCloudObject, object): """ if self.__stack: try: - logger.info('Deleting stack - %s' + self.__stack.name) + logger.info('Deleting stack - %s', self.__stack.name) heat_utils.delete_stack(self.__heat_cli, self.__stack) try: @@ -181,7 +177,7 @@ class OpenStackHeatStack(OpenStackCloudObject, object): return heat_utils.get_stack_status(self.__heat_cli, self.__stack.id) def stack_complete(self, block=False, timeout=None, - poll_interval=POLL_INTERVAL): + poll_interval=snaps.config.stack.POLL_INTERVAL): """ Returns true when the stack status returns the value of expected_status_code @@ -193,11 +189,13 @@ class OpenStackHeatStack(OpenStackCloudObject, object): """ if not timeout: timeout = self.stack_settings.stack_create_timeout - return self._stack_status_check(STATUS_CREATE_COMPLETE, block, timeout, - poll_interval, STATUS_CREATE_FAILED) + return self._stack_status_check( + snaps.config.stack.STATUS_CREATE_COMPLETE, block, timeout, + poll_interval, snaps.config.stack.STATUS_CREATE_FAILED) - def stack_deleted(self, block=False, timeout=STACK_DELETE_TIMEOUT, - poll_interval=POLL_INTERVAL): + def stack_deleted(self, block=False, + timeout=snaps.config.stack.STACK_DELETE_TIMEOUT, + poll_interval=snaps.config.stack.POLL_INTERVAL): """ Returns true when the stack status returns the value of expected_status_code @@ -207,8 +205,9 @@ class OpenStackHeatStack(OpenStackCloudObject, object): :param poll_interval: The polling interval in seconds :return: T/F """ - return self._stack_status_check(STATUS_DELETE_COMPLETE, block, timeout, - poll_interval, STATUS_DELETE_FAILED) + return self._stack_status_check( + snaps.config.stack.STATUS_DELETE_COMPLETE, block, timeout, + poll_interval, snaps.config.stack.STATUS_DELETE_FAILED) def get_network_creators(self): """ @@ -224,7 +223,7 @@ class OpenStackHeatStack(OpenStackCloudObject, object): self.__heat_cli, neutron, self.__stack) for stack_network in stack_networks: - net_settings = settings_utils.create_network_settings( + net_settings = settings_utils.create_network_config( neutron, stack_network) net_creator = OpenStackNetwork(self._os_creds, net_settings) out.append(net_creator) @@ -232,6 +231,50 @@ class OpenStackHeatStack(OpenStackCloudObject, object): return out + def get_security_group_creators(self): + """ + Returns a list of security group creator objects as configured by the + heat template + :return: list() of OpenStackNetwork objects + """ + + neutron = neutron_utils.neutron_client(self._os_creds) + + out = list() + stack_security_groups = heat_utils.get_stack_security_groups( + self.__heat_cli, neutron, self.__stack) + + for stack_security_group in stack_security_groups: + settings = settings_utils.create_security_group_config( + neutron, stack_security_group) + creator = OpenStackSecurityGroup(self._os_creds, settings) + out.append(creator) + creator.initialize() + + return out + + def get_router_creators(self): + """ + Returns a list of router creator objects as configured by the heat + template + :return: list() of OpenStackRouter objects + """ + + neutron = neutron_utils.neutron_client(self._os_creds) + + out = list() + stack_routers = heat_utils.get_stack_routers( + self.__heat_cli, neutron, self.__stack) + + for routers in stack_routers: + settings = settings_utils.create_router_config( + neutron, routers) + creator = OpenStackRouter(self._os_creds, settings) + out.append(creator) + creator.initialize() + + return out + def get_vm_inst_creators(self, heat_keypair_option=None): """ Returns a list of VM Instance creator objects as configured by the heat @@ -241,19 +284,19 @@ class OpenStackHeatStack(OpenStackCloudObject, object): out = list() nova = nova_utils.nova_client(self._os_creds) + neutron = neutron_utils.neutron_client(self._os_creds) stack_servers = heat_utils.get_stack_servers( - self.__heat_cli, nova, self.__stack) + self.__heat_cli, nova, neutron, self.__stack) - neutron = neutron_utils.neutron_client(self._os_creds) glance = glance_utils.glance_client(self._os_creds) for stack_server in stack_servers: - vm_inst_settings = settings_utils.create_vm_inst_settings( + vm_inst_settings = settings_utils.create_vm_inst_config( nova, neutron, stack_server) - image_settings = settings_utils.determine_image_settings( + image_settings = settings_utils.determine_image_config( glance, stack_server, self.image_settings) - keypair_settings = settings_utils.determine_keypair_settings( + keypair_settings = settings_utils.determine_keypair_config( self.__heat_cli, self.__stack, stack_server, keypair_settings=self.keypair_settings, priv_key_key=heat_keypair_option) @@ -265,6 +308,113 @@ class OpenStackHeatStack(OpenStackCloudObject, object): return out + def get_volume_creators(self): + """ + Returns a list of Volume creator objects as configured by the heat + template + :return: list() of OpenStackVolume objects + """ + + out = list() + cinder = cinder_utils.cinder_client(self._os_creds) + + volumes = heat_utils.get_stack_volumes( + self.__heat_cli, cinder, self.__stack) + + for volume in volumes: + settings = settings_utils.create_volume_config(volume) + creator = OpenStackVolume(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume creator - %s', e) + + return out + + def get_volume_type_creators(self): + """ + Returns a list of VolumeType creator objects as configured by the heat + template + :return: list() of OpenStackVolumeType objects + """ + + out = list() + cinder = cinder_utils.cinder_client(self._os_creds) + + vol_types = heat_utils.get_stack_volume_types( + self.__heat_cli, cinder, self.__stack) + + for volume in vol_types: + settings = settings_utils.create_volume_type_config(volume) + creator = OpenStackVolumeType(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume type creator - %s', + e) + + return out + + def get_keypair_creators(self, outputs_pk_key=None): + """ + Returns a list of keypair creator objects as configured by the heat + template + :return: list() of OpenStackKeypair objects + """ + + out = list() + nova = nova_utils.nova_client(self._os_creds) + + keypairs = heat_utils.get_stack_keypairs( + self.__heat_cli, nova, self.__stack) + + for keypair in keypairs: + settings = settings_utils.create_keypair_config( + self.__heat_cli, self.__stack, keypair, outputs_pk_key) + creator = OpenStackKeypair(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume type creator - %s', + e) + + return out + + def get_flavor_creators(self): + """ + Returns a list of Flavor creator objects as configured by the heat + template + :return: list() of OpenStackFlavor objects + """ + + out = list() + nova = nova_utils.nova_client(self._os_creds) + + flavors = heat_utils.get_stack_flavors( + self.__heat_cli, nova, self.__stack) + + for flavor in flavors: + settings = settings_utils.create_flavor_config(flavor) + creator = OpenStackFlavor(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume creator - %s', e) + + return out + def _stack_status_check(self, expected_status_code, block, timeout, poll_interval, fail_status): """ @@ -302,7 +452,8 @@ class OpenStackHeatStack(OpenStackCloudObject, object): 'Timeout checking for stack status for ' + expected_status_code) return False - def _status(self, expected_status_code, fail_status=STATUS_CREATE_FAILED): + def _status(self, expected_status_code, + fail_status=snaps.config.stack.STATUS_CREATE_FAILED): """ Returns True when active else False :param expected_status_code: stack status evaluated with this string @@ -316,52 +467,38 @@ class OpenStackHeatStack(OpenStackCloudObject, object): return False if fail_status and status == fail_status: + resources = heat_utils.get_resources( + self.__heat_cli, self.__stack.id) + logger.error('Stack %s failed', self.__stack.name) + for resource in resources: + if (resource.status != + snaps.config.stack.STATUS_CREATE_COMPLETE): + logger.error( + 'Resource: [%s] status: [%s] reason: [%s]', + resource.name, resource.status, resource.status_reason) + else: + logger.debug( + 'Resource: [%s] status: [%s] reason: [%s]', + resource.name, resource.status, resource.status_reason) + raise StackError('Stack had an error') logger.debug('Stack status is - ' + status) return status == expected_status_code -class StackSettings: - def __init__(self, **kwargs): - """ - Constructor - :param name: the stack's name (required) - :param template: the heat template in dict() format (required if - template_path attribute is None) - :param template_path: the location of the heat template file (required - if template attribute is None) - :param env_values: k/v pairs of strings for substitution of template - default values (optional) - """ - - self.name = kwargs.get('name') - self.template = kwargs.get('template') - self.template_path = kwargs.get('template_path') - self.env_values = kwargs.get('env_values') - if 'stack_create_timeout' in kwargs: - self.stack_create_timeout = kwargs['stack_create_timeout'] - else: - self.stack_create_timeout = STACK_COMPLETE_TIMEOUT - - if not self.name: - raise StackSettingsError('name is required') - - if not self.template and not self.template_path: - raise StackSettingsError('A Heat template is required') - - def __eq__(self, other): - return (self.name == other.name and - self.template == other.template and - self.template_path == other.template_path and - self.env_values == other.env_values and - self.stack_create_timeout == other.stack_create_timeout) - - -class StackSettingsError(Exception): +class StackSettings(StackConfig): """ - Exception to be thrown when an stack settings are incorrect + Class to hold the configuration settings required for creating OpenStack + stack objects + deprecated """ + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.stack.StackConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) + class StackCreationError(Exception): """ diff --git a/snaps/openstack/create_user.py b/snaps/openstack/create_user.py index bcf4790..5da3a5e 100644 --- a/snaps/openstack/create_user.py +++ b/snaps/openstack/create_user.py @@ -16,6 +16,7 @@ import logging from keystoneclient.exceptions import NotFound +from snaps.config.user import UserConfig from snaps.openstack.openstack_creator import OpenStackIdentityObject from snaps.openstack.os_credentials import OSCreds from snaps.openstack.utils import keystone_utils @@ -96,6 +97,11 @@ class OpenStackUser(OpenStackIdentityObject): auth_url=self._os_creds.auth_url, project_name=project_name, identity_api_version=self._os_creds.identity_api_version, + image_api_version=self._os_creds.image_api_version, + network_api_version=self._os_creds.network_api_version, + compute_api_version=self._os_creds.compute_api_version, + heat_api_version=self._os_creds.heat_api_version, + volume_api_version=self._os_creds.volume_api_version, user_domain_name=self._os_creds.user_domain_name, user_domain_id=self._os_creds.user_domain_id, project_domain_name=self._os_creds.project_domain_name, @@ -105,43 +111,15 @@ class OpenStackUser(OpenStackIdentityObject): cacert=self._os_creds.cacert) -class UserSettings: - def __init__(self, **kwargs): - - """ - Constructor - :param name: the user's name (required) - :param password: the user's password (required) - :param project_name: the user's primary project name (optional) - :param domain_name: the user's domain name (default='default'). For v3 - APIs - :param email: the user's email address (optional) - :param enabled: denotes whether or not the user is enabled - (default True) - :param roles: dict where key is the role's name and value is the name - the project to associate with the role - """ - - self.name = kwargs.get('name') - self.password = kwargs.get('password') - self.project_name = kwargs.get('project_name') - self.email = kwargs.get('email') - self.domain_name = kwargs.get('domain_name', 'Default') - self.enabled = kwargs.get('enabled', True) - self.roles = kwargs.get('roles', dict()) - - if not self.name or not self.password: - raise UserSettingsException( - 'The attributes name and password are required for ' - 'UserSettings') - - if not isinstance(self.enabled, bool): - raise UserSettingsException('The attribute enabled must be of type' - ' boolean') - - -class UserSettingsException(Exception): +class UserSettings(UserConfig): """ - Raised when there is a problem with the values set in the UserSettings - class + Class to hold the configuration settings required for creating OpenStack + user objects + deprecated """ + + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.user.UserConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) diff --git a/snaps/openstack/create_volume.py b/snaps/openstack/create_volume.py new file mode 100644 index 0000000..c134ca1 --- /dev/null +++ b/snaps/openstack/create_volume.py @@ -0,0 +1,252 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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 time + +from cinderclient.exceptions import NotFound + +from snaps.config.volume import VolumeConfig +from snaps.openstack.openstack_creator import OpenStackVolumeObject +from snaps.openstack.utils import cinder_utils + +__author__ = 'spisarski' + +logger = logging.getLogger('create_volume') + +VOLUME_ACTIVE_TIMEOUT = 300 +VOLUME_DELETE_TIMEOUT = 60 +POLL_INTERVAL = 3 +STATUS_ACTIVE = 'available' +STATUS_IN_USE = 'in-use' +STATUS_FAILED = 'error' +STATUS_DELETED = 'deleted' + + +class OpenStackVolume(OpenStackVolumeObject): + """ + Class responsible for managing an volume in OpenStack + """ + + def __init__(self, os_creds, volume_settings): + """ + Constructor + :param os_creds: The OpenStack connection credentials + :param volume_settings: The volume settings + :return: + """ + super(self.__class__, self).__init__(os_creds) + + self.volume_settings = volume_settings + self.__volume = None + + def initialize(self): + """ + Loads the existing Volume + :return: The Volume domain object or None + """ + super(self.__class__, self).initialize() + + self.__volume = cinder_utils.get_volume( + self._cinder, volume_settings=self.volume_settings) + return self.__volume + + def create(self, block=False): + """ + Creates the volume in OpenStack if it does not already exist and + returns the domain Volume object + :return: The Volume domain object or None + """ + self.initialize() + + if not self.__volume: + self.__volume = cinder_utils.create_volume( + self._cinder, self.volume_settings) + + logger.info( + 'Created volume with name - %s', self.volume_settings.name) + if self.__volume: + if block: + if self.volume_active(block=True): + logger.info('Volume is now active with name - %s', + self.volume_settings.name) + return self.__volume + else: + raise VolumeCreationError( + 'Volume was not created or activated in the ' + 'alloted amount of time') + else: + logger.info('Did not create volume due to cleanup mode') + + return self.__volume + + def clean(self): + """ + Cleanse environment of all artifacts + :return: void + """ + if self.__volume: + try: + if self.volume_active(): + cinder_utils.delete_volume(self._cinder, self.__volume) + else: + logger.warn('Timeout waiting to delete volume %s', + self.__volume.name) + except NotFound: + pass + + try: + if self.volume_deleted(block=True): + logger.info( + 'Volume has been properly deleted with name - %s', + self.volume_settings.name) + self.__vm = None + else: + logger.error( + 'Volume not deleted within the timeout period of %s ' + 'seconds', VOLUME_DELETE_TIMEOUT) + except Exception as e: + logger.error( + 'Unexpected error while checking VM instance status - %s', + e) + + self.__volume = None + + def get_volume(self): + """ + Returns the domain Volume object as it was populated when create() was + called + :return: the object + """ + return self.__volume + + def volume_active(self, block=False, timeout=VOLUME_ACTIVE_TIMEOUT, + poll_interval=POLL_INTERVAL): + """ + Returns true when the volume status returns the value of + expected_status_code + :param block: When true, thread will block until active or timeout + value in seconds has been exceeded (False) + :param timeout: The timeout value + :param poll_interval: The polling interval in seconds + :return: T/F + """ + return self._volume_status_check(STATUS_ACTIVE, block, timeout, + poll_interval) + + def volume_in_use(self): + """ + Returns true when the volume status returns the value of + expected_status_code + :return: T/F + """ + return self._volume_status_check(STATUS_IN_USE, False, 0, 0) + + def volume_deleted(self, block=False, poll_interval=POLL_INTERVAL): + """ + Returns true when the VM status returns the value of + expected_status_code or instance retrieval throws a NotFound exception. + :param block: When true, thread will block until active or timeout + value in seconds has been exceeded (False) + :param poll_interval: The polling interval in seconds + :return: T/F + """ + try: + return self._volume_status_check( + STATUS_DELETED, block, VOLUME_DELETE_TIMEOUT, poll_interval) + except NotFound as e: + logger.debug( + "Volume not found when querying status for %s with message " + "%s", STATUS_DELETED, e) + return True + + def _volume_status_check(self, expected_status_code, block, timeout, + poll_interval): + """ + Returns true when the volume status returns the value of + expected_status_code + :param expected_status_code: instance status evaluated with this string + value + :param block: When true, thread will block until active or timeout + value in seconds has been exceeded (False) + :param timeout: The timeout value + :param poll_interval: The polling interval in seconds + :return: T/F + """ + # sleep and wait for volume status change + if block: + start = time.time() + else: + start = time.time() - timeout + 1 + + while timeout > time.time() - start: + status = self._status(expected_status_code) + if status: + logger.debug('Volume is active with name - %s', + self.volume_settings.name) + return True + + logger.debug('Retry querying volume status in %s seconds', + str(poll_interval)) + time.sleep(poll_interval) + logger.debug('Volume status query timeout in %s', + str(timeout - (time.time() - start))) + + logger.error( + 'Timeout checking for volume status for ' + expected_status_code) + return False + + def _status(self, expected_status_code): + """ + Returns True when active else False + :param expected_status_code: instance status evaluated with this string + value + :return: T/F + """ + status = cinder_utils.get_volume_status(self._cinder, self.__volume) + if not status: + logger.warning( + 'Cannot volume status for volume with ID - %s', + self.__volume.id) + return False + + if status == 'ERROR': + raise VolumeCreationError( + 'Instance had an error during deployment') + logger.debug('Instance status is - ' + status) + return status == expected_status_code + + +class VolumeSettings(VolumeConfig): + """ + Class to hold the configuration settings required for creating OpenStack + Volume Type Encryption objects + deprecated + """ + + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.volume.VolumeConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) + + +class VolumeCreationError(Exception): + """ + Exception to be thrown when an volume cannot be created + """ + + def __init__(self, message): + Exception.__init__(self, message) diff --git a/snaps/openstack/create_volume_type.py b/snaps/openstack/create_volume_type.py index a60bb1e..a7198d8 100644 --- a/snaps/openstack/create_volume_type.py +++ b/snaps/openstack/create_volume_type.py @@ -15,10 +15,10 @@ import logging -import enum from cinderclient.exceptions import NotFound -from neutronclient.common.utils import str2bool +from snaps.config.volume_type import ( + VolumeTypeConfig, VolumeTypeEncryptionConfig) from snaps.openstack.openstack_creator import OpenStackVolumeObject from snaps.openstack.utils import cinder_utils @@ -56,7 +56,7 @@ class OpenStackVolumeType(OpenStackVolumeObject): return self.__volume_type - def create(self, block=False): + def create(self): """ Creates the volume in OpenStack if it does not already exist and returns the domain Volume object @@ -96,134 +96,32 @@ class OpenStackVolumeType(OpenStackVolumeObject): return self.__volume_type -class VolumeTypeSettings: - def __init__(self, **kwargs): - """ - Constructor - :param name: the volume's name (required) - :param description: the volume's name (optional) - :param encryption: VolumeTypeEncryptionSettings (optional) - :param qos_spec_name: name of the QoS Spec to associate (optional) - :param public: When True, an image will be created with public - visibility (default - False) - - TODO - Implement project_access parameter that will associate this - VolumeType to a list of project names - """ - - self.name = kwargs.get('name') - self.description = kwargs.get('description') - self.qos_spec_name = kwargs.get('qos_spec_name') - - if 'encryption' in kwargs: - if isinstance(kwargs['encryption'], dict): - self.encryption = VolumeTypeEncryptionSettings( - **kwargs['encryption']) - elif isinstance(kwargs['encryption'], - VolumeTypeEncryptionSettings): - self.encryption = kwargs['encryption'] - else: - self.encryption = None - - if 'public' in kwargs: - if isinstance(kwargs['public'], str): - self.public = str2bool(kwargs['public']) - else: - self.public = kwargs['public'] - else: - self.public = False - - if not self.name: - raise VolumeTypeSettingsError("The attribute name is required") - - def __eq__(self, other): - return (self.name == other.name - and self.description == other.description - and self.qos_spec_name == other.qos_spec_name - and self.encryption == other.encryption - and self.public == other.public) - - -class ControlLocation(enum.Enum): +class VolumeTypeSettings(VolumeTypeConfig): """ - QoS Specification consumer types + Class to hold the configuration settings required for creating OpenStack + Volume Type objects + deprecated """ - front_end = 'front-end' - back_end = 'back-end' - -class VolumeTypeEncryptionSettings: def __init__(self, **kwargs): - """ - Constructor - :param name: the volume's name (required) - :param provider_class: the volume's provider class (e.g. LuksEncryptor) - :param control_location: the notional service where encryption is - performed (e.g., front-end=Nova). The default - value is 'front-end.' - :param cipher: the encryption algorithm/mode to use - (e.g., aes-xts-plain64). If the field is left empty, - the provider default will be used - :param key_size: the size of the encryption key, in bits - (e.g., 128, 256). If the field is left empty, the - provider default will be used - """ + from warnings import warn + warn('Use snaps.config.volume_type.VolumeTypeConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) - self.name = kwargs.get('name') - self.provider_class = kwargs.get('provider_class') - self.control_location = kwargs.get('control_location') - if kwargs.get('control_location'): - self.control_location = map_control_location( - kwargs['control_location']) - else: - self.control_location = None - - self.cipher = kwargs.get('cipher') - self.key_size = kwargs.get('key_size') - - if (not self.name or not self.provider_class - or not self.control_location): - raise VolumeTypeSettingsError( - 'The attributes name, provider_class, and control_location ' - 'are required') - - def __eq__(self, other): - return (self.name == other.name - and self.provider_class == other.provider_class - and self.control_location == other.control_location - and self.cipher == other.cipher - and self.key_size == other.key_size) - - -def map_control_location(control_location): - """ - Takes a the protocol value maps it to the Consumer enum. When None return - None - :param control_location: the value to map to the Enum - :return: a ControlLocation enum object - :raise: Exception if control_location parameter is invalid - """ - if not control_location: - return None - elif isinstance(control_location, ControlLocation): - return control_location - else: - proto_str = str(control_location) - if proto_str == 'front-end': - return ControlLocation.front_end - elif proto_str == 'back-end': - return ControlLocation.back_end - else: - raise VolumeTypeSettingsError('Invalid Consumer - ' + proto_str) - - -class VolumeTypeSettingsError(Exception): + +class VolumeTypeEncryptionSettings(VolumeTypeEncryptionConfig): """ - Exception to be thrown when an volume settings are incorrect + Class to hold the configuration settings required for creating OpenStack + Volume Type Encryption objects + deprecated """ - def __init__(self, message): - Exception.__init__(self, message) + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.volume_type.VolumeTypeEncryptionConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) class VolumeTypeCreationError(Exception): diff --git a/snaps/openstack/openstack_creator.py b/snaps/openstack/openstack_creator.py index 945a78b..0caee9a 100644 --- a/snaps/openstack/openstack_creator.py +++ b/snaps/openstack/openstack_creator.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. from snaps.domain.creator import CloudObject -from snaps.openstack.utils import (nova_utils, neutron_utils, keystone_utils, - cinder_utils) +from snaps.openstack.utils import ( + nova_utils, neutron_utils, keystone_utils, cinder_utils, magnum_utils) __author__ = 'spisarski' @@ -132,3 +132,26 @@ class OpenStackVolumeObject(OpenStackCloudObject): def clean(self): raise NotImplementedError('Do not override abstract method') + + +class OpenStackMagnumObject(OpenStackCloudObject): + """ + Abstract class for all OpenStack compute creators + """ + + def __init__(self, os_creds): + """ + Constructor + :param os_creds: the OpenStack credentials object + """ + super(OpenStackMagnumObject, self).__init__(os_creds) + self._magnum = None + + def initialize(self): + self._magnum = magnum_utils.magnum_client(self._os_creds) + + def create(self): + raise NotImplementedError('Do not override abstract method') + + def clean(self): + raise NotImplementedError('Do not override abstract method') diff --git a/snaps/openstack/os_credentials.py b/snaps/openstack/os_credentials.py index cff2dd8..2553410 100644 --- a/snaps/openstack/os_credentials.py +++ b/snaps/openstack/os_credentials.py @@ -44,6 +44,8 @@ class OSCreds: clients :param volume_api_version: The OpenStack's API version to use for Cinder clients + :param magnum_api_version: The OpenStack's API version to use + for magnum clients :param user_domain_id: Used for v3 APIs (default='default') :param user_domain_name: Used for v3 APIs (default='Default') :param project_domain_id: Used for v3 APIs (default='default') @@ -51,10 +53,8 @@ class OSCreds: :param interface: Used to specify the endpoint type for keystone as public, admin, internal :param proxy_settings: instance of os_credentials.ProxySettings class - :param cacert: Default to be True for http, or the certification file - is specified for https verification, or set to be False - to disable server certificate verification without cert - file + :param cacert: True for https or the certification file for https + verification (default=False) :param region_name: the region (optional default = None) """ self.username = kwargs.get('username') @@ -90,8 +90,12 @@ class OSCreds: if kwargs.get('volume_api_version') is None: self.volume_api_version = cinder_utils.VERSION_2 else: - self.volume_api_version = float( - kwargs['volume_api_version']) + self.volume_api_version = float(kwargs['volume_api_version']) + + if kwargs.get('magnum_api_version') is None: + self.magnum_api_version = 1 + else: + self.magnum_api_version = float(kwargs['magnum_api_version']) self.user_domain_id = kwargs.get('user_domain_id', 'default') @@ -200,7 +204,6 @@ class ProxySettings: :param port: the HTTP proxy port :param https_host: the HTTPS proxy host (defaults to host) :param https_port: the HTTPS proxy port (defaults to port) - :param port: the HTTP proxy port :param ssh_proxy_cmd: the SSH proxy command string (optional) """ self.host = kwargs.get('host') diff --git a/snaps/openstack/tests/cluster_template_tests.py b/snaps/openstack/tests/cluster_template_tests.py new file mode 100644 index 0000000..355467d --- /dev/null +++ b/snaps/openstack/tests/cluster_template_tests.py @@ -0,0 +1,282 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from magnumclient.common.apiclient.exceptions import BadRequest + +from snaps.config.cluster_template import ClusterTemplateConfig +from snaps.config.flavor import FlavorConfig +from snaps.config.keypair import KeypairConfig +from snaps.openstack.cluster_template import OpenStackClusterTemplate +from snaps.openstack.create_flavor import OpenStackFlavor +from snaps.openstack.create_image import OpenStackImage +from snaps.openstack.create_keypairs import OpenStackKeypair +from snaps.openstack.tests import openstack_tests + +try: + from urllib.request import URLError +except ImportError: + from urllib2 import URLError + +import logging +import uuid + +from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase +from snaps.openstack.utils import magnum_utils + +__author__ = 'spisarski' + +logger = logging.getLogger('cluster_template_tests') + + +class CreateClusterTemplateTests(OSIntegrationTestCase): + """ + Test for the OpenStackClusterTemplate class defined in py + without any QoS Specs or Encryption + """ + + def setUp(self): + """ + Instantiates the CreateClusterTemplate object that is responsible for + downloading and creating an OS template config file within OpenStack + """ + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.cluster_type_name = self.guid + '-cluster-type' + self.magnum = magnum_utils.magnum_client(self.os_creds) + + metadata = self.image_metadata + if not metadata: + metadata = dict() + if 'extra_properties' not in metadata: + metadata['extra_properties'] = dict() + metadata['extra_properties']['os_distro'] = 'cirros' + + os_image_settings = openstack_tests.cirros_image_settings( + name=self.guid + '-image', image_metadata=metadata) + + self.image_creator = OpenStackImage(self.os_creds, os_image_settings) + + self.flavor_creator = OpenStackFlavor( + self.os_creds, FlavorConfig( + name=self.guid + '-flavor', ram=512, disk=10, vcpus=1)) + + keypair_priv_filepath = 'tmp/' + self.guid + keypair_pub_filepath = keypair_priv_filepath + '.pub' + + self.keypair_creator = OpenStackKeypair( + self.os_creds, KeypairConfig( + name=self.guid + '-keypair', + public_filepath=keypair_pub_filepath, + private_filepath=keypair_priv_filepath)) + + self.cluster_template_creator = None + + self.cluster_template_config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name) + + try: + self.image_creator.create() + self.flavor_creator.create() + self.keypair_creator.create() + except: + self.tearDown() + raise + + def tearDown(self): + """ + Cleans the template config + """ + if self.cluster_template_creator: + try: + self.cluster_template_creator.clean() + except: + pass + if self.keypair_creator: + try: + self.keypair_creator.clean() + except: + pass + if self.flavor_creator: + try: + self.flavor_creator.clean() + except: + pass + if self.image_creator: + try: + self.image_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_create_cluster_template(self): + """ + Tests the creation of an OpenStack cluster template. + """ + # Create ClusterTemplate + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, self.cluster_template_config) + created_cluster_template = self.cluster_template_creator.create() + self.assertIsNotNone(created_cluster_template) + self.assertEqual(self.cluster_template_config.name, + created_cluster_template.name) + + retrieved_cluster_template1 = magnum_utils.get_cluster_template( + self.magnum, template_config=self.cluster_template_config) + self.assertIsNotNone(retrieved_cluster_template1) + self.assertEqual(created_cluster_template, retrieved_cluster_template1) + + retrieved_cluster_template2 = magnum_utils.get_cluster_template_by_id( + self.magnum, created_cluster_template.id) + self.assertEqual(created_cluster_template, retrieved_cluster_template2) + + def test_create_delete_cluster_template(self): + """ + Tests the creation then deletion of an OpenStack template config to + ensure clean() does not raise an Exception. + """ + # Create ClusterTemplate + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, self.cluster_template_config) + created_cluster_template = self.cluster_template_creator.create() + self.assertIsNotNone(created_cluster_template) + + self.cluster_template_creator.clean() + + tmplt = magnum_utils.get_cluster_template( + self.magnum, template_name=self.cluster_template_config.name) + self.assertIsNone(tmplt) + + def test_create_same_cluster_template(self): + """ + Tests the creation of an OpenStack cluster_template when one already + exists. + """ + # Create ClusterTemplate + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, self.cluster_template_config) + cluster_template1 = self.cluster_template_creator.create() + + retrieved_cluster_template = magnum_utils.get_cluster_template( + self.magnum, template_config=self.cluster_template_config) + self.assertEqual(cluster_template1, retrieved_cluster_template) + + # Should be retrieving the instance data + os_cluster_template_2 = OpenStackClusterTemplate( + self.os_creds, self.cluster_template_config) + cluster_template2 = os_cluster_template_2.create() + self.assertEqual(cluster_template2, cluster_template2) + + def test_create_cluster_template_bad_flavor(self): + """ + Tests the creation of an OpenStack cluster template raises an + exception with an invalid flavor. + """ + # Create ClusterTemplate + cluster_template_config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor='foo') + + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, cluster_template_config) + + with self.assertRaises(BadRequest): + self.cluster_template_creator.create() + + def test_create_cluster_template_bad_master_flavor(self): + """ + Tests the creation of an OpenStack cluster template raises an + exception with an invalid master flavor. + """ + # Create ClusterTemplate + cluster_template_config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name, + master_flavor='foo') + + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, cluster_template_config) + + with self.assertRaises(BadRequest): + self.cluster_template_creator.create() + + def test_create_cluster_template_bad_image(self): + """ + Tests the creation of an OpenStack cluster template raises an + exception with an invalid image. + """ + # Create ClusterTemplate + cluster_template_config = ClusterTemplateConfig( + name=self.cluster_type_name, + image='foo', + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name) + + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, cluster_template_config) + + with self.assertRaises(BadRequest): + self.cluster_template_creator.create() + + def test_create_cluster_template_bad_network_driver(self): + """ + Tests the creation of an OpenStack cluster template raises an + exception with an invalid keypair. + """ + # Create ClusterTemplate + cluster_template_config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name, + network_driver='foo') + + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, cluster_template_config) + + with self.assertRaises(BadRequest): + self.cluster_template_creator.create() + + def test_create_cluster_template_bad_volume_driver(self): + """ + Tests the creation of an OpenStack cluster template raises an + exception with an invalid keypair. + """ + # Create ClusterTemplate + cluster_template_config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name, + volume_driver='foo') + + self.cluster_template_creator = OpenStackClusterTemplate( + self.os_creds, cluster_template_config) + + with self.assertRaises(BadRequest): + self.cluster_template_creator.create() diff --git a/snaps/openstack/tests/conf/os_credentials_tests.py b/snaps/openstack/tests/conf/os_credentials_tests.py index 5efb32c..192be86 100644 --- a/snaps/openstack/tests/conf/os_credentials_tests.py +++ b/snaps/openstack/tests/conf/os_credentials_tests.py @@ -18,6 +18,7 @@ import unittest from snaps.openstack.os_credentials import ( OSCredsError, OSCreds, ProxySettings, ProxySettingsError) +from snaps.openstack.utils import cinder_utils __author__ = 'spisarski' @@ -147,6 +148,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(2, os_creds.image_api_version) self.assertEqual(2, os_creds.compute_api_version) self.assertEqual(1, os_creds.heat_api_version) + self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version) + self.assertEqual(1, os_creds.magnum_api_version) self.assertEqual('default', os_creds.user_domain_id) self.assertEqual('Default', os_creds.user_domain_name) self.assertEqual('default', os_creds.project_domain_id) @@ -168,6 +171,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(2, os_creds.image_api_version) self.assertEqual(2, os_creds.compute_api_version) self.assertEqual(1, os_creds.heat_api_version) + self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version) + self.assertEqual(1, os_creds.magnum_api_version) self.assertEqual('default', os_creds.user_domain_id) self.assertEqual('Default', os_creds.user_domain_name) self.assertEqual('default', os_creds.project_domain_id) @@ -183,6 +188,7 @@ class OSCredsUnitTests(unittest.TestCase): 'auth_url': 'http://foo.bar:5000/v2', 'project_name': 'hello', 'identity_api_version': '5', 'image_api_version': '6', 'compute_api_version': '7', 'heat_api_version': '8.0', + 'volume_api_version': '9.5', 'magnum_api_version': '10.6', 'cacert': 'true', 'region_name': 'test_region'}) self.assertEqual('foo', os_creds.username) self.assertEqual('bar', os_creds.password) @@ -192,6 +198,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(6, os_creds.image_api_version) self.assertEqual(7, os_creds.compute_api_version) self.assertEqual(8.0, os_creds.heat_api_version) + self.assertEqual(9.5, os_creds.volume_api_version) + self.assertEqual(10.6, os_creds.magnum_api_version) self.assertEqual('default', os_creds.user_domain_id) self.assertEqual('Default', os_creds.user_domain_name) self.assertEqual('default', os_creds.project_domain_id) @@ -207,6 +215,7 @@ class OSCredsUnitTests(unittest.TestCase): 'auth_url': 'http://foo.bar:5000/v2', 'project_name': 'hello', 'identity_api_version': 5, 'image_api_version': 6, 'compute_api_version': 7, 'heat_api_version': 8.0, + 'volume_api_version': 9.5, 'magnum_api_version': 10.6, 'cacert': True, 'region_name': 'test_region'}) self.assertEqual('foo', os_creds.username) self.assertEqual('bar', os_creds.password) @@ -216,6 +225,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(6, os_creds.image_api_version) self.assertEqual(7, os_creds.compute_api_version) self.assertEqual(8.0, os_creds.heat_api_version) + self.assertEqual(9.5, os_creds.volume_api_version) + self.assertEqual(10.6, os_creds.magnum_api_version) self.assertEqual('default', os_creds.user_domain_id) self.assertEqual('Default', os_creds.user_domain_name) self.assertEqual('default', os_creds.project_domain_id) @@ -238,6 +249,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(2, os_creds.image_api_version) self.assertEqual(2, os_creds.compute_api_version) self.assertEqual(1, os_creds.heat_api_version) + self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version) + self.assertEqual(1, os_creds.magnum_api_version) self.assertEqual('default', os_creds.user_domain_id) self.assertEqual('Default', os_creds.user_domain_name) self.assertEqual('default', os_creds.project_domain_id) @@ -266,6 +279,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(2, os_creds.image_api_version) self.assertEqual(2, os_creds.compute_api_version) self.assertEqual(1, os_creds.heat_api_version) + self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version) + self.assertEqual(1, os_creds.magnum_api_version) self.assertEqual('domain1', os_creds.user_domain_id) self.assertEqual('domain2', os_creds.user_domain_name) self.assertEqual('domain3', os_creds.project_domain_id) @@ -291,6 +306,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(2, os_creds.image_api_version) self.assertEqual(2, os_creds.compute_api_version) self.assertEqual(1, os_creds.heat_api_version) + self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version) + self.assertEqual(1, os_creds.magnum_api_version) self.assertEqual('domain1', os_creds.user_domain_id) self.assertEqual('domain2', os_creds.user_domain_name) self.assertEqual('domain3', os_creds.project_domain_id) @@ -315,6 +332,8 @@ class OSCredsUnitTests(unittest.TestCase): self.assertEqual(2, os_creds.image_api_version) self.assertEqual(2, os_creds.compute_api_version) self.assertEqual(1, os_creds.heat_api_version) + self.assertEqual(cinder_utils.VERSION_2, os_creds.volume_api_version) + self.assertEqual(1, os_creds.magnum_api_version) self.assertEqual('default', os_creds.user_domain_id) self.assertEqual('Default', os_creds.user_domain_name) self.assertEqual('default', os_creds.project_domain_id) diff --git a/snaps/openstack/tests/conf/os_env.yaml.template b/snaps/openstack/tests/conf/os_env.yaml.template index e88bfaa..36e3cfd 100644 --- a/snaps/openstack/tests/conf/os_env.yaml.template +++ b/snaps/openstack/tests/conf/os_env.yaml.template @@ -1,17 +1,17 @@ # Keystone v2.0 #username: admin #password: admin -#os_auth_url: http://<host>:<port>/v2.0/ +#os_auth_url: http://<host>:<port>/ #project_name: admin #ext_net: <external network name> #http_proxy: <host>:<port> #ssh_proxy_cmd: '/usr/local/bin/corkscrew <host> <port> %h %p' #ssh_proxy_cmd: 'ssh <host> nc %h %p' -# Keystone v2.0 +# Keystone v3 #username: admin #password: admin -#os_auth_url: http://<host>:<port>/v3 +#os_auth_url: http://<host>:<port>/ #project_name: admin #identity_api_version: 3 #ext_net: <external network name>
\ No newline at end of file diff --git a/snaps/openstack/tests/create_flavor_tests.py b/snaps/openstack/tests/create_flavor_tests.py index 4852d06..f84355d 100644 --- a/snaps/openstack/tests/create_flavor_tests.py +++ b/snaps/openstack/tests/create_flavor_tests.py @@ -15,9 +15,9 @@ import unittest import uuid +from snaps.config.flavor import FlavorConfig, FlavorConfigError from snaps.openstack import create_flavor -from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor, \ - FlavorSettingsError +from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings from snaps.openstack.tests.os_source_file_test import OSComponentTestCase from snaps.openstack.utils import nova_utils @@ -30,169 +30,169 @@ class FlavorSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings() def test_empty_config(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(config=dict()) def test_name_only(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo') def test_config_with_name_only(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(config={'name': 'foo'}) def test_name_ram_only(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1) def test_config_with_name_ram_only(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(config={'name': 'foo', 'ram': 1}) def test_name_ram_disk_only(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=1) def test_config_with_name_ram_disk_only(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(config={'name': 'foo', 'ram': 1, 'disk': 1}) def test_ram_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram='bar', disk=2, vcpus=3, ephemeral=4, swap=5, rxtx_factor=6.0, is_public=False) def test_config_ram_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 'bar', 'disk': 2, 'vcpus': 3, 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 6.0, 'is_public': False}) def test_ram_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1.5, disk=2, vcpus=3, ephemeral=4, swap=5, rxtx_factor=6.0, is_public=False) def test_config_ram_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1.5, 'disk': 2, 'vcpus': 3, 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 6.0, 'is_public': False}) def test_disk_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk='bar', vcpus=3, ephemeral=4, swap=5, rxtx_factor=6.0, is_public=False) def test_config_disk_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 'bar', 'vcpus': 3, 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 6.0, 'is_public': False}) def test_disk_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2.5, vcpus=3, ephemeral=4, swap=5, rxtx_factor=6.0, is_public=False) def test_config_disk_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2.5, 'vcpus': 3, 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 6.0, 'is_public': False}) def test_vcpus_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2, vcpus='bar', ephemeral=4, swap=5, rxtx_factor=6.0, is_public=False) def test_config_vcpus_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 'bar', 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 6.0, 'is_public': False}) def test_ephemeral_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral='bar', swap=5, rxtx_factor=6.0, is_public=False) def test_config_ephemeral_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, 'ephemeral': 'bar', 'swap': 5, 'rxtx_factor': 6.0, 'is_public': False}) def test_ephemeral_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4.5, swap=5, rxtx_factor=6.0, is_public=False) def test_config_ephemeral_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, 'ephemeral': 4.5, 'swap': 5, 'rxtx_factor': 6.0, 'is_public': False}) def test_swap_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, swap='bar', rxtx_factor=6.0, is_public=False) def test_config_swap_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, 'ephemeral': 4, 'swap': 'bar', 'rxtx_factor': 6.0, 'is_public': False}) def test_swap_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, swap=5.5, rxtx_factor=6.0, is_public=False) def test_config_swap_float(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, 'ephemeral': 4, 'swap': 5.5, 'rxtx_factor': 6.0, 'is_public': False}) def test_rxtx_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, swap=5, rxtx_factor='bar', is_public=False) def test_config_rxtx_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, 'ephemeral': 4, 'swap': 5, 'rxtx_factor': 'bar', 'is_public': False}) def test_is_pub_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings(name='foo', ram=1, disk=2, vcpus=3, ephemeral=4, swap=5, rxtx_factor=6.0, is_public='bar') def test_config_is_pub_string(self): - with self.assertRaises(FlavorSettingsError): + with self.assertRaises(FlavorConfigError): FlavorSettings( config={'name': 'foo', 'ram': 1, 'disk': 2, 'vcpus': 3, 'ephemeral': 4, 'swap': 5, @@ -291,8 +291,8 @@ class CreateFlavorTests(OSComponentTestCase): Tests the creation of an OpenStack flavor. """ # Create Flavor - flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1, - vcpus=1) + flavor_settings = FlavorConfig( + name=self.flavor_name, ram=1, disk=1, vcpus=1) self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings) flavor = self.flavor_creator.create() self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor)) @@ -303,8 +303,8 @@ class CreateFlavorTests(OSComponentTestCase): to ensure it has not been done twice. """ # Create Flavor - flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1, - vcpus=1) + flavor_settings = FlavorConfig( + name=self.flavor_name, ram=1, disk=1, vcpus=1) self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings) flavor = self.flavor_creator.create() self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor)) @@ -319,8 +319,8 @@ class CreateFlavorTests(OSComponentTestCase): Tests the creation and cleanup of an OpenStack flavor. """ # Create Flavor - flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1, - vcpus=1) + flavor_settings = FlavorConfig( + name=self.flavor_name, ram=1, disk=1, vcpus=1) self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings) flavor = self.flavor_creator.create() self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor)) @@ -339,8 +339,8 @@ class CreateFlavorTests(OSComponentTestCase): raise any exceptions. """ # Create Flavor - flavor_settings = FlavorSettings(name=self.flavor_name, ram=1, disk=1, - vcpus=1) + flavor_settings = FlavorConfig( + name=self.flavor_name, ram=1, disk=1, vcpus=1) self.flavor_creator = OpenStackFlavor(self.os_creds, flavor_settings) flavor = self.flavor_creator.create() self.assertTrue(validate_flavor(self.nova, flavor_settings, flavor)) @@ -362,7 +362,7 @@ class CreateFlavorTests(OSComponentTestCase): raise any exceptions. """ # Create Flavor - flavor_settings = FlavorSettings( + flavor_settings = FlavorConfig( name=self.flavor_name, ram=1, disk=1, vcpus=1, ephemeral=2, swap=3, rxtx_factor=2.2, is_public=False, metadata=create_flavor.MEM_PAGE_SIZE_ANY) @@ -399,7 +399,7 @@ def validate_flavor(nova, flavor_settings, flavor): equals = False break - swap = str() + swap = None if flavor_settings.swap != 0: swap = flavor_settings.swap diff --git a/snaps/openstack/tests/create_image_tests.py b/snaps/openstack/tests/create_image_tests.py index f70a71c..9965f87 100644 --- a/snaps/openstack/tests/create_image_tests.py +++ b/snaps/openstack/tests/create_image_tests.py @@ -27,9 +27,10 @@ import uuid import os from snaps import file_utils +from snaps.config.image import ImageConfigError from snaps.openstack import create_image -from snaps.openstack.create_image import (ImageSettings, ImageCreationError, - ImageSettingsError) +from snaps.openstack.create_image import ImageSettings, ImageCreationError +from snaps.config.image import ImageConfig from snaps.openstack.tests import openstack_tests from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase from snaps.openstack.utils import glance_utils @@ -42,38 +43,40 @@ logger = logging.getLogger('create_image_tests') class ImageSettingsUnitTests(unittest.TestCase): """ Tests the construction of the ImageSettings class + To be removed once the deprecated class ImageSettings is finally removed + from the source tree """ def test_no_params(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings() def test_empty_config(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings(**dict()) def test_name_only(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings(name='foo') def test_config_with_name_only(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings(**{'name': 'foo'}) def test_name_user_only(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings(name='foo', image_user='bar') def test_config_with_name_user_only(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings(**{'name': 'foo', 'image_user': 'bar'}) def test_name_user_format_only(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings(name='foo', image_user='bar', img_format='qcow2') def test_config_with_name_user_format_only(self): - with self.assertRaises(ImageSettingsError): + with self.assertRaises(ImageConfigError): ImageSettings( **{'name': 'foo', 'image_user': 'bar', 'format': 'qcow2'}) @@ -448,8 +451,8 @@ class CreateImageSuccessTests(OSIntegrationTestCase): self.assertEqual(image1.properties, retrieved_image.properties) # Should be retrieving the instance data - image_2_settings = ImageSettings(name=self.image_settings.name, - image_user='foo', exists=True) + image_2_settings = ImageConfig(name=self.image_settings.name, + image_user='foo', exists=True) os_image_2 = create_image.OpenStackImage(self.os_creds, image_2_settings) image2 = os_image_2.create() @@ -478,8 +481,8 @@ class CreateImageNegativeTests(OSIntegrationTestCase): Expect an ImageCreationError when the image name does not exist when a file or URL has not been configured """ - os_image_settings = ImageSettings(name='foo', image_user='bar', - exists=True) + os_image_settings = ImageConfig(name='foo', image_user='bar', + exists=True) self.image_creator = create_image.OpenStackImage(self.os_creds, os_image_settings) @@ -497,10 +500,11 @@ class CreateImageNegativeTests(OSIntegrationTestCase): name=self.image_name) self.image_creator = create_image.OpenStackImage( self.os_creds, - create_image.ImageSettings(name=os_image_settings.name, - image_user=os_image_settings.image_user, - img_format=os_image_settings.format, - url="http://foo.bar")) + ImageConfig( + name=os_image_settings.name, + image_user=os_image_settings.image_user, + img_format=os_image_settings.format, + url="http://foo.bar")) try: self.image_creator.create() @@ -519,10 +523,10 @@ class CreateImageNegativeTests(OSIntegrationTestCase): name=self.image_name) self.image_creator = create_image.OpenStackImage( self.os_creds, - create_image.ImageSettings(name=os_image_settings.name, - image_user=os_image_settings.image_user, - img_format='foo', - url=os_image_settings.url)) + ImageConfig( + name=os_image_settings.name, + image_user=os_image_settings.image_user, + img_format='foo', url=os_image_settings.url)) with self.assertRaises(Exception): self.image_creator.create() @@ -535,10 +539,10 @@ class CreateImageNegativeTests(OSIntegrationTestCase): name=self.image_name) self.image_creator = create_image.OpenStackImage( self.os_creds, - create_image.ImageSettings(name=os_image_settings.name, - image_user=os_image_settings.image_user, - img_format=os_image_settings.format, - image_file="/foo/bar.qcow")) + ImageConfig( + name=os_image_settings.name, + image_user=os_image_settings.image_user, + img_format=os_image_settings.format, image_file="/foo/bar.qcow")) with self.assertRaises(IOError): self.image_creator.create() diff --git a/snaps/openstack/tests/create_instance_tests.py b/snaps/openstack/tests/create_instance_tests.py index 9c872bc..55da144 100644 --- a/snaps/openstack/tests/create_instance_tests.py +++ b/snaps/openstack/tests/create_instance_tests.py @@ -21,25 +21,36 @@ import uuid import os from neutronclient.common.exceptions import InvalidIpForSubnetClient +from novaclient.exceptions import BadRequest from snaps import file_utils -from snaps.openstack import create_network, create_router -from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings -from snaps.openstack.create_image import OpenStackImage, ImageSettings +from snaps.config.flavor import FlavorConfig +from snaps.config.image import ImageConfig +from snaps.config.keypair import KeypairConfig +from snaps.config.network import PortConfig, NetworkConfig, SubnetConfig +from snaps.config.router import RouterConfig +from snaps.config.security_group import ( + Protocol, SecurityGroupRuleConfig, Direction, SecurityGroupConfig) +from snaps.config.vm_inst import ( + VmInstanceConfig, FloatingIpConfig, VmInstanceConfigError, + FloatingIpConfigError) +from snaps.config.volume import VolumeConfig +from snaps.openstack import create_network, create_router, create_instance +from snaps.openstack.create_flavor import OpenStackFlavor +from snaps.openstack.create_image import OpenStackImage from snaps.openstack.create_instance import ( - VmInstanceSettings, OpenStackVmInstance, FloatingIpSettings, - VmInstanceSettingsError, FloatingIpSettingsError) -from snaps.openstack.create_keypairs import OpenStackKeypair, KeypairSettings -from snaps.openstack.create_network import ( - OpenStackNetwork, PortSettings, NetworkSettings) -from snaps.openstack.create_router import OpenStackRouter, RouterSettings -from snaps.openstack.create_security_group import ( - SecurityGroupSettings, OpenStackSecurityGroup, SecurityGroupRuleSettings, - Direction, Protocol) + VmInstanceSettings, OpenStackVmInstance, FloatingIpSettings) +from snaps.openstack.create_keypairs import OpenStackKeypair +from snaps.openstack.create_network import OpenStackNetwork +from snaps.openstack.create_router import OpenStackRouter +from snaps.openstack.create_security_group import OpenStackSecurityGroup +from snaps.openstack.create_volume import OpenStackVolume from snaps.openstack.tests import openstack_tests, validation_utils from snaps.openstack.tests.os_source_file_test import ( OSIntegrationTestCase, OSComponentTestCase) from snaps.openstack.utils import nova_utils +from snaps.openstack.utils.nova_utils import RebootType +from snaps.openstack.utils import nova_utils, settings_utils, neutron_utils __author__ = 'spisarski' @@ -54,31 +65,31 @@ class VmInstanceSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(VmInstanceSettingsError): + with self.assertRaises(VmInstanceConfigError): VmInstanceSettings() def test_empty_config(self): - with self.assertRaises(VmInstanceSettingsError): + with self.assertRaises(VmInstanceConfigError): VmInstanceSettings(config=dict()) def test_name_only(self): - with self.assertRaises(VmInstanceSettingsError): + with self.assertRaises(VmInstanceConfigError): VmInstanceSettings(name='foo') def test_config_with_name_only(self): - with self.assertRaises(VmInstanceSettingsError): + with self.assertRaises(VmInstanceConfigError): VmInstanceSettings(config={'name': 'foo'}) def test_name_flavor_only(self): - with self.assertRaises(VmInstanceSettingsError): + with self.assertRaises(VmInstanceConfigError): VmInstanceSettings(name='foo', flavor='bar') def test_config_with_name_flavor_only(self): - with self.assertRaises(VmInstanceSettingsError): + with self.assertRaises(VmInstanceConfigError): VmInstanceSettings(config={'name': 'foo', 'flavor': 'bar'}) def test_name_flavor_port_only(self): - port_settings = PortSettings(name='foo-port', network_name='bar-net') + port_settings = PortConfig(name='foo-port', network_name='bar-net') settings = VmInstanceSettings(name='foo', flavor='bar', port_settings=[port_settings]) self.assertEqual('foo', settings.name) @@ -93,9 +104,10 @@ class VmInstanceSettingsUnitTests(unittest.TestCase): self.assertEqual(300, settings.vm_delete_timeout) self.assertEqual(180, settings.ssh_connect_timeout) self.assertIsNone(settings.availability_zone) + self.assertIsNone(settings.volume_names) def test_config_with_name_flavor_port_only(self): - port_settings = PortSettings(name='foo-port', network_name='bar-net') + port_settings = PortConfig(name='foo-port', network_name='bar-net') settings = VmInstanceSettings( **{'name': 'foo', 'flavor': 'bar', 'ports': [port_settings]}) self.assertEqual('foo', settings.name) @@ -110,20 +122,20 @@ class VmInstanceSettingsUnitTests(unittest.TestCase): self.assertEqual(300, settings.vm_delete_timeout) self.assertEqual(180, settings.ssh_connect_timeout) self.assertIsNone(settings.availability_zone) + self.assertIsNone(settings.volume_names) def test_all(self): - port_settings = PortSettings(name='foo-port', network_name='bar-net') + port_settings = PortConfig(name='foo-port', network_name='bar-net') fip_settings = FloatingIpSettings(name='foo-fip', port_name='bar-port', router_name='foo-bar-router') - settings = VmInstanceSettings(name='foo', flavor='bar', - port_settings=[port_settings], - security_group_names=['sec_grp_1'], - floating_ip_settings=[fip_settings], - sudo_user='joe', vm_boot_timeout=999, - vm_delete_timeout=333, - ssh_connect_timeout=111, - availability_zone='server name') + settings = VmInstanceSettings( + name='foo', flavor='bar', port_settings=[port_settings], + security_group_names=['sec_grp_1'], + floating_ip_settings=[fip_settings], sudo_user='joe', + vm_boot_timeout=999, vm_delete_timeout=333, + ssh_connect_timeout=111, availability_zone='server name', + volume_names=['vol1']) self.assertEqual('foo', settings.name) self.assertEqual('bar', settings.flavor) self.assertEqual(1, len(settings.port_settings)) @@ -142,9 +154,10 @@ class VmInstanceSettingsUnitTests(unittest.TestCase): self.assertEqual(333, settings.vm_delete_timeout) self.assertEqual(111, settings.ssh_connect_timeout) self.assertEqual('server name', settings.availability_zone) + self.assertEqual('vol1', settings.volume_names[0]) def test_config_all(self): - port_settings = PortSettings(name='foo-port', network_name='bar-net') + port_settings = PortConfig(name='foo-port', network_name='bar-net') fip_settings = FloatingIpSettings(name='foo-fip', port_name='bar-port', router_name='foo-bar-router') @@ -153,7 +166,8 @@ class VmInstanceSettingsUnitTests(unittest.TestCase): 'security_group_names': ['sec_grp_1'], 'floating_ips': [fip_settings], 'sudo_user': 'joe', 'vm_boot_timeout': 999, 'vm_delete_timeout': 333, - 'ssh_connect_timeout': 111, 'availability_zone': 'server name'}) + 'ssh_connect_timeout': 111, 'availability_zone': 'server name', + 'volume_names': ['vol2']}) self.assertEqual('foo', settings.name) self.assertEqual('bar', settings.flavor) self.assertEqual(1, len(settings.port_settings)) @@ -171,6 +185,7 @@ class VmInstanceSettingsUnitTests(unittest.TestCase): self.assertEqual(333, settings.vm_delete_timeout) self.assertEqual(111, settings.ssh_connect_timeout) self.assertEqual('server name', settings.availability_zone) + self.assertEqual('vol2', settings.volume_names[0]) class FloatingIpSettingsUnitTests(unittest.TestCase): @@ -179,35 +194,35 @@ class FloatingIpSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings() def test_empty_config(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings(**dict()) def test_name_only(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings(name='foo') def test_config_with_name_only(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings(**{'name': 'foo'}) def test_name_port_only(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings(name='foo', port_name='bar') def test_config_with_name_port_only(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings(**{'name': 'foo', 'port_name': 'bar'}) def test_name_router_only(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings(name='foo', router_name='bar') def test_config_with_name_router_only(self): - with self.assertRaises(FloatingIpSettingsError): + with self.assertRaises(FloatingIpConfigError): FloatingIpSettings(**{'name': 'foo', 'router_name': 'bar'}) def test_name_port_router_name_only(self): @@ -291,8 +306,10 @@ class SimpleHealthCheck(OSIntegrationTestCase): self.inst_creator = None self.priv_net_config = openstack_tests.get_priv_net_config( - net_name=guid + '-priv-net', subnet_name=guid + '-priv-subnet') - self.port_settings = PortSettings( + net_name=guid + '-priv-net', + subnet_name=guid + '-priv-subnet', + netconf_override=self.netconf_override) + self.port_settings = PortConfig( name=self.port_1_name, network_name=self.priv_net_config.network_settings.name) @@ -313,10 +330,14 @@ class SimpleHealthCheck(OSIntegrationTestCase): self.network_creator.create() # Create Flavor + self.flavor_ram = 256 + if (self.flavor_metadata and + self.flavor_metadata.get('hw:mem_page_size') == 'large'): + self.flavor_ram = 1024 self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10, - vcpus=1, metadata=self.flavor_metadata)) + FlavorConfig(name=guid + '-flavor-name', ram=self.flavor_ram, + disk=10, vcpus=1, metadata=self.flavor_metadata)) self.flavor_creator.create() except Exception as e: self.tearDown() @@ -365,7 +386,7 @@ class SimpleHealthCheck(OSIntegrationTestCase): Tests the creation of an OpenStack instance with a single port and ensures that it's assigned IP address is the actual. """ - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -399,12 +420,14 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase): guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) self.vm_inst_name = guid + '-inst' self.nova = nova_utils.nova_client(self.os_creds) + self.neutron = neutron_utils.neutron_client(self.os_creds) os_image_settings = openstack_tests.cirros_image_settings( name=guid + '-image', image_metadata=self.image_metadata) net_config = openstack_tests.get_priv_net_config( net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet', - router_name=guid + '-pub-router', external_net=self.ext_net_name) + router_name=guid + '-pub-router', external_net=self.ext_net_name, + netconf_override=self.netconf_override) # Initialize for tearDown() self.image_creator = None @@ -422,8 +445,8 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10, - vcpus=2, metadata=self.flavor_metadata)) + FlavorConfig(name=guid + '-flavor-name', ram=256, disk=10, + vcpus=2, metadata=self.flavor_metadata)) self.flavor_creator.create() # Create Network @@ -431,7 +454,7 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase): self.os_creds, net_config.network_settings) self.network_creator.create() - self.port_settings = PortSettings( + self.port_settings = PortConfig( name=guid + '-port', network_name=net_config.network_settings.name) @@ -481,7 +504,7 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase): Tests the creation of an OpenStack instance with a single port with a static IP without a Floating IP. """ - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -492,14 +515,14 @@ class CreateInstanceSimpleTests(OSIntegrationTestCase): vm_inst = self.inst_creator.create() self.assertIsNotNone(nova_utils.get_server( - self.nova, vm_inst_settings=instance_settings)) + self.nova, self.neutron, vm_inst_settings=instance_settings)) # Delete instance nova_utils.delete_vm_instance(self.nova, vm_inst) self.assertTrue(self.inst_creator.vm_deleted(block=True)) self.assertIsNone(nova_utils.get_server( - self.nova, vm_inst_settings=instance_settings)) + self.nova, self.neutron, vm_inst_settings=instance_settings)) # Exception should not be thrown self.inst_creator.clean() @@ -538,7 +561,8 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): self.pub_net_config = openstack_tests.get_pub_net_config( net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet', - router_name=guid + '-pub-router', external_net=self.ext_net_name) + router_name=guid + '-pub-router', external_net=self.ext_net_name, + netconf_override=self.netconf_override) os_image_settings = openstack_tests.cirros_image_settings( name=guid + '-image', image_metadata=self.image_metadata) try: @@ -560,30 +584,28 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10, - vcpus=2, metadata=self.flavor_metadata)) + FlavorConfig(name=guid + '-flavor-name', ram=256, disk=10, + vcpus=2, metadata=self.flavor_metadata)) self.flavor_creator.create() self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.keypair_pub_filepath, private_filepath=self.keypair_priv_filepath)) self.keypair_creator.create() sec_grp_name = guid + '-sec-grp' - rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.icmp) - rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.tcp, - port_range_min=22, - port_range_max=22) + rule1 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.icmp) + rule2 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.tcp, port_range_min=22, port_range_max=22) self.sec_grp_creator = OpenStackSecurityGroup( self.os_creds, - SecurityGroupSettings(name=sec_grp_name, - rule_settings=[rule1, rule2])) + SecurityGroupConfig( + name=sec_grp_name, rule_settings=[rule1, rule2])) self.sec_grp_creator.create() except Exception as e: self.tearDown() @@ -609,12 +631,6 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): 'Unexpected exception cleaning keypair with message - %s', e) - if os.path.isfile(self.keypair_pub_filepath): - os.remove(self.keypair_pub_filepath) - - if os.path.isfile(self.keypair_priv_filepath): - os.remove(self.keypair_priv_filepath) - if self.flavor_creator: try: self.flavor_creator.clean() @@ -663,17 +679,17 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): """ ip_1 = '10.55.1.100' sub_settings = self.pub_net_config.network_settings.subnet_settings - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.pub_net_config.network_settings.name, ip_addrs=[ {'subnet_name': sub_settings[0].name, 'ip': ip_1}]) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings], - floating_ip_settings=[FloatingIpSettings( + floating_ip_settings=[FloatingIpConfig( name=self.floating_ip_name, port_name=self.port_1_name, router_name=self.pub_net_config.router_settings.name)]) @@ -682,7 +698,7 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): self.image_creator.image_settings, keypair_settings=self.keypair_creator.keypair_settings) self.inst_creators.append(inst_creator) - vm_inst = inst_creator.create() + vm_inst = inst_creator.create(block=True) self.assertEqual(ip_1, inst_creator.get_port_ip(self.port_1_name)) self.assertTrue(inst_creator.vm_active(block=True)) @@ -693,15 +709,16 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): Tests the ability to access a VM via SSH and a floating IP when it has been assigned prior to being active. """ - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.pub_net_config.network_settings.name) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings], - floating_ip_settings=[FloatingIpSettings( + security_group_names=[self.sec_grp_creator.sec_grp_settings.name], + floating_ip_settings=[FloatingIpConfig( name=self.floating_ip_name, port_name=self.port_1_name, router_name=self.pub_net_config.router_settings.name)]) @@ -718,8 +735,6 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): ip = inst_creator.get_port_ip(port_settings.name) self.assertTrue(check_dhcp_lease(inst_creator, ip)) - inst_creator.add_security_group( - self.sec_grp_creator.get_security_group()) self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id) self.assertTrue(validate_ssh_client(inst_creator)) @@ -729,15 +744,53 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): Tests the ability to access a VM via SSH and a floating IP when it has been assigned prior to being active. """ - port_settings = PortSettings( + port_settings = PortConfig( + name=self.port_1_name, + network_name=self.pub_net_config.network_settings.name) + + instance_settings = VmInstanceConfig( + name=self.vm_inst_name, + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[port_settings], + security_group_names=[self.sec_grp_creator.sec_grp_settings.name], + floating_ip_settings=[FloatingIpConfig( + name=self.floating_ip_name, port_name=self.port_1_name, + router_name=self.pub_net_config.router_settings.name)]) + + inst_creator = OpenStackVmInstance( + self.os_creds, instance_settings, + self.image_creator.image_settings, + keypair_settings=self.keypair_creator.keypair_settings) + self.inst_creators.append(inst_creator) + + # block=True will force the create() method to block until the + vm_inst = inst_creator.create(block=True) + self.assertIsNotNone(vm_inst) + + self.assertTrue(inst_creator.vm_active(block=True)) + + ip = inst_creator.get_port_ip(port_settings.name) + self.assertTrue(check_dhcp_lease(inst_creator, ip)) + + self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id) + + self.assertTrue(validate_ssh_client(inst_creator)) + + def test_ssh_client_fip_after_reboot(self): + """ + Tests the ability to access a VM via SSH and a floating IP after it has + been rebooted. + """ + port_settings = PortConfig( name=self.port_1_name, network_name=self.pub_net_config.network_settings.name) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings], - floating_ip_settings=[FloatingIpSettings( + security_group_names=[self.sec_grp_creator.sec_grp_settings.name], + floating_ip_settings=[FloatingIpConfig( name=self.floating_ip_name, port_name=self.port_1_name, router_name=self.pub_net_config.router_settings.name)]) @@ -756,26 +809,121 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): ip = inst_creator.get_port_ip(port_settings.name) self.assertTrue(check_dhcp_lease(inst_creator, ip)) - inst_creator.add_security_group( - self.sec_grp_creator.get_security_group()) self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id) self.assertTrue(validate_ssh_client(inst_creator)) + # Test default reboot which should be 'SOFT' + inst_creator.reboot() + # Lag time to allow for shutdown routine to take effect + time.sleep(10) + self.assertTrue(check_dhcp_lease(inst_creator, ip)) + self.assertTrue(validate_ssh_client(inst_creator)) + + # Test 'SOFT' reboot + inst_creator.reboot(reboot_type=RebootType.soft) + time.sleep(10) + self.assertTrue(check_dhcp_lease(inst_creator, ip)) + self.assertTrue(validate_ssh_client(inst_creator)) + + # Test 'HARD' reboot + inst_creator.reboot(reboot_type=RebootType.hard) + time.sleep(10) + self.assertTrue(check_dhcp_lease(inst_creator, ip)) + self.assertTrue(validate_ssh_client(inst_creator)) + + def test_ssh_client_fip_after_init(self): + """ + Tests the ability to assign a floating IP to an already initialized + OpenStackVmInstance object. After the floating IP has been allocated + and assigned, this test will ensure that it can be accessed via SSH. + """ + port_settings = PortConfig( + name=self.port_1_name, + network_name=self.pub_net_config.network_settings.name) + + instance_settings = VmInstanceConfig( + name=self.vm_inst_name, + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[port_settings], + security_group_names=[self.sec_grp_creator.sec_grp_settings.name]) + + inst_creator = OpenStackVmInstance( + self.os_creds, instance_settings, + self.image_creator.image_settings, + keypair_settings=self.keypair_creator.keypair_settings) + self.inst_creators.append(inst_creator) + + # block=True will force the create() method to block until the + vm_inst = inst_creator.create(block=True) + self.assertIsNotNone(vm_inst) + + self.assertTrue(inst_creator.vm_active(block=True)) + ip = inst_creator.get_port_ip(port_settings.name) + self.assertTrue(check_dhcp_lease(inst_creator, ip)) + self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id) + + inst_creator.add_floating_ip(FloatingIpConfig( + name=self.floating_ip_name, port_name=self.port_1_name, + router_name=self.pub_net_config.router_settings.name)) + + self.assertTrue(validate_ssh_client(inst_creator)) + + def test_ssh_client_fip_reverse_engineer(self): + """ + Tests the ability to assign a floating IP to a reverse engineered + OpenStackVmInstance object. After the floating IP has been allocated + and assigned, this test will ensure that it can be accessed via SSH. + """ + port_settings = PortConfig( + name=self.port_1_name, + network_name=self.pub_net_config.network_settings.name) + + instance_settings = VmInstanceConfig( + name=self.vm_inst_name, + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[port_settings], + security_group_names=[self.sec_grp_creator.sec_grp_settings.name]) + + inst_creator = OpenStackVmInstance( + self.os_creds, instance_settings, + self.image_creator.image_settings, + keypair_settings=self.keypair_creator.keypair_settings) + self.inst_creators.append(inst_creator) + + # block=True will force the create() method to block until the + vm_inst = inst_creator.create(block=True) + self.assertIsNotNone(vm_inst) + + self.assertTrue(inst_creator.vm_active(block=True)) + + derived_inst_creator = create_instance.generate_creator( + self.os_creds, vm_inst, self.image_creator.image_settings, + self.keypair_creator.keypair_settings) + + derived_inst_creator.add_floating_ip(FloatingIpConfig( + name=self.floating_ip_name, port_name=self.port_1_name, + router_name=self.pub_net_config.router_settings.name)) + self.inst_creators.append(derived_inst_creator) + + self.assertTrue(validate_ssh_client( + derived_inst_creator, fip_name=self.floating_ip_name)) + def test_ssh_client_fip_second_creator(self): """ Tests the ability to access a VM via SSH and a floating IP via a creator that is identical to the original creator. """ - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.pub_net_config.network_settings.name) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings], - floating_ip_settings=[FloatingIpSettings( + security_group_names=[self.sec_grp_creator.sec_grp_settings.name], + floating_ip_settings=[FloatingIpConfig( name=self.floating_ip_name, port_name=self.port_1_name, router_name=self.pub_net_config.router_settings.name)]) @@ -794,8 +942,6 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): ip = inst_creator.get_port_ip(port_settings.name) self.assertTrue(check_dhcp_lease(inst_creator, ip)) - inst_creator.add_security_group( - self.sec_grp_creator.get_security_group()) self.assertEqual(vm_inst.id, inst_creator.get_vm_inst().id) self.assertTrue(validate_ssh_client(inst_creator)) @@ -808,6 +954,228 @@ class CreateInstanceSingleNetworkTests(OSIntegrationTestCase): self.assertTrue(validate_ssh_client(inst_creator2)) +class CreateInstanceIPv6NetworkTests(OSIntegrationTestCase): + """ + Test for the CreateInstance class with a single NIC/Port with Floating IPs + """ + + def setUp(self): + """ + Instantiates the CreateImage object that is responsible for downloading + and creating an OS image file within OpenStack + """ + super(self.__class__, self).__start__() + + self.nova = nova_utils.nova_client(self.os_creds) + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.keypair_priv_filepath = 'tmp/' + self.guid + self.keypair_pub_filepath = self.keypair_priv_filepath + '.pub' + self.keypair_name = self.guid + '-kp' + self.vm_inst_name = self.guid + '-inst' + self.port1_name = self.guid + 'port1' + self.port2_name = self.guid + 'port2' + + # Initialize for tearDown() + self.image_creator = None + self.network_creator = None + self.router_creator = None + self.flavor_creator = None + self.keypair_creator = None + self.sec_grp_creator = None + self.inst_creator = None + + os_image_settings = openstack_tests.cirros_image_settings( + name=self.guid + '-image', image_metadata=self.image_metadata) + try: + self.image_creator = OpenStackImage( + self.os_creds, os_image_settings) + self.image_creator.create() + + self.flavor_creator = OpenStackFlavor( + self.admin_os_creds, + FlavorConfig( + name=self.guid + '-flavor-name', ram=256, disk=10, vcpus=2, + metadata=self.flavor_metadata)) + self.flavor_creator.create() + + self.keypair_creator = OpenStackKeypair( + self.os_creds, KeypairConfig( + name=self.keypair_name, + public_filepath=self.keypair_pub_filepath, + private_filepath=self.keypair_priv_filepath)) + self.keypair_creator.create() + + sec_grp_name = self.guid + '-sec-grp' + rule1 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.icmp) + rule2 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.tcp, port_range_min=22, port_range_max=22) + self.sec_grp_creator = OpenStackSecurityGroup( + self.os_creds, + SecurityGroupConfig( + name=sec_grp_name, rule_settings=[rule1, rule2])) + self.sec_grp_creator.create() + except Exception as e: + self.tearDown() + raise e + + def tearDown(self): + """ + Cleans the created object + """ + if self.inst_creator: + try: + self.inst_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning VM instance with message ' + '- %s', e) + + if self.keypair_creator: + try: + self.keypair_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning keypair with message - %s', + e) + + if self.flavor_creator: + try: + self.flavor_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning flavor with message - %s', + e) + + if self.sec_grp_creator: + try: + self.sec_grp_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning security group with message' + ' - %s', e) + + if self.router_creator: + try: + self.router_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning router with message - %s', + e) + + if self.network_creator: + try: + self.network_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning network with message - %s', + e) + + if self.image_creator and not self.image_creator.image_settings.exists: + try: + self.image_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning image with message - %s', e) + + super(self.__class__, self).__clean__() + + def test_v4fip_v6overlay(self): + """ + Tests the ability to assign an IPv4 floating IP to an IPv6 overlay + network when the external network does not have an IPv6 subnet. + """ + subnet_settings = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6) + network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[subnet_settings]) + router_settings = RouterConfig( + name=self.guid + '-router', external_gateway=self.ext_net_name, + internal_subnets=[subnet_settings.name]) + + # Create Network + self.network_creator = OpenStackNetwork( + self.os_creds, network_settings) + self.network_creator.create() + + # Create Router + self.router_creator = OpenStackRouter( + self.os_creds, router_settings) + self.router_creator.create() + + port_settings = PortConfig( + name=self.port1_name, network_name=network_settings.name) + + instance_settings = VmInstanceConfig( + name=self.vm_inst_name, + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[port_settings], + security_group_names=[self.sec_grp_creator.sec_grp_settings.name], + floating_ip_settings=[FloatingIpConfig( + name='fip1', port_name=self.port1_name, + router_name=router_settings.name)]) + + self.inst_creator = OpenStackVmInstance( + self.os_creds, instance_settings, + self.image_creator.image_settings, + keypair_settings=self.keypair_creator.keypair_settings) + + with self.assertRaises(BadRequest): + self.inst_creator.create(block=True) + + def test_fip_v4and6_overlay(self): + """ + Tests the ability to assign an IPv4 floating IP to an IPv6 overlay + network when the external network does not have an IPv6 subnet. + """ + subnet4_settings = SubnetConfig( + name=self.guid + '-subnet4', cidr='10.0.1.0/24', + ip_version=4) + subnet6_settings = SubnetConfig( + name=self.guid + '-subnet6', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6) + network_settings = NetworkConfig( + name=self.guid + '-net', + subnet_settings=[subnet4_settings, subnet6_settings]) + router_settings = RouterConfig( + name=self.guid + '-router', external_gateway=self.ext_net_name, + internal_subnets=[subnet4_settings.name]) + + # Create Network + self.network_creator = OpenStackNetwork( + self.os_creds, network_settings) + self.network_creator.create() + + # Create Router + self.router_creator = OpenStackRouter( + self.os_creds, router_settings) + self.router_creator.create() + + port_settings = PortConfig( + name=self.port1_name, network_name=network_settings.name) + + instance_settings = VmInstanceConfig( + name=self.vm_inst_name, + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[port_settings], + security_group_names=[self.sec_grp_creator.sec_grp_settings.name], + floating_ip_settings=[FloatingIpConfig( + name='fip1', port_name=self.port1_name, + router_name=router_settings.name)]) + + self.inst_creator = OpenStackVmInstance( + self.os_creds, instance_settings, + self.image_creator.image_settings, + keypair_settings=self.keypair_creator.keypair_settings) + + self.inst_creator.create(block=True) + ssh_client = self.inst_creator.ssh_client() + self.assertIsNotNone(ssh_client) + + class CreateInstancePortManipulationTests(OSIntegrationTestCase): """ Test for the CreateInstance class with a single NIC/Port where mac and IP @@ -835,7 +1203,8 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): self.net_config = openstack_tests.get_priv_net_config( net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet', - router_name=guid + '-pub-router', external_net=self.ext_net_name) + router_name=guid + '-pub-router', external_net=self.ext_net_name, + netconf_override=self.netconf_override) os_image_settings = openstack_tests.cirros_image_settings( name=guid + '-image', image_metadata=self.image_metadata) @@ -853,8 +1222,8 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10, - vcpus=2, metadata=self.flavor_metadata)) + FlavorConfig(name=guid + '-flavor-name', ram=256, disk=10, + vcpus=2, metadata=self.flavor_metadata)) self.flavor_creator.create() except Exception as e: self.tearDown() @@ -904,12 +1273,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): """ ip = '10.55.0.101' sub_settings = self.net_config.network_settings.subnet_settings - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, ip_addrs=[{'subnet_name': sub_settings[0].name, 'ip': ip}]) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -931,12 +1300,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): """ ip = '10.66.0.101' sub_settings = self.net_config.network_settings.subnet_settings - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, ip_addrs=[{'subnet_name': sub_settings[0].name, 'ip': ip}]) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -954,12 +1323,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): the MAC address is assigned. """ mac_addr = '0a:1b:2c:3d:4e:5f' - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, mac_address=mac_addr) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -978,12 +1347,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): invalid MAC address value is being assigned. This should raise an Exception """ - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, mac_address='foo') - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -1003,13 +1372,13 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): ip = '10.55.0.101' mac_addr = '0a:1b:2c:3d:4e:5f' sub_settings = self.net_config.network_settings.subnet_settings - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, mac_address=mac_addr, ip_addrs=[{'subnet_name': sub_settings[0].name, 'ip': ip}]) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -1034,12 +1403,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): ip = '10.55.0.101' mac_addr = '0a:1b:2c:3d:4e:5f' pair = {'ip_address': ip, 'mac_address': mac_addr} - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, allowed_address_pairs=[pair]) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -1066,12 +1435,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): pair = {'ip_address': ip, 'mac_address': mac_addr} pairs = set() pairs.add((ip, mac_addr)) - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, allowed_address_pairs=[pair]) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -1092,12 +1461,12 @@ class CreateInstancePortManipulationTests(OSIntegrationTestCase): pair = {'ip_address': ip, 'mac_address': mac_addr} pairs = set() pairs.add((ip, mac_addr)) - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_1_name, network_name=self.net_config.network_settings.name, allowed_address_pairs=[pair]) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -1132,7 +1501,8 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase): self.inst_creators = list() self.priv_net_config = openstack_tests.get_priv_net_config( - net_name=guid + '-priv-net', subnet_name=guid + '-priv-subnet') + net_name=guid + '-priv-net', subnet_name=guid + '-priv-subnet', + netconf_override=self.netconf_override) os_image_settings = openstack_tests.cirros_image_settings( name=guid + '-image', image_metadata=self.image_metadata) @@ -1146,8 +1516,8 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=guid + '-flavor-name', ram=512, disk=1, - vcpus=1, metadata=self.flavor_metadata)) + FlavorConfig(name=guid + '-flavor-name', ram=512, disk=1, + vcpus=1, metadata=self.flavor_metadata)) self.flavor_creator.create() # Create Image @@ -1209,11 +1579,11 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase): for zone in zone_hosts: inst_name = self.vm_inst_name + '-' + zone ctr += 1 - port_settings = PortSettings( + port_settings = PortConfig( name=self.port_base_name + '-' + str(ctr), network_name=self.priv_net_config.network_settings.name) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=inst_name, flavor=self.flavor_creator.flavor_settings.name, availability_zone=zone, @@ -1237,235 +1607,6 @@ class CreateInstanceOnComputeHost(OSIntegrationTestCase): index += 1 -class CreateInstancePubPrivNetTests(OSIntegrationTestCase): - """ - Test for the CreateInstance class with two NIC/Ports, eth0 with floating IP - and eth1 w/o. - These tests require a Centos image - """ - - def setUp(self): - """ - Instantiates the CreateImage object that is responsible for downloading - and creating an OS image file within OpenStack - """ - super(self.__class__, self).__start__() - - self.nova = nova_utils.nova_client(self.os_creds) - - # Initialize for tearDown() - self.image_creator = None - self.network_creators = list() - self.router_creators = list() - self.flavor_creator = None - self.keypair_creator = None - self.sec_grp_creator = None - self.inst_creator = None - - self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) - self.keypair_priv_filepath = 'tmp/' + self.guid - self.keypair_pub_filepath = self.keypair_priv_filepath + '.pub' - self.keypair_name = self.guid + '-kp' - self.vm_inst_name = self.guid + '-inst' - self.port_1_name = self.guid + '-port-1' - self.port_2_name = self.guid + '-port-2' - self.floating_ip_name = self.guid + 'fip1' - self.priv_net_config = openstack_tests.get_priv_net_config( - net_name=self.guid + '-priv-net', - subnet_name=self.guid + '-priv-subnet', - router_name=self.guid + '-priv-router', - external_net=self.ext_net_name) - self.pub_net_config = openstack_tests.get_pub_net_config( - net_name=self.guid + '-pub-net', - subnet_name=self.guid + '-pub-subnet', - router_name=self.guid + '-pub-router', - external_net=self.ext_net_name) - - image_name = self.__class__.__name__ + '-' + str(uuid.uuid4()) - os_image_settings = openstack_tests.centos_image_settings( - name=image_name, image_metadata=self.image_metadata) - - try: - # Create Image - self.image_creator = OpenStackImage(self.os_creds, - os_image_settings) - self.image_creator.create() - - # First network is public - self.network_creators.append(OpenStackNetwork( - self.os_creds, self.pub_net_config.network_settings)) - # Second network is private - self.network_creators.append(OpenStackNetwork( - self.os_creds, self.priv_net_config.network_settings)) - for network_creator in self.network_creators: - network_creator.create() - - self.router_creators.append(OpenStackRouter( - self.os_creds, self.pub_net_config.router_settings)) - self.router_creators.append(OpenStackRouter( - self.os_creds, self.priv_net_config.router_settings)) - - # Create Routers - for router_creator in self.router_creators: - router_creator.create() - - # Create Flavor - self.flavor_creator = OpenStackFlavor( - self.admin_os_creds, - FlavorSettings(name=self.guid + '-flavor-name', ram=512, - disk=10, vcpus=2, - metadata=self.flavor_metadata)) - self.flavor_creator.create() - - # Create Keypair - self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( - name=self.keypair_name, - public_filepath=self.keypair_pub_filepath, - private_filepath=self.keypair_priv_filepath)) - self.keypair_creator.create() - - sec_grp_name = self.guid + '-sec-grp' - rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.icmp) - rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.tcp, - port_range_min=22, - port_range_max=22) - self.sec_grp_creator = OpenStackSecurityGroup( - self.os_creds, - SecurityGroupSettings(name=sec_grp_name, - rule_settings=[rule1, rule2])) - self.sec_grp_creator.create() - except: - self.tearDown() - raise - - def tearDown(self): - """ - Cleans the created objects - """ - if self.inst_creator: - try: - self.inst_creator.clean() - except Exception as e: - logger.error( - 'Unexpected exception cleaning VM instance with message ' - '- %s', e) - - if self.keypair_creator: - try: - self.keypair_creator.clean() - except Exception as e: - logger.error( - 'Unexpected exception cleaning keypair with message - %s', - e) - - if os.path.isfile(self.keypair_pub_filepath): - os.remove(self.keypair_pub_filepath) - - if os.path.isfile(self.keypair_priv_filepath): - os.remove(self.keypair_priv_filepath) - - if self.flavor_creator: - try: - self.flavor_creator.clean() - except Exception as e: - logger.error( - 'Unexpected exception cleaning flavor with message - %s', - e) - - for router_creator in self.router_creators: - try: - router_creator.clean() - except Exception as e: - logger.error( - 'Unexpected exception cleaning router with message - %s', - e) - - for network_creator in self.network_creators: - try: - network_creator.clean() - except Exception as e: - logger.error( - 'Unexpected exception cleaning network with message - %s', - e) - - if self.sec_grp_creator: - try: - self.sec_grp_creator.clean() - except Exception as e: - logger.error( - 'Unexpected exception cleaning security group with message' - ' - %s', e) - - if self.image_creator and not self.image_creator.image_settings.exists: - try: - self.image_creator.clean() - except Exception as e: - logger.error( - 'Unexpected exception cleaning image with message - %s', e) - - super(self.__class__, self).__clean__() - - def test_dual_ports_dhcp(self): - """ - Tests the creation of an OpenStack instance with a dual ports/NICs with - a DHCP assigned IP. - NOTE: This test and any others that call ansible will most likely fail - unless you do one of two things: - 1. Have a ~/.ansible.cfg (or alternate means) to - set host_key_checking = False - 2. Set the following environment variable in your executing shell: - ANSIBLE_HOST_KEY_CHECKING=False - Should this not be performed, the creation of the host ssh key will - cause your ansible calls to fail. - """ - # Create ports/NICs for instance - ports_settings = [] - ctr = 1 - for network_creator in self.network_creators: - ports_settings.append(PortSettings( - name=self.guid + '-port-' + str(ctr), - network_name=network_creator.network_settings.name)) - ctr += 1 - - # Create instance - instance_settings = VmInstanceSettings( - name=self.vm_inst_name, - flavor=self.flavor_creator.flavor_settings.name, - port_settings=ports_settings, - floating_ip_settings=[FloatingIpSettings( - name=self.floating_ip_name, port_name=self.port_1_name, - router_name=self.pub_net_config.router_settings.name)]) - - self.inst_creator = OpenStackVmInstance( - self.os_creds, instance_settings, - self.image_creator.image_settings, - keypair_settings=self.keypair_creator.keypair_settings) - - vm_inst = self.inst_creator.create(block=True) - - self.assertEqual(vm_inst.id, self.inst_creator.get_vm_inst().id) - - # Effectively blocks until VM has been properly activated - self.assertTrue(self.inst_creator.vm_active(block=True)) - - ip = self.inst_creator.get_port_ip(ports_settings[0].name) - self.assertTrue(check_dhcp_lease(self.inst_creator, ip)) - - # Add security group to VM - self.inst_creator.add_security_group( - self.sec_grp_creator.get_security_group()) - - # Effectively blocks until VM's ssh port has been opened - self.assertTrue(self.inst_creator.vm_ssh_active(block=True)) - - self.assertEqual(0, self.inst_creator.config_nics()) - - class InstanceSecurityGroupTests(OSIntegrationTestCase): """ Tests that include, add, and remove security groups from VM instances @@ -1493,7 +1634,8 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): net_name=self.guid + '-pub-net', subnet_name=self.guid + '-pub-subnet', router_name=self.guid + '-pub-router', - external_net=self.ext_net_name) + external_net=self.ext_net_name, + netconf_override=self.netconf_override) # Initialize for tearDown() self.image_creator = None @@ -1517,12 +1659,12 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=self.guid + '-flavor-name', ram=256, - disk=10, vcpus=2, - metadata=self.flavor_metadata)) + FlavorConfig(name=self.guid + '-flavor-name', ram=256, + disk=10, vcpus=2, + metadata=self.flavor_metadata)) self.flavor_creator.create() - self.port_settings = PortSettings( + self.port_settings = PortConfig( name=self.guid + '-port', network_name=net_config.network_settings.name) except Exception as e: @@ -1579,7 +1721,7 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): Tests the addition of a security group created after the instance. """ # Create instance - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -1590,8 +1732,8 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): self.assertIsNotNone(vm_inst) # Create security group object to add to instance - sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name', - description='hello group') + sec_grp_settings = SecurityGroupConfig( + name=self.guid + '-name', description='hello group') sec_grp_creator = OpenStackSecurityGroup(self.os_creds, sec_grp_settings) sec_grp = sec_grp_creator.create() @@ -1613,7 +1755,7 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): Tests the addition of a security group that no longer exists. """ # Create instance - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -1624,8 +1766,8 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): self.assertIsNotNone(vm_inst) # Create security group object to add to instance - sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name', - description='hello group') + sec_grp_settings = SecurityGroupConfig( + name=self.guid + '-name', description='hello group') sec_grp_creator = OpenStackSecurityGroup(self.os_creds, sec_grp_settings) sec_grp = sec_grp_creator.create() @@ -1649,15 +1791,15 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): instance. """ # Create security group object to add to instance - sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name', - description='hello group') + sec_grp_settings = SecurityGroupConfig( + name=self.guid + '-name', description='hello group') sec_grp_creator = OpenStackSecurityGroup(self.os_creds, sec_grp_settings) sec_grp = sec_grp_creator.create() self.sec_grp_creators.append(sec_grp_creator) # Create instance - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, security_group_names=[sec_grp_settings.name], @@ -1685,15 +1827,15 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): place. """ # Create security group object to add to instance - sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name', - description='hello group') + sec_grp_settings = SecurityGroupConfig( + name=self.guid + '-name', description='hello group') sec_grp_creator = OpenStackSecurityGroup(self.os_creds, sec_grp_settings) sec_grp = sec_grp_creator.create() self.sec_grp_creators.append(sec_grp_creator) # Create instance - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -1720,15 +1862,15 @@ class InstanceSecurityGroupTests(OSIntegrationTestCase): instance. """ # Create security group object to add to instance - sec_grp_settings = SecurityGroupSettings(name=self.guid + '-name', - description='hello group') + sec_grp_settings = SecurityGroupConfig( + name=self.guid + '-name', description='hello group') sec_grp_creator = OpenStackSecurityGroup(self.os_creds, sec_grp_settings) sec_grp = sec_grp_creator.create() self.sec_grp_creators.append(sec_grp_creator) # Create instance - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, security_group_names=[sec_grp_settings.name], @@ -1766,17 +1908,18 @@ def inst_has_sec_grp(nova, vm_inst, sec_grp_name): return False -def validate_ssh_client(instance_creator): +def validate_ssh_client(instance_creator, fip_name=None): """ Returns True if instance_creator returns an SSH client that is valid :param instance_creator: the object responsible for creating the VM instance + :param fip_name: the name of the floating IP to use :return: T/F """ ssh_active = instance_creator.vm_ssh_active(block=True) if ssh_active: - ssh_client = instance_creator.ssh_client() + ssh_client = instance_creator.ssh_client(fip_name=fip_name) if ssh_client: try: out = ssh_client.exec_command('pwd')[1] @@ -1813,7 +1956,8 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase): net_config = openstack_tests.get_priv_net_config( net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet', - router_name=guid + '-pub-router', external_net=self.ext_net_name) + router_name=guid + '-pub-router', external_net=self.ext_net_name, + netconf_override=self.netconf_override) # Initialize for tearDown() self.image_creator = None @@ -1851,8 +1995,8 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=guid + '-flavor-name', ram=256, disk=10, - vcpus=2, metadata=self.flavor_metadata)) + FlavorConfig(name=guid + '-flavor-name', ram=256, disk=10, + vcpus=2, metadata=self.flavor_metadata)) self.flavor_creator.create() # Create Network @@ -1860,7 +2004,7 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase): self.os_creds, net_config.network_settings) self.network_creator.create() - self.port_settings = PortSettings( + self.port_settings = PortConfig( name=guid + '-port', network_name=net_config.network_settings.name) except Exception as e: @@ -1908,7 +2052,7 @@ class CreateInstanceFromThreePartImage(OSIntegrationTestCase): """ Tests the creation of an OpenStack instance from a 3-part image. """ - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -1955,7 +2099,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): self.priv_net_config = openstack_tests.get_priv_net_config( net_name=self.guid + '-priv-net', subnet_name=self.guid + '-priv-subnet') - self.port_settings = PortSettings( + self.port_settings = PortConfig( name=self.port_1_name, network_name=self.priv_net_config.network_settings.name) @@ -1972,7 +2116,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.os_creds, - FlavorSettings( + FlavorConfig( name=self.guid + '-flavor-name', ram=256, disk=10, vcpus=1)) self.flavor_creator.create() @@ -2043,7 +2187,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): self.image_creator = OpenStackImage(self.os_creds, os_image_settings) self.image_creator.create() - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2079,7 +2223,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): self.image_creator = OpenStackImage(self.os_creds, os_image_settings) self.image_creator.create() - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2104,14 +2248,14 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): image_settings = self.image_creator.image_settings test_image_creator = OpenStackImage( self.os_creds, - ImageSettings(name=image_settings.name, - image_user=image_settings.image_user, - exists=True)) + ImageConfig( + name=image_settings.name, image_user=image_settings.image_user, + exists=True)) test_image_creator.create() self.assertEqual(self.image_creator.get_image().id, test_image_creator.get_image().id) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2146,7 +2290,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): test_image = OpenStackImage(self.os_creds, test_image_settings) test_image.create() - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2226,7 +2370,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): self.image_creator = OpenStackImage(self.os_creds, os_image_settings) self.image_creator.create() - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2296,7 +2440,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): self.assertIsNotNone(self.image_creator.get_kernel_image()) self.assertIsNotNone(self.image_creator.get_ramdisk_image()) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2366,7 +2510,7 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): self.assertIsNotNone(self.image_creator.get_kernel_image()) self.assertIsNotNone(self.image_creator.get_ramdisk_image()) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2399,14 +2543,14 @@ class CreateInstanceMockOfflineTests(OSComponentTestCase): image_settings = self.image_creator.image_settings test_image_creator = OpenStackImage( self.os_creds, - ImageSettings(name=image_settings.name, - image_user=image_settings.image_user, - exists=True)) + ImageConfig( + name=image_settings.name, image_user=image_settings.image_user, + exists=True)) test_image_creator.create() self.assertEqual(self.image_creator.get_image().id, test_image_creator.get_image().id) - instance_settings = VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=[self.port_settings]) @@ -2453,16 +2597,16 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): self.vm_inst2_name = self.guid + '-inst2' self.port_1_name = self.guid + '-vm1-port' self.port_2_name = self.guid + '-vm2-port' - self.net_config_1 = NetworkSettings( + self.net_config_1 = NetworkConfig( name=self.guid + '-net1', subnet_settings=[ - create_network.SubnetSettings( + create_network.SubnetConfig( cidr=cidr1, name=self.guid + '-subnet1', gateway_ip=static_gateway_ip1)]) - self.net_config_2 = NetworkSettings( + self.net_config_2 = NetworkConfig( name=self.guid + '-net2', subnet_settings=[ - create_network.SubnetSettings( + create_network.SubnetConfig( cidr=cidr2, name=self.guid + '-subnet2', gateway_ip=static_gateway_ip2)]) @@ -2486,7 +2630,7 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): network_creator.create() port_settings = [ - create_network.PortSettings( + create_network.PortConfig( name=self.guid + '-router-port1', ip_addrs=[{ 'subnet_name': @@ -2495,7 +2639,7 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): }], network_name=self.net_config_1.name, project_name=self.os_creds.project_name), - create_network.PortSettings( + create_network.PortConfig( name=self.guid + '-router-port2', ip_addrs=[{ 'subnet_name': @@ -2505,8 +2649,8 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): network_name=self.net_config_2.name, project_name=self.os_creds.project_name)] - router_settings = RouterSettings(name=self.guid + '-pub-router', - port_settings=port_settings) + router_settings = RouterConfig( + name=self.guid + '-pub-router', port_settings=port_settings) self.router_creator = create_router.OpenStackRouter( self.os_creds, router_settings) self.router_creator.create() @@ -2514,19 +2658,19 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=self.guid + '-flavor-name', ram=512, - disk=10, vcpus=2, - metadata=self.flavor_metadata)) + FlavorConfig(name=self.guid + '-flavor-name', ram=512, + disk=10, vcpus=2, + metadata=self.flavor_metadata)) self.flavor_creator.create() sec_grp_name = self.guid + '-sec-grp' - rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.icmp) + rule1 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.icmp) self.sec_grp_creator = OpenStackSecurityGroup( self.os_creds, - SecurityGroupSettings(name=sec_grp_name, - rule_settings=[rule1])) + SecurityGroupConfig( + name=sec_grp_name, rule_settings=[rule1])) self.sec_grp_creator.create() except: self.tearDown() @@ -2596,17 +2740,17 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): ports_settings = [] ctr = 1 for network_creator in self.network_creators: - ports_settings.append(PortSettings( + ports_settings.append(PortConfig( name=self.guid + '-port-' + str(ctr), network_name=network_creator.network_settings.name)) ctr += 1 # Configure instances - instance1_settings = VmInstanceSettings( + instance1_settings = VmInstanceConfig( name=self.vm_inst1_name, flavor=self.flavor_creator.flavor_settings.name, userdata=_get_ping_userdata(self.ip2), - port_settings=[PortSettings( + port_settings=[PortConfig( name=self.port_1_name, ip_addrs=[{ 'subnet_name': @@ -2614,11 +2758,11 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): 'ip': self.ip1 }], network_name=self.network_creators[0].network_settings.name)]) - instance2_settings = VmInstanceSettings( + instance2_settings = VmInstanceConfig( name=self.vm_inst2_name, flavor=self.flavor_creator.flavor_settings.name, userdata=_get_ping_userdata(self.ip1), - port_settings=[PortSettings( + port_settings=[PortConfig( name=self.port_2_name, ip_addrs=[{ 'subnet_name': @@ -2647,6 +2791,182 @@ class CreateInstanceTwoNetTests(OSIntegrationTestCase): self.assertTrue(check_ping(self.inst_creators[1])) +class CreateInstanceVolumeTests(OSIntegrationTestCase): + """ + Simple instance creation with an attached volume + """ + + def setUp(self): + """ + Instantiates the CreateImage object that is responsible for downloading + and creating an OS image file + within OpenStack + """ + super(self.__class__, self).__start__() + + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.vm_inst_name = guid + '-inst' + self.nova = nova_utils.nova_client(self.os_creds) + self.neutron = neutron_utils.neutron_client(self.os_creds) + os_image_settings = openstack_tests.cirros_image_settings( + name=guid + '-image', image_metadata=self.image_metadata) + + net_config = openstack_tests.get_priv_net_config( + net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet', + router_name=guid + '-pub-router', external_net=self.ext_net_name, + netconf_override=self.netconf_override) + + self.volume_settings1 = VolumeConfig( + name=self.__class__.__name__ + '-' + str(guid) + '-1') + self.volume_settings2 = VolumeConfig( + name=self.__class__.__name__ + '-' + str(guid) + '-2') + + # Initialize for tearDown() + self.image_creator = None + self.flavor_creator = None + + self.network_creator = None + self.inst_creator = None + self.volume_creator1 = None + self.volume_creator2 = None + + try: + # Create Image + self.image_creator = OpenStackImage(self.os_creds, + os_image_settings) + self.image_creator.create() + + # Create Flavor + self.flavor_creator = OpenStackFlavor( + self.admin_os_creds, + FlavorConfig(name=guid + '-flavor-name', ram=256, disk=1, + vcpus=2, metadata=self.flavor_metadata)) + self.flavor_creator.create() + + # Create Network + self.network_creator = OpenStackNetwork( + self.os_creds, net_config.network_settings) + self.network_creator.create() + + self.port_settings = PortConfig( + name=guid + '-port', + network_name=net_config.network_settings.name) + + self.volume_creator1 = OpenStackVolume( + self.os_creds, self.volume_settings1) + self.volume_creator1.create(block=True) + + self.volume_creator2 = OpenStackVolume( + self.os_creds, self.volume_settings2) + self.volume_creator2.create(block=True) + + except Exception as e: + self.tearDown() + raise e + + def tearDown(self): + """ + Cleans the created object + """ + if self.inst_creator: + try: + self.inst_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning VM instance with message ' + '- %s', e) + + if self.flavor_creator: + try: + self.flavor_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning flavor with message - %s', + e) + + if self.network_creator: + try: + self.network_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning network with message - %s', + e) + + if self.volume_creator2: + try: + self.volume_creator2.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning volume with message - %s', + e) + + if self.volume_creator1: + try: + self.volume_creator1.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning volume with message - %s', + e) + + if self.image_creator and not self.image_creator.image_settings.exists: + try: + self.image_creator.clean() + except Exception as e: + logger.error( + 'Unexpected exception cleaning image with message - %s', e) + + super(self.__class__, self).__clean__() + + def test_create_instance_with_one_volume(self): + """ + Tests the creation of an OpenStack instance with a single volume. + """ + instance_settings = VmInstanceConfig( + name=self.vm_inst_name, + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[self.port_settings], + volume_names=[self.volume_settings1.name]) + + self.inst_creator = OpenStackVmInstance( + self.os_creds, instance_settings, + self.image_creator.image_settings) + + vm_inst = self.inst_creator.create(block=True) + self.assertIsNotNone(nova_utils.get_server( + self.nova, self.neutron, vm_inst_settings=instance_settings)) + + self.assertIsNotNone(vm_inst) + self.assertEqual(1, len(vm_inst.volume_ids)) + self.assertEqual(self.volume_creator1.get_volume().id, + vm_inst.volume_ids[0]['id']) + + def test_create_instance_with_two_volumes(self): + """ + Tests the creation of an OpenStack instance with a single volume. + """ + instance_settings = VmInstanceConfig( + name=self.vm_inst_name, + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[self.port_settings], + volume_names=[self.volume_settings1.name, + self.volume_settings2.name]) + + self.inst_creator = OpenStackVmInstance( + self.os_creds, instance_settings, + self.image_creator.image_settings) + + vm_inst = self.inst_creator.create(block=True) + self.assertIsNotNone(nova_utils.get_server( + self.nova, self.neutron, vm_inst_settings=instance_settings)) + + self.assertIsNotNone(vm_inst) + self.assertEqual(2, len(vm_inst.volume_ids)) + self.assertEqual(self.volume_creator1.get_volume().id, + vm_inst.volume_ids[0]['id']) + self.assertEqual(self.volume_creator2.get_volume().id, + vm_inst.volume_ids[1]['id']) + + def check_dhcp_lease(inst_creator, ip, timeout=160): """ Returns true if the expected DHCP lease has been acquired diff --git a/snaps/openstack/tests/create_keypairs_tests.py b/snaps/openstack/tests/create_keypairs_tests.py index d2de6fe..63e0bcc 100644 --- a/snaps/openstack/tests/create_keypairs_tests.py +++ b/snaps/openstack/tests/create_keypairs_tests.py @@ -18,8 +18,9 @@ import uuid import os from snaps import file_utils +from snaps.config.keypair import KeypairConfig, KeypairConfigError from snaps.openstack.create_keypairs import ( - KeypairSettings, OpenStackKeypair, KeypairSettingsError) + KeypairSettings, OpenStackKeypair) from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase from snaps.openstack.utils import nova_utils @@ -32,15 +33,15 @@ class KeypairSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(KeypairSettingsError): + with self.assertRaises(KeypairConfigError): KeypairSettings() def test_empty_config(self): - with self.assertRaises(KeypairSettingsError): + with self.assertRaises(KeypairConfigError): KeypairSettings(**dict()) def test_small_key_size(self): - with self.assertRaises(KeypairSettingsError): + with self.assertRaises(KeypairConfigError): KeypairSettings(name='foo', key_size=511) def test_name_only(self): @@ -228,7 +229,7 @@ class CreateKeypairsTests(OSIntegrationTestCase): Tests the creation of a generated keypair without saving to file :return: """ - self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairSettings( + self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairConfig( name=self.keypair_name)) self.keypair_creator.create() @@ -241,7 +242,7 @@ class CreateKeypairsTests(OSIntegrationTestCase): Tests the creation of a generated keypair without saving to file :return: """ - self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairSettings( + self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairConfig( name=self.keypair_name, key_size=10000)) self.keypair_creator.create() @@ -255,7 +256,7 @@ class CreateKeypairsTests(OSIntegrationTestCase): clean() does not raise an Exception. """ # Create Image - self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairSettings( + self.keypair_creator = OpenStackKeypair(self.os_creds, KeypairConfig( name=self.keypair_name)) created_keypair = self.keypair_creator.create() self.assertIsNotNone(created_keypair) @@ -277,8 +278,8 @@ class CreateKeypairsTests(OSIntegrationTestCase): :return: """ self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings(name=self.keypair_name, - public_filepath=self.pub_file_path)) + self.os_creds, KeypairConfig( + name=self.keypair_name, public_filepath=self.pub_file_path)) self.keypair_creator.create() keypair = nova_utils.keypair_exists(self.nova, @@ -302,7 +303,7 @@ class CreateKeypairsTests(OSIntegrationTestCase): :return: """ self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.pub_file_path, private_filepath=self.priv_file_path)) self.keypair_creator.create() @@ -335,8 +336,8 @@ class CreateKeypairsTests(OSIntegrationTestCase): file_utils.save_keys_to_files(keys=keys, pub_file_path=self.pub_file_path) self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings(name=self.keypair_name, - public_filepath=self.pub_file_path)) + self.os_creds, KeypairConfig( + name=self.keypair_name, public_filepath=self.pub_file_path)) self.keypair_creator.create() keypair = nova_utils.keypair_exists(self.nova, @@ -400,7 +401,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase): :return: """ self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.pub_file_path, private_filepath=self.priv_file_path)) self.keypair_creator.create() @@ -416,7 +417,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase): :return: """ self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.pub_file_path, private_filepath=self.priv_file_path, delete_on_clean=True)) self.keypair_creator.create() @@ -432,7 +433,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase): :return: """ self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.pub_file_path, private_filepath=self.priv_file_path, delete_on_clean=False)) self.keypair_creator.create() @@ -452,7 +453,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase): keys=keys, pub_file_path=self.pub_file_path, priv_file_path=self.priv_file_path) self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.pub_file_path, private_filepath=self.priv_file_path, delete_on_clean=False)) self.keypair_creator.create() @@ -472,7 +473,7 @@ class CreateKeypairsCleanupTests(OSIntegrationTestCase): keys=keys, pub_file_path=self.pub_file_path, priv_file_path=self.priv_file_path) self.keypair_creator = OpenStackKeypair( - self.os_creds, KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.pub_file_path, private_filepath=self.priv_file_path, delete_on_clean=True)) self.keypair_creator.create() diff --git a/snaps/openstack/tests/create_network_tests.py b/snaps/openstack/tests/create_network_tests.py index 9cee130..966cbd0 100644 --- a/snaps/openstack/tests/create_network_tests.py +++ b/snaps/openstack/tests/create_network_tests.py @@ -15,17 +15,18 @@ import unittest import uuid +from snaps.config.network import ( + NetworkConfig, SubnetConfig, SubnetConfigError, NetworkConfigError, + PortConfigError, IPv6Mode) from snaps.openstack import create_router -from snaps.openstack.create_network import (OpenStackNetwork, NetworkSettings, - SubnetSettings, PortSettings, - NetworkSettingsError, - SubnetSettingsError, - PortSettingsError) +from snaps.openstack.create_network import ( + OpenStackNetwork, NetworkSettings, SubnetSettings, PortSettings) from snaps.openstack.tests import openstack_tests -from snaps.openstack.tests.os_source_file_test import (OSIntegrationTestCase, - OSComponentTestCase) +from snaps.openstack.tests.os_source_file_test import ( + OSIntegrationTestCase, OSComponentTestCase) from snaps.openstack.utils import neutron_utils from snaps.openstack.utils.tests import neutron_utils_tests +from snaps.openstack.create_network import IPv6Mode as IPv6Mode_old __author__ = 'spisarski' @@ -36,11 +37,11 @@ class NetworkSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(NetworkSettingsError): + with self.assertRaises(NetworkConfigError): NetworkSettings() def test_empty_config(self): - with self.assertRaises(NetworkSettingsError): + with self.assertRaises(NetworkConfigError): NetworkSettings(**dict()) def test_name_only(self): @@ -111,19 +112,19 @@ class SubnetSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(SubnetSettingsError): + with self.assertRaises(SubnetConfigError): SubnetSettings() def test_empty_config(self): - with self.assertRaises(SubnetSettingsError): + with self.assertRaises(SubnetConfigError): SubnetSettings(**dict()) def test_name_only(self): - with self.assertRaises(SubnetSettingsError): + with self.assertRaises(SubnetConfigError): SubnetSettings(name='foo') def test_config_with_name_only(self): - with self.assertRaises(SubnetSettingsError): + with self.assertRaises(SubnetConfigError): SubnetSettings(**{'name': 'foo'}) def test_name_cidr_only(self): @@ -161,18 +162,44 @@ class SubnetSettingsUnitTests(unittest.TestCase): self.assertIsNone(settings.ipv6_ra_mode) self.assertIsNone(settings.ipv6_address_mode) - def test_all(self): + def test_all_string_enums(self): + host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'} + settings = SubnetSettings( + name='foo', cidr='10.0.0.0/24', ip_version=6, + project_name='bar-project', start='10.0.0.2', end='10.0.0.101', + gateway_ip='10.0.0.1', enable_dhcp=False, + dns_nameservers=['8.8.8.8'], host_routes=[host_routes], + destination='dest', nexthop='hop', ipv6_ra_mode='dhcpv6-stateful', + ipv6_address_mode='slaac') + self.assertEqual('foo', settings.name) + self.assertEqual('10.0.0.0/24', settings.cidr) + self.assertEqual(6, settings.ip_version) + self.assertEqual('bar-project', settings.project_name) + self.assertEqual('10.0.0.2', settings.start) + self.assertEqual('10.0.0.101', settings.end) + self.assertEqual('10.0.0.1', settings.gateway_ip) + self.assertEqual(False, settings.enable_dhcp) + self.assertEqual(1, len(settings.dns_nameservers)) + self.assertEqual('8.8.8.8', settings.dns_nameservers[0]) + self.assertEqual(1, len(settings.host_routes)) + self.assertEqual(host_routes, settings.host_routes[0]) + self.assertEqual('dest', settings.destination) + self.assertEqual('hop', settings.nexthop) + self.assertEqual(IPv6Mode_old.stateful.value, + settings.ipv6_ra_mode.value) + self.assertEqual(IPv6Mode_old.slaac.value, + settings.ipv6_address_mode.value) + + def test_all_type_enums(self): host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'} - settings = SubnetSettings(name='foo', cidr='10.0.0.0/24', ip_version=6, - project_name='bar-project', - start='10.0.0.2', end='10.0.0.101', - gateway_ip='10.0.0.1', enable_dhcp=False, - dns_nameservers=['8.8.8.8'], - host_routes=[host_routes], - destination='dest', - nexthop='hop', - ipv6_ra_mode='dhcpv6-stateful', - ipv6_address_mode='slaac') + settings = SubnetSettings( + name='foo', cidr='10.0.0.0/24', ip_version=6, + project_name='bar-project', start='10.0.0.2', end='10.0.0.101', + gateway_ip='10.0.0.1', enable_dhcp=False, + dns_nameservers=['8.8.8.8'], host_routes=[host_routes], + destination='dest', nexthop='hop', + ipv6_ra_mode=IPv6Mode_old.stateful, + ipv6_address_mode=IPv6Mode.slaac) self.assertEqual('foo', settings.name) self.assertEqual('10.0.0.0/24', settings.cidr) self.assertEqual(6, settings.ip_version) @@ -187,8 +214,9 @@ class SubnetSettingsUnitTests(unittest.TestCase): self.assertEqual(host_routes, settings.host_routes[0]) self.assertEqual('dest', settings.destination) self.assertEqual('hop', settings.nexthop) - self.assertEqual('dhcpv6-stateful', settings.ipv6_ra_mode) - self.assertEqual('slaac', settings.ipv6_address_mode) + self.assertEqual(IPv6Mode.stateful.value, settings.ipv6_ra_mode.value) + self.assertEqual(IPv6Mode.slaac.value, + settings.ipv6_address_mode.value) def test_config_all(self): host_routes = {'destination': '0.0.0.0/0', 'nexthop': '123.456.78.9'} @@ -199,7 +227,7 @@ class SubnetSettingsUnitTests(unittest.TestCase): 'gateway_ip': '10.0.0.1', 'enable_dhcp': False, 'dns_nameservers': ['8.8.8.8'], 'host_routes': [host_routes], 'destination': 'dest', 'nexthop': 'hop', - 'ipv6_ra_mode': 'dhcpv6-stateful', + 'ipv6_ra_mode': 'dhcpv6-stateless', 'ipv6_address_mode': 'slaac'}) self.assertEqual('foo', settings.name) self.assertEqual('10.0.0.0/24', settings.cidr) @@ -215,8 +243,9 @@ class SubnetSettingsUnitTests(unittest.TestCase): self.assertEqual(host_routes, settings.host_routes[0]) self.assertEqual('dest', settings.destination) self.assertEqual('hop', settings.nexthop) - self.assertEqual('dhcpv6-stateful', settings.ipv6_ra_mode) - self.assertEqual('slaac', settings.ipv6_address_mode) + self.assertEqual(IPv6Mode.stateless.value, settings.ipv6_ra_mode.value) + self.assertEqual(IPv6Mode.slaac.value, + settings.ipv6_address_mode.value) class PortSettingsUnitTests(unittest.TestCase): @@ -225,19 +254,19 @@ class PortSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(PortSettingsError): + with self.assertRaises(PortConfigError): PortSettings() def test_empty_config(self): - with self.assertRaises(PortSettingsError): + with self.assertRaises(PortConfigError): PortSettings(**dict()) def test_name_only(self): - with self.assertRaises(PortSettingsError): + with self.assertRaises(PortConfigError): PortSettings(name='foo') def test_config_name_only(self): - with self.assertRaises(PortSettingsError): + with self.assertRaises(PortConfigError): PortSettings(**{'name': 'foo'}) def test_name_netname_only(self): @@ -248,7 +277,6 @@ class PortSettingsUnitTests(unittest.TestCase): self.assertIsNone(settings.project_name) self.assertIsNone(settings.mac_address) self.assertIsNone(settings.ip_addrs) - self.assertIsNone(settings.fixed_ips) self.assertIsNone(settings.security_groups) self.assertIsNone(settings.allowed_address_pairs) self.assertIsNone(settings.opt_value) @@ -264,7 +292,6 @@ class PortSettingsUnitTests(unittest.TestCase): self.assertIsNone(settings.project_name) self.assertIsNone(settings.mac_address) self.assertIsNone(settings.ip_addrs) - self.assertIsNone(settings.fixed_ips) self.assertIsNone(settings.security_groups) self.assertIsNone(settings.allowed_address_pairs) self.assertIsNone(settings.opt_value) @@ -274,14 +301,12 @@ class PortSettingsUnitTests(unittest.TestCase): def test_all(self): ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}] - fixed_ips = {'sub_id', '10.0.0.10'} allowed_address_pairs = {'10.0.0.101', '1234.5678'} settings = PortSettings(name='foo', network_name='bar', admin_state_up=False, project_name='foo-project', mac_address='1234', ip_addrs=ip_addrs, - fixed_ips=fixed_ips, security_groups=['foo_grp_id'], allowed_address_pairs=allowed_address_pairs, opt_value='opt value', opt_name='opt name', @@ -293,7 +318,6 @@ class PortSettingsUnitTests(unittest.TestCase): self.assertEqual('foo-project', settings.project_name) self.assertEqual('1234', settings.mac_address) self.assertEqual(ip_addrs, settings.ip_addrs) - self.assertEqual(fixed_ips, settings.fixed_ips) self.assertEqual(1, len(settings.security_groups)) self.assertEqual('foo_grp_id', settings.security_groups[0]) self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs) @@ -304,25 +328,21 @@ class PortSettingsUnitTests(unittest.TestCase): def test_config_all(self): ip_addrs = [{'subnet_name', 'foo-sub', 'ip', '10.0.0.10'}] - fixed_ips = {'sub_id', '10.0.0.10'} allowed_address_pairs = {'10.0.0.101', '1234.5678'} settings = PortSettings( **{'name': 'foo', 'network_name': 'bar', 'admin_state_up': False, 'project_name': 'foo-project', 'mac_address': '1234', - 'ip_addrs': ip_addrs, - 'fixed_ips': fixed_ips, 'security_groups': ['foo_grp_id'], + 'ip_addrs': ip_addrs, 'security_groups': ['foo_grp_id'], 'allowed_address_pairs': allowed_address_pairs, - 'opt_value': 'opt value', - 'opt_name': 'opt name', 'device_owner': 'owner', - 'device_id': 'device number'}) + 'opt_value': 'opt value', 'opt_name': 'opt name', + 'device_owner': 'owner', 'device_id': 'device number'}) self.assertEqual('foo', settings.name) self.assertEqual('bar', settings.network_name) self.assertFalse(settings.admin_state_up) self.assertEqual('foo-project', settings.project_name) self.assertEqual('1234', settings.mac_address) self.assertEqual(ip_addrs, settings.ip_addrs) - self.assertEqual(fixed_ips, settings.fixed_ips) self.assertEqual(1, len(settings.security_groups)) self.assertEqual('foo_grp_id', settings.security_groups[0]) self.assertEqual(allowed_address_pairs, settings.allowed_address_pairs) @@ -334,7 +354,7 @@ class PortSettingsUnitTests(unittest.TestCase): class CreateNetworkSuccessTests(OSIntegrationTestCase): """ - Test for the CreateNework class defined in create_nework.py + Test for the CreateNetwork class defined in create_nework.py """ def setUp(self): @@ -346,14 +366,14 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase): guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) self.net_config = openstack_tests.get_pub_net_config( net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet', - router_name=guid + '-pub-router', external_net=self.ext_net_name) + router_name=guid + '-pub-router', external_net=self.ext_net_name, + netconf_override=self.netconf_override) self.neutron = neutron_utils.neutron_client(self.os_creds) # Initialize for cleanup self.net_creator = None self.router_creator = None - self.neutron = neutron_utils.neutron_client(self.os_creds) def tearDown(self): """ @@ -371,7 +391,7 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase): """ Tests the creation of an OpenStack network without a router. """ - # Create Nework + # Create Network self.net_creator = OpenStackNetwork(self.os_creds, self.net_config.network_settings) self.net_creator.create() @@ -390,7 +410,7 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase): """ Tests the creation of an OpenStack network, it's deletion, then cleanup """ - # Create Nework + # Create Network self.net_creator = OpenStackNetwork(self.os_creds, self.net_config.network_settings) self.net_creator.create() @@ -438,14 +458,14 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase): neutron_utils_tests.validate_interface_router( self.router_creator.get_internal_router_interface(), self.router_creator.get_router(), - self.net_creator.get_subnets()[0]) + self.net_creator.get_network().subnets[0]) def test_create_networks_same_name(self): """ Tests the creation of an OpenStack network and ensures that the OpenStackNetwork object will not create a second. """ - # Create Nework + # Create Network self.net_creator = OpenStackNetwork(self.os_creds, self.net_config.network_settings) self.net_creator.create() @@ -518,9 +538,108 @@ class CreateNetworkSuccessTests(OSIntegrationTestCase): self.router_creator.get_router().id, retrieved_router.id) +class CreateNetworkIPv6Tests(OSIntegrationTestCase): + """ + Test for the CreateNetwork class defined in create_nework.py when + """ + + def setUp(self): + """ + Sets up object for test + """ + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.neutron = neutron_utils.neutron_client(self.os_creds) + + # Initialize for cleanup + self.net_creator = None + + def tearDown(self): + """ + Cleans the network + """ + if self.net_creator: + self.net_creator.clean() + + super(self.__class__, self).__clean__() + + def test_create_network_one_ipv6_subnet(self): + """ + Tests the creation of an OpenStack network without a router. + """ + # Create Network + subnet_settings = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6) + network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[subnet_settings]) + + self.net_creator = OpenStackNetwork(self.os_creds, network_settings) + self.net_creator.create() + + # Validate network was created + self.assertTrue(neutron_utils_tests.validate_network( + self.neutron, self.net_creator.network_settings.name, True)) + + network = self.net_creator.get_network() + self.assertEqual(1, len(network.subnets)) + subnet = network.subnets[0] + + self.assertEqual(network.id, subnet.network_id) + self.assertEqual(subnet_settings.name, subnet.name) + self.assertEqual('1:1::/64', subnet.cidr) + self.assertEqual(6, subnet.ip_version) + self.assertEqual(0, len(subnet.dns_nameservers)) + + def test_create_network_ipv4_ipv6_subnet(self): + """ + Tests the creation of an OpenStack network without a router. + """ + # Create Network + subnet4_settings = SubnetConfig( + name=self.guid + '-subnet4', cidr='10.0.1.0/24', ip_version=4) + subnet6_settings = SubnetConfig( + name=self.guid + '-subnet6', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6) + + network_settings = NetworkConfig( + name=self.guid + '-net', + subnet_settings=[subnet4_settings, subnet6_settings]) + + self.net_creator = OpenStackNetwork(self.os_creds, network_settings) + self.net_creator.create() + + # Validate network was created + network = self.net_creator.get_network() + self.assertEqual(2, len(network.subnets)) + + subnet4 = None + subnet6 = None + for subnet in network.subnets: + if subnet.name == subnet4_settings.name: + subnet4 = subnet + if subnet.name == subnet6_settings.name: + subnet6 = subnet + + # Validate IPv4 subnet + self.assertEqual(network.id, subnet4.network_id) + self.assertEqual(subnet4_settings.name, subnet4.name) + self.assertEqual(subnet4_settings.cidr, subnet4.cidr) + self.assertEqual(4, subnet4.ip_version) + self.assertEqual(1, len(subnet4.dns_nameservers)) + + # Validate IPv6 subnet + self.assertEqual(network.id, subnet6.network_id) + self.assertEqual(subnet6_settings.name, subnet6.name) + self.assertEqual('1:1::/64', subnet6.cidr) + self.assertEqual(6, subnet6.ip_version) + self.assertEqual(0, len(subnet6.dns_nameservers)) + + class CreateNetworkTypeTests(OSComponentTestCase): """ - Test for the CreateNework class defined in create_nework.py for testing + Test for the CreateNetwork class defined in create_nework.py for testing creating networks of different types """ @@ -536,7 +655,6 @@ class CreateNetworkTypeTests(OSComponentTestCase): # Initialize for cleanup self.net_creator = None - self.neutron = neutron_utils.neutron_client(self.os_creds) def tearDown(self): """ @@ -551,7 +669,7 @@ class CreateNetworkTypeTests(OSComponentTestCase): """ # Create Network network_type = 'vlan' - net_settings = NetworkSettings( + net_settings = NetworkConfig( name=self.net_config.network_settings.name, subnet_settings=self.net_config.network_settings.subnet_settings, network_type=network_type) @@ -578,7 +696,7 @@ class CreateNetworkTypeTests(OSComponentTestCase): physical_network = 'datacentre' segmentation_id = 2366 - net_settings = NetworkSettings( + net_settings = NetworkConfig( name=self.net_config.network_settings.name, subnet_settings=self.net_config.network_settings.subnet_settings, network_type=network_type, @@ -601,7 +719,7 @@ class CreateNetworkTypeTests(OSComponentTestCase): """ # Create Network network_type = 'vxlan' - net_settings = NetworkSettings( + net_settings = NetworkConfig( name=self.net_config.network_settings.name, subnet_settings=self.net_config.network_settings.subnet_settings, network_type=network_type) @@ -626,7 +744,7 @@ class CreateNetworkTypeTests(OSComponentTestCase): # This value must be variable to work on all OpenStack pods physical_network = 'datacentre' - net_settings = NetworkSettings( + net_settings = NetworkConfig( name=self.net_config.network_settings.name, subnet_settings=self.net_config.network_settings.subnet_settings, network_type=network_type, physical_network=physical_network) @@ -646,7 +764,7 @@ class CreateNetworkTypeTests(OSComponentTestCase): """ # Create Network network_type = 'foo' - net_settings = NetworkSettings( + net_settings = NetworkConfig( name=self.net_config.network_settings.name, subnet_settings=self.net_config.network_settings.subnet_settings, network_type=network_type) diff --git a/snaps/openstack/tests/create_project_tests.py b/snaps/openstack/tests/create_project_tests.py index aa9dcfb..2c10311 100644 --- a/snaps/openstack/tests/create_project_tests.py +++ b/snaps/openstack/tests/create_project_tests.py @@ -17,13 +17,14 @@ import uuid from keystoneclient.exceptions import BadRequest +from snaps.config.security_group import SecurityGroupConfig +from snaps.config.user import UserConfig +from snaps.config.project import ProjectConfigError, ProjectConfig from snaps.domain.project import ComputeQuotas, NetworkQuotas from snaps.openstack.create_project import ( - OpenStackProject, ProjectSettings, ProjectSettingsError) + OpenStackProject, ProjectSettings) from snaps.openstack.create_security_group import OpenStackSecurityGroup -from snaps.openstack.create_security_group import SecurityGroupSettings from snaps.openstack.create_user import OpenStackUser -from snaps.openstack.create_user import UserSettings from snaps.openstack.tests.os_source_file_test import OSComponentTestCase from snaps.openstack.utils import keystone_utils, nova_utils, neutron_utils @@ -36,11 +37,11 @@ class ProjectSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(ProjectSettingsError): + with self.assertRaises(ProjectConfigError): ProjectSettings() def test_empty_config(self): - with self.assertRaises(ProjectSettingsError): + with self.assertRaises(ProjectConfigError): ProjectSettings(**dict()) def test_name_only(self): @@ -94,7 +95,7 @@ class CreateProjectSuccessTests(OSComponentTestCase): """ guid = str(uuid.uuid4())[:-19] guid = self.__class__.__name__ + '-' + guid - self.project_settings = ProjectSettings( + self.project_settings = ProjectConfig( name=guid + '-name', domain=self.os_creds.project_domain_name) @@ -236,7 +237,7 @@ class CreateProjectUserTests(OSComponentTestCase): """ guid = str(uuid.uuid4())[:-19] self.guid = self.__class__.__name__ + '-' + guid - self.project_settings = ProjectSettings( + self.project_settings = ProjectConfig( name=self.guid + '-name', domain=self.os_creds.project_domain_name) @@ -272,9 +273,10 @@ class CreateProjectUserTests(OSComponentTestCase): self.assertIsNotNone(created_project) user_creator = OpenStackUser( - self.os_creds, UserSettings( + self.os_creds, UserConfig( name=self.guid + '-user', - password=self.guid, roles={'admin': self.project_settings.name}, + password=self.guid, + roles={'admin': self.project_settings.name}, domain_name=self.os_creds.user_domain_name)) self.project_creator.assoc_user(user_creator.create()) self.user_creators.append(user_creator) @@ -282,8 +284,8 @@ class CreateProjectUserTests(OSComponentTestCase): sec_grp_os_creds = user_creator.get_os_creds( self.project_creator.get_project().name) sec_grp_creator = OpenStackSecurityGroup( - sec_grp_os_creds, SecurityGroupSettings(name=self.guid + '-name', - description='hello group')) + sec_grp_os_creds, SecurityGroupConfig( + name=self.guid + '-name', description='hello group')) sec_grp = sec_grp_creator.create() self.assertIsNotNone(sec_grp) self.sec_grp_creators.append(sec_grp_creator) @@ -302,7 +304,7 @@ class CreateProjectUserTests(OSComponentTestCase): self.assertIsNotNone(created_project) user_creator_1 = OpenStackUser( - self.os_creds, UserSettings( + self.os_creds, UserConfig( name=self.guid + '-user1', password=self.guid, roles={'admin': self.project_settings.name}, domain_name=self.os_creds.user_domain_name)) @@ -310,7 +312,7 @@ class CreateProjectUserTests(OSComponentTestCase): self.user_creators.append(user_creator_1) user_creator_2 = OpenStackUser( - self.os_creds, UserSettings( + self.os_creds, UserConfig( name=self.guid + '-user2', password=self.guid, roles={'admin': self.project_settings.name}, domain_name=self.os_creds.user_domain_name)) @@ -325,8 +327,8 @@ class CreateProjectUserTests(OSComponentTestCase): sec_grp_creator = OpenStackSecurityGroup( sec_grp_os_creds, - SecurityGroupSettings(name=self.guid + '-name', - description='hello group')) + SecurityGroupConfig( + name=self.guid + '-name', description='hello group')) sec_grp = sec_grp_creator.create() self.assertIsNotNone(sec_grp) self.sec_grp_creators.append(sec_grp_creator) diff --git a/snaps/openstack/tests/create_qos_tests.py b/snaps/openstack/tests/create_qos_tests.py index 6c0a056..68737f8 100644 --- a/snaps/openstack/tests/create_qos_tests.py +++ b/snaps/openstack/tests/create_qos_tests.py @@ -12,6 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from snaps.config.qos import Consumer, QoSConfigError, QoSConfig +from snaps.openstack.create_qos import Consumer as Consumer_old try: from urllib.request import URLError @@ -23,8 +25,7 @@ import unittest import uuid from snaps.openstack import create_qos -from snaps.openstack.create_qos import (QoSSettings, QoSSettingsError, - Consumer) +from snaps.openstack.create_qos import QoSSettings from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase from snaps.openstack.utils import cinder_utils @@ -39,64 +40,64 @@ class QoSSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(QoSSettingsError): + with self.assertRaises(QoSConfigError): QoSSettings() def test_empty_config(self): - with self.assertRaises(QoSSettingsError): + with self.assertRaises(QoSConfigError): QoSSettings(**dict()) def test_name_only(self): - with self.assertRaises(QoSSettingsError): + with self.assertRaises(QoSConfigError): QoSSettings(name='foo') def test_config_with_name_only(self): - with self.assertRaises(QoSSettingsError): + with self.assertRaises(QoSConfigError): QoSSettings(**{'name': 'foo'}) def test_invalid_consumer(self): - with self.assertRaises(QoSSettingsError): + with self.assertRaises(QoSConfigError): QoSSettings(name='foo', consumer='bar') def test_config_with_invalid_consumer(self): - with self.assertRaises(QoSSettingsError): + with self.assertRaises(QoSConfigError): QoSSettings(**{'name': 'foo', 'consumer': 'bar'}) def test_name_consumer(self): - settings = QoSSettings(name='foo', consumer=Consumer.front_end) + settings = QoSSettings(name='foo', consumer=Consumer_old.front_end) self.assertEqual('foo', settings.name) - self.assertEqual(Consumer.front_end, settings.consumer) + self.assertEqual(Consumer_old.front_end.value, settings.consumer.value) self.assertEqual(dict(), settings.specs) def test_name_consumer_front_end_strings(self): settings = QoSSettings(name='foo', consumer='front-end') self.assertEqual('foo', settings.name) - self.assertEqual(Consumer.front_end, settings.consumer) + self.assertEqual(Consumer_old.front_end.value, settings.consumer.value) self.assertEqual(dict(), settings.specs) def test_name_consumer_back_end_strings(self): settings = QoSSettings(name='foo', consumer='back-end') self.assertEqual('foo', settings.name) - self.assertEqual(Consumer.back_end, settings.consumer) + self.assertEqual(Consumer_old.back_end.value, settings.consumer.value) self.assertEqual(dict(), settings.specs) def test_name_consumer_both_strings(self): settings = QoSSettings(name='foo', consumer='both') self.assertEqual('foo', settings.name) - self.assertEqual(Consumer.both, settings.consumer) + self.assertEqual(Consumer_old.both.value, settings.consumer.value) self.assertEqual(dict(), settings.specs) def test_all(self): specs = {'spec1': 'val1', 'spec2': 'val2'} - settings = QoSSettings(name='foo', consumer=Consumer.both, + settings = QoSSettings(name='foo', consumer=Consumer_old.both, specs=specs) self.assertEqual('foo', settings.name) - self.assertEqual(Consumer.both, settings.consumer) + self.assertEqual(Consumer_old.both.value, settings.consumer.value) self.assertEqual(specs, settings.specs) def test_config_all(self): @@ -121,7 +122,7 @@ class CreateQoSTests(OSIntegrationTestCase): super(self.__class__, self).__start__() guid = uuid.uuid4() - self.qos_settings = QoSSettings( + self.qos_settings = QoSConfig( name=self.__class__.__name__ + '-' + str(guid), consumer=Consumer.both) diff --git a/snaps/openstack/tests/create_router_tests.py b/snaps/openstack/tests/create_router_tests.py index db3170e..09471a3 100644 --- a/snaps/openstack/tests/create_router_tests.py +++ b/snaps/openstack/tests/create_router_tests.py @@ -15,15 +15,14 @@ import unittest import uuid +from snaps.config.network import PortConfig, NetworkConfig +from snaps.config.router import RouterConfigError, RouterConfig from snaps.openstack import create_network from snaps.openstack import create_router -from snaps.openstack.create_network import ( - NetworkSettings, PortSettings) from snaps.openstack.create_network import OpenStackNetwork -from snaps.openstack.create_router import ( - RouterSettings, RouterSettingsError) +from snaps.openstack.create_router import RouterSettings from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase -from snaps.openstack.utils import neutron_utils +from snaps.openstack.utils import neutron_utils, settings_utils __author__ = 'mmakati' @@ -39,11 +38,11 @@ class RouterSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(RouterSettingsError): + with self.assertRaises(RouterConfigError): RouterSettings() def test_empty_config(self): - with self.assertRaises(RouterSettingsError): + with self.assertRaises(RouterConfigError): RouterSettings(**dict()) def test_name_only(self): @@ -51,9 +50,8 @@ class RouterSettingsUnitTests(unittest.TestCase): self.assertEqual('foo', settings.name) self.assertIsNone(settings.project_name) self.assertIsNone(settings.external_gateway) - self.assertIsNone(settings.admin_state_up) + self.assertTrue(settings.admin_state_up) self.assertIsNone(settings.enable_snat) - self.assertIsNone(settings.external_fixed_ips) self.assertIsNotNone(settings.internal_subnets) self.assertTrue(isinstance(settings.internal_subnets, list)) self.assertEqual(0, len(settings.internal_subnets)) @@ -66,9 +64,8 @@ class RouterSettingsUnitTests(unittest.TestCase): self.assertEqual('foo', settings.name) self.assertIsNone(settings.project_name) self.assertIsNone(settings.external_gateway) - self.assertIsNone(settings.admin_state_up) + self.assertTrue(settings.admin_state_up) self.assertIsNone(settings.enable_snat) - self.assertIsNone(settings.external_fixed_ips) self.assertIsNotNone(settings.internal_subnets) self.assertTrue(isinstance(settings.internal_subnets, list)) self.assertEqual(0, len(settings.internal_subnets)) @@ -77,17 +74,16 @@ class RouterSettingsUnitTests(unittest.TestCase): self.assertEqual(0, len(settings.port_settings)) def test_all(self): - port_settings = PortSettings(name='foo', network_name='bar') + port_settings = PortConfig(name='foo', network_name='bar') settings = RouterSettings( name='foo', project_name='bar', external_gateway='foo_gateway', - admin_state_up=True, enable_snat=False, external_fixed_ips=['ip1'], + admin_state_up=True, enable_snat=False, internal_subnets=['10.0.0.1/24'], interfaces=[port_settings]) self.assertEqual('foo', settings.name) self.assertEqual('bar', settings.project_name) self.assertEqual('foo_gateway', settings.external_gateway) self.assertTrue(settings.admin_state_up) self.assertFalse(settings.enable_snat) - self.assertEqual(['ip1'], settings.external_fixed_ips) self.assertIsNotNone(settings.internal_subnets) self.assertTrue(isinstance(settings.internal_subnets, list)) self.assertEqual(1, len(settings.internal_subnets)) @@ -98,8 +94,7 @@ class RouterSettingsUnitTests(unittest.TestCase): settings = RouterSettings( **{'name': 'foo', 'project_name': 'bar', 'external_gateway': 'foo_gateway', 'admin_state_up': True, - 'enable_snat': False, 'external_fixed_ips': ['ip1'], - 'internal_subnets': ['10.0.0.1/24'], + 'enable_snat': False, 'internal_subnets': ['10.0.0.1/24'], 'interfaces': [{'port': {'name': 'foo-port', 'network_name': 'bar-net'}}]}) @@ -108,14 +103,13 @@ class RouterSettingsUnitTests(unittest.TestCase): self.assertEqual('foo_gateway', settings.external_gateway) self.assertTrue(settings.admin_state_up) self.assertFalse(settings.enable_snat) - self.assertEqual(['ip1'], settings.external_fixed_ips) self.assertIsNotNone(settings.internal_subnets) self.assertTrue(isinstance(settings.internal_subnets, list)) self.assertEqual(1, len(settings.internal_subnets)) self.assertEqual(['10.0.0.1/24'], settings.internal_subnets) - self.assertEqual([PortSettings(**{'name': 'foo-port', - 'network_name': 'bar-net'})], - settings.port_settings) + self.assertEqual( + [PortConfig(**{'name': 'foo-port', 'network_name': 'bar-net'})], + settings.port_settings) class CreateRouterSuccessTests(OSIntegrationTestCase): @@ -155,8 +149,8 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): """ Test creation of a most basic router with minimal options. """ - router_settings = RouterSettings(name=self.guid + '-pub-router', - external_gateway=self.ext_net_name) + router_settings = RouterConfig( + name=self.guid + '-pub-router', external_gateway=self.ext_net_name) self.router_creator = create_router.OpenStackRouter(self.os_creds, router_settings) @@ -166,15 +160,16 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): router_settings=router_settings) self.assertIsNotNone(router) - self.assertTrue(verify_router_attributes( - router, self.router_creator, ext_gateway=self.ext_net_name)) + self.assertEqual(self.router_creator.get_router(), router) + + self.check_router_recreation(router, router_settings) def test_create_router_admin_user_to_new_project(self): """ Test creation of a most basic router with the admin user pointing to the new project. """ - router_settings = RouterSettings( + router_settings = RouterConfig( name=self.guid + '-pub-router', external_gateway=self.ext_net_name, project_name=self.os_creds.project_name) @@ -186,15 +181,16 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): router_settings=router_settings) self.assertIsNotNone(router) - self.assertTrue(verify_router_attributes( - router, self.router_creator, ext_gateway=self.ext_net_name)) + self.assertEqual(self.router_creator.get_router(), router) + + self.check_router_recreation(router, router_settings) def test_create_router_new_user_to_admin_project(self): """ Test creation of a most basic router with the new user pointing to the admin project. """ - router_settings = RouterSettings( + router_settings = RouterConfig( name=self.guid + '-pub-router', external_gateway=self.ext_net_name, project_name=self.admin_os_creds.project_name) @@ -206,15 +202,16 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): router_settings=router_settings) self.assertIsNotNone(router) - self.assertTrue(verify_router_attributes( - router, self.router_creator, ext_gateway=self.ext_net_name)) + self.assertEqual(self.router_creator.get_router(), router) + + self.check_router_recreation(router, router_settings) def test_create_delete_router(self): """ Test that clean() will not raise an exception if the router is deleted by another process. """ - self.router_settings = RouterSettings( + self.router_settings = RouterConfig( name=self.guid + '-pub-router', external_gateway=self.ext_net_name) self.router_creator = create_router.OpenStackRouter( @@ -238,8 +235,8 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): """ Test creation of a basic router with admin state down. """ - router_settings = RouterSettings(name=self.guid + '-pub-router', - admin_state_up=False) + router_settings = RouterConfig( + name=self.guid + '-pub-router', admin_state_up=False) self.router_creator = create_router.OpenStackRouter(self.os_creds, router_settings) @@ -249,42 +246,44 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): router_settings=router_settings) self.assertIsNotNone(router) - self.assertTrue(verify_router_attributes(router, self.router_creator, - admin_state=False)) + self.assertEqual(self.router_creator.get_router(), router) + + self.check_router_recreation(router, router_settings) def test_create_router_admin_state_True(self): """ Test creation of a basic router with admin state Up. """ - router_settings = RouterSettings(name=self.guid + '-pub-router', - admin_state_up=True) + router_settings = RouterConfig( + name=self.guid + '-pub-router', admin_state_up=True) - self.router_creator = create_router.OpenStackRouter(self.os_creds, - router_settings) + self.router_creator = create_router.OpenStackRouter( + self.os_creds, router_settings) self.router_creator.create() - router = neutron_utils.get_router(self.neutron, - router_settings=router_settings) + router = neutron_utils.get_router( + self.neutron, router_settings=router_settings) self.assertIsNotNone(router) - self.assertTrue(verify_router_attributes(router, self.router_creator, - admin_state=True)) + self.assertEqual(self.router_creator.get_router(), router) + + self.check_router_recreation(router, router_settings) def test_create_router_private_network(self): """ Test creation of a router connected with two private networks and no external gateway """ - network_settings1 = NetworkSettings( + network_settings1 = NetworkConfig( name=self.guid + '-pub-net1', subnet_settings=[ - create_network.SubnetSettings( + create_network.SubnetConfig( cidr=cidr1, name=self.guid + '-pub-subnet1', gateway_ip=static_gateway_ip1)]) - network_settings2 = NetworkSettings( + network_settings2 = NetworkConfig( name=self.guid + '-pub-net2', subnet_settings=[ - create_network.SubnetSettings( + create_network.SubnetConfig( cidr=cidr2, name=self.guid + '-pub-subnet2', gateway_ip=static_gateway_ip2)]) @@ -297,7 +296,7 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): self.network_creator2.create() port_settings = [ - create_network.PortSettings( + create_network.PortConfig( name=self.guid + '-port1', ip_addrs=[{ 'subnet_name': @@ -306,7 +305,7 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): }], network_name=network_settings1.name, project_name=self.os_creds.project_name), - create_network.PortSettings( + create_network.PortConfig( name=self.guid + '-port2', ip_addrs=[{ 'subnet_name': network_settings2.subnet_settings[0].name, @@ -315,16 +314,16 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): network_name=network_settings2.name, project_name=self.os_creds.project_name)] - router_settings = RouterSettings(name=self.guid + '-pub-router', - port_settings=port_settings) + router_settings = RouterConfig( + name=self.guid + '-pub-router', port_settings=port_settings) self.router_creator = create_router.OpenStackRouter(self.os_creds, router_settings) self.router_creator.create() - router = neutron_utils.get_router(self.neutron, - router_settings=router_settings) + router = neutron_utils.get_router( + self.neutron, router_settings=router_settings) - self.assertTrue(verify_router_attributes(router, self.router_creator)) + self.assertEqual(router, self.router_creator.get_router()) # Instantiate second identical creator to ensure a second router # has not been created @@ -333,15 +332,17 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): router2 = router_creator2.create() self.assertIsNotNone(self.router_creator.get_router(), router2) + self.check_router_recreation(router2, router_settings) + def test_create_router_external_network(self): """ Test creation of a router connected to an external network and a private network. """ - network_settings = NetworkSettings( + network_settings = NetworkConfig( name=self.guid + '-pub-net1', subnet_settings=[ - create_network.SubnetSettings( + create_network.SubnetConfig( cidr=cidr1, name=self.guid + '-pub-subnet1', gateway_ip=static_gateway_ip1)]) self.network_creator1 = OpenStackNetwork(self.os_creds, @@ -349,7 +350,7 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): self.network_creator1.create() port_settings = [ - create_network.PortSettings( + create_network.PortConfig( name=self.guid + '-port1', ip_addrs=[{ 'subnet_name': network_settings.subnet_settings[0].name, @@ -357,18 +358,55 @@ class CreateRouterSuccessTests(OSIntegrationTestCase): network_name=network_settings.name, project_name=self.os_creds.project_name)] - router_settings = RouterSettings( + router_settings = RouterConfig( name=self.guid + '-pub-router', external_gateway=self.ext_net_name, port_settings=port_settings) - self.router_creator = create_router.OpenStackRouter(self.os_creds, - router_settings) + self.router_creator = create_router.OpenStackRouter( + self.os_creds, router_settings) self.router_creator.create() - router = neutron_utils.get_router(self.neutron, - router_settings=router_settings) + router = neutron_utils.get_router( + self.neutron, router_settings=router_settings) + + self.assertEquals(router, self.router_creator.get_router()) - self.assertTrue(verify_router_attributes( - router, self.router_creator, ext_gateway=self.ext_net_name)) + self.check_router_recreation(router, router_settings) + + def check_router_recreation(self, router, orig_settings): + """ + Validates the derived RouterConfig with the original + :param router: the Router domain object to test + :param orig_settings: the original RouterConfig object that was + responsible for creating the router + :return: the derived RouterConfig object + """ + derived_settings = settings_utils.create_router_config( + self.neutron, router) + self.assertIsNotNone(derived_settings) + self.assertEqual( + orig_settings.enable_snat, derived_settings.enable_snat) + self.assertEqual(orig_settings.external_gateway, + derived_settings.external_gateway) + self.assertEqual(orig_settings.name, derived_settings.name) + self.assertEqual(orig_settings.internal_subnets, + derived_settings.internal_subnets) + + if orig_settings.external_gateway: + self.assertEqual(len(orig_settings.port_settings), + len(derived_settings.port_settings)) + else: + self.assertEqual(len(orig_settings.port_settings), + len(derived_settings.port_settings)) + + if len(orig_settings.port_settings) > 0: + self.assertEqual(orig_settings.port_settings[0].name, + derived_settings.port_settings[0].name) + + if len(orig_settings.port_settings) > 1: + self.assertEqual(orig_settings.port_settings[1].name, + derived_settings.port_settings[1].name) + + return derived_settings class CreateRouterNegativeTests(OSIntegrationTestCase): @@ -398,8 +436,8 @@ class CreateRouterNegativeTests(OSIntegrationTestCase): """ Test creating a router without a name. """ - with self.assertRaises(RouterSettingsError): - router_settings = RouterSettings( + with self.assertRaises(RouterConfigError): + router_settings = RouterConfig( name=None, external_gateway=self.ext_net_name) self.router_creator = create_router.OpenStackRouter( self.os_creds, router_settings) @@ -409,48 +447,10 @@ class CreateRouterNegativeTests(OSIntegrationTestCase): """ Test creating a router without a valid network gateway name. """ - with self.assertRaises(RouterSettingsError): - router_settings = RouterSettings(name=self.guid + '-pub-router', - external_gateway="Invalid_name") + with self.assertRaises(RouterConfigError): + router_settings = RouterConfig( + name=self.guid + '-pub-router', + external_gateway="Invalid_name") self.router_creator = create_router.OpenStackRouter( self.os_creds, router_settings) self.router_creator.create() - - -def verify_router_attributes(router_operational, router_creator, - admin_state=True, ext_gateway=None): - """ - Helper function to validate the attributes of router created with the one - operational - :param router_operational: Operational Router object returned from neutron - utils of type snaps.domain.Router - :param router_creator: router_creator object returned from creating a - router in the router test functions - :param admin_state: True if router is expected to be Up, else False - :param ext_gateway: None if router is not connected to external gateway - :return: - """ - - router = router_creator.get_router() - - if not router_operational: - return False - elif not router_creator: - return False - elif not (router_operational.name == router_creator.router_settings.name): - return False - elif not (router_operational.id == router.id): - return False - elif not (router_operational.status == router.status): - return False - elif not (router_operational.tenant_id == router.tenant_id): - return False - elif not (admin_state == router_operational.admin_state_up): - return False - elif (ext_gateway is None) and \ - (router_operational.external_gateway_info is not None): - return False - elif ext_gateway is not None: - if router_operational.external_gateway_info is None: - return False - return True diff --git a/snaps/openstack/tests/create_security_group_tests.py b/snaps/openstack/tests/create_security_group_tests.py index 99ea53a..090d736 100644 --- a/snaps/openstack/tests/create_security_group_tests.py +++ b/snaps/openstack/tests/create_security_group_tests.py @@ -15,10 +15,13 @@ import unittest import uuid +from snaps.config.security_group import ( + SecurityGroupConfig, SecurityGroupRuleConfig, + SecurityGroupRuleConfigError, SecurityGroupConfigError) from snaps.openstack import create_security_group from snaps.openstack.create_security_group import ( SecurityGroupSettings, SecurityGroupRuleSettings, Direction, Ethertype, - Protocol, SecurityGroupRuleSettingsError, SecurityGroupSettingsError) + Protocol) from snaps.openstack.tests import validation_utils from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase from snaps.openstack.utils import neutron_utils @@ -32,32 +35,64 @@ class SecurityGroupRuleSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(SecurityGroupRuleSettingsError): + with self.assertRaises(SecurityGroupRuleConfigError): SecurityGroupRuleSettings() def test_empty_config(self): - with self.assertRaises(SecurityGroupRuleSettingsError): + with self.assertRaises(SecurityGroupRuleConfigError): SecurityGroupRuleSettings(**dict()) def test_name_only(self): - with self.assertRaises(SecurityGroupRuleSettingsError): + with self.assertRaises(SecurityGroupRuleConfigError): SecurityGroupRuleSettings(sec_grp_name='foo') def test_config_with_name_only(self): - with self.assertRaises(SecurityGroupRuleSettingsError): + with self.assertRaises(SecurityGroupRuleConfigError): SecurityGroupRuleSettings(**{'sec_grp_name': 'foo'}) def test_name_and_direction(self): settings = SecurityGroupRuleSettings(sec_grp_name='foo', direction=Direction.ingress) self.assertEqual('foo', settings.sec_grp_name) - self.assertEqual(Direction.ingress, settings.direction) + self.assertEqual(Direction.ingress.value, settings.direction.value) def test_config_name_and_direction(self): settings = SecurityGroupRuleSettings( **{'sec_grp_name': 'foo', 'direction': 'ingress'}) self.assertEqual('foo', settings.sec_grp_name) - self.assertEqual(Direction.ingress, settings.direction) + self.assertEqual(Direction.ingress.value, settings.direction.value) + + def test_proto_ah_str(self): + settings = SecurityGroupRuleSettings( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 'ah'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress.value, settings.direction.value) + self.assertEqual(Protocol.ah.value, settings.protocol.value) + + def test_proto_ah_value(self): + settings = SecurityGroupRuleSettings( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 51}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress.value, settings.direction.value) + self.assertEqual(Protocol.ah.value, settings.protocol.value) + + def test_proto_any(self): + settings = SecurityGroupRuleSettings( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 'any'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress.value, settings.direction.value) + self.assertEqual(Protocol.null.value, settings.protocol.value) + + def test_proto_null(self): + settings = SecurityGroupRuleSettings( + **{'sec_grp_name': 'foo', 'direction': 'ingress', + 'protocol': 'null'}) + self.assertEqual('foo', settings.sec_grp_name) + self.assertEqual(Direction.ingress.value, settings.direction.value) + self.assertEqual(Protocol.null.value, settings.protocol.value) def test_all(self): settings = SecurityGroupRuleSettings( @@ -68,10 +103,10 @@ class SecurityGroupRuleSettingsUnitTests(unittest.TestCase): remote_ip_prefix='prfx') self.assertEqual('foo', settings.sec_grp_name) self.assertEqual('fubar', settings.description) - self.assertEqual(Direction.egress, settings.direction) + self.assertEqual(Direction.egress.value, settings.direction.value) self.assertEqual('rgi', settings.remote_group_id) - self.assertEqual(Protocol.icmp, settings.protocol) - self.assertEqual(Ethertype.IPv6, settings.ethertype) + self.assertEqual(Protocol.icmp.value, settings.protocol.value) + self.assertEqual(Ethertype.IPv6.value, settings.ethertype.value) self.assertEqual(1, settings.port_range_min) self.assertEqual(2, settings.port_range_max) self.assertEqual('prfx', settings.remote_ip_prefix) @@ -89,10 +124,10 @@ class SecurityGroupRuleSettingsUnitTests(unittest.TestCase): 'remote_ip_prefix': 'prfx'}) self.assertEqual('foo', settings.sec_grp_name) self.assertEqual('fubar', settings.description) - self.assertEqual(Direction.egress, settings.direction) + self.assertEqual(Direction.egress.value, settings.direction.value) self.assertEqual('rgi', settings.remote_group_id) - self.assertEqual(Protocol.tcp, settings.protocol) - self.assertEqual(Ethertype.IPv6, settings.ethertype) + self.assertEqual(Protocol.tcp.value, settings.protocol.value) + self.assertEqual(Ethertype.IPv6.value, settings.ethertype.value) self.assertEqual(1, settings.port_range_min) self.assertEqual(2, settings.port_range_max) self.assertEqual('prfx', settings.remote_ip_prefix) @@ -104,11 +139,11 @@ class SecurityGroupSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(SecurityGroupSettingsError): + with self.assertRaises(SecurityGroupConfigError): SecurityGroupSettings() def test_empty_config(self): - with self.assertRaises(SecurityGroupSettingsError): + with self.assertRaises(SecurityGroupConfigError): SecurityGroupSettings(**dict()) def test_name_only(self): @@ -123,7 +158,7 @@ class SecurityGroupSettingsUnitTests(unittest.TestCase): rule_setting = SecurityGroupRuleSettings( sec_grp_name='bar', direction=Direction.ingress, description='test_rule_1') - with self.assertRaises(SecurityGroupSettingsError): + with self.assertRaises(SecurityGroupConfigError): SecurityGroupSettings(name='foo', rule_settings=[rule_setting]) def test_all(self): @@ -157,8 +192,8 @@ class SecurityGroupSettingsUnitTests(unittest.TestCase): self.assertEqual('foo', settings.project_name) self.assertEqual(1, len(settings.rule_settings)) self.assertEqual('bar', settings.rule_settings[0].sec_grp_name) - self.assertEqual(Direction.ingress, - settings.rule_settings[0].direction) + self.assertEqual(Direction.ingress.value, + settings.rule_settings[0].direction.value) class CreateSecurityGroupTests(OSIntegrationTestCase): @@ -194,8 +229,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): Tests the creation of an OpenStack Security Group without custom rules. """ # Create Image - sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name, - description='hello group') + sec_grp_settings = SecurityGroupConfig(name=self.sec_grp_name, + description='hello group') self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( self.os_creds, sec_grp_settings) self.sec_grp_creator.create() @@ -222,7 +257,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): Tests the creation of an OpenStack Security Group without custom rules. """ # Create Image - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', project_name=self.admin_os_creds.project_name) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -251,7 +286,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): Tests the creation of an OpenStack Security Group without custom rules. """ # Create Image - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', project_name=self.os_creds.project_name) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -280,8 +315,8 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): Tests the creation of an OpenStack Security Group without custom rules. """ # Create Image - sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name, - description='hello group') + sec_grp_settings = SecurityGroupConfig(name=self.sec_grp_name, + description='hello group') self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( self.os_creds, sec_grp_settings) created_sec_grp = self.sec_grp_creator.create() @@ -307,10 +342,10 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): # Create Image sec_grp_rule_settings = list() sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.ingress, description='test_rule_1')) - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', rule_settings=sec_grp_rule_settings) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -340,12 +375,12 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): # Create Image sec_grp_rule_settings = list() sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.egress, protocol=Protocol.udp, ethertype=Ethertype.IPv4, port_range_min=10, port_range_max=20, description='test_rule_1')) - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', rule_settings=sec_grp_rule_settings) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -375,21 +410,21 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): # Create Image sec_grp_rule_settings = list() sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.ingress, description='test_rule_1')) sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.egress, protocol=Protocol.udp, ethertype=Ethertype.IPv6, description='test_rule_2')) sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.egress, protocol=Protocol.udp, ethertype=Ethertype.IPv4, port_range_min=10, port_range_max=20, description='test_rule_3')) - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', rule_settings=sec_grp_rule_settings) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -419,10 +454,10 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): # Create Image sec_grp_rule_settings = list() sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.ingress, description='test_rule_1')) - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', rule_settings=sec_grp_rule_settings) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -448,7 +483,7 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): validation_utils.objects_equivalent(self.sec_grp_creator.get_rules(), rules) - self.sec_grp_creator.add_rule(SecurityGroupRuleSettings( + self.sec_grp_creator.add_rule(SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_creator.sec_grp_settings.name, direction=Direction.egress, protocol=Protocol.icmp, description='test_rule_2')) @@ -464,21 +499,21 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): # Create Image sec_grp_rule_settings = list() sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.ingress, description='test_rule_1')) sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.egress, protocol=Protocol.udp, ethertype=Ethertype.IPv6, description='test_rule_2')) sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.egress, protocol=Protocol.udp, ethertype=Ethertype.IPv4, port_range_min=10, port_range_max=20, description='test_rule_3')) - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', rule_settings=sec_grp_rule_settings) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -515,21 +550,21 @@ class CreateSecurityGroupTests(OSIntegrationTestCase): # Create Image sec_grp_rule_settings = list() sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.ingress, description='test_rule_1')) sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.egress, protocol=Protocol.udp, ethertype=Ethertype.IPv6, description='test_rule_2')) sec_grp_rule_settings.append( - SecurityGroupRuleSettings( + SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.egress, protocol=Protocol.udp, ethertype=Ethertype.IPv4, port_range_min=10, port_range_max=20, description='test_rule_3')) - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', rule_settings=sec_grp_rule_settings) self.sec_grp_creator = create_security_group.OpenStackSecurityGroup( @@ -583,7 +618,7 @@ def validate_sec_grp_rules(neutron, rule_settings, rules): this is the only means to tell if the rule is custom or defaulted by OpenStack :param neutron: the neutron client - :param rule_settings: collection of SecurityGroupRuleSettings objects + :param rule_settings: collection of SecurityGroupRuleConfig objects :param rules: a collection of SecurityGroupRule domain objects :return: T/F """ @@ -592,11 +627,6 @@ def validate_sec_grp_rules(neutron, rule_settings, rules): if rule_setting.description: match = False for rule in rules: - if rule_setting.protocol == Protocol.null: - setting_proto = None - else: - setting_proto = rule_setting.protocol.name - sec_grp = neutron_utils.get_security_group( neutron, sec_grp_name=rule_setting.sec_grp_name) @@ -607,15 +637,19 @@ def validate_sec_grp_rules(neutron, rule_settings, rules): if not sec_grp: return False + proto_str = 'null' + if rule.protocol: + proto_str = rule.protocol + if (rule.description == rule_setting.description and rule.direction == rule_setting.direction.name and rule.ethertype == setting_eth_type.name and rule.port_range_max == rule_setting.port_range_max and rule.port_range_min == rule_setting.port_range_min and - rule.protocol == setting_proto and + proto_str == str(rule_setting.protocol.value) and rule.remote_group_id == rule_setting.remote_group_id and rule.remote_ip_prefix == rule_setting.remote_ip_prefix and - rule.security_group_id == sec_grp.id): + rule.security_group_id == sec_grp.id): match = True break diff --git a/snaps/openstack/tests/create_stack_tests.py b/snaps/openstack/tests/create_stack_tests.py index f4eb1eb..6041735 100644 --- a/snaps/openstack/tests/create_stack_tests.py +++ b/snaps/openstack/tests/create_stack_tests.py @@ -12,12 +12,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import os import time import pkg_resources from heatclient.exc import HTTPBadRequest + +import snaps from snaps import file_utils -from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings +from snaps.config.flavor import FlavorConfig +from snaps.config.image import ImageConfig +from snaps.config.stack import StackConfigError, StackConfig +from snaps.openstack.create_flavor import OpenStackFlavor from snaps.openstack.create_image import OpenStackImage try: @@ -29,9 +35,8 @@ import logging import unittest import uuid -from snaps.openstack import create_stack from snaps.openstack.create_stack import ( - StackSettings, StackSettingsError, StackCreationError) + StackSettings, StackCreationError, StackError, OpenStackHeatStack) from snaps.openstack.tests import openstack_tests, create_instance_tests from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase from snaps.openstack.utils import heat_utils, neutron_utils, nova_utils @@ -47,19 +52,19 @@ class StackSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(StackSettingsError): + with self.assertRaises(StackConfigError): StackSettings() def test_empty_config(self): - with self.assertRaises(StackSettingsError): + with self.assertRaises(StackConfigError): StackSettings(**dict()) def test_name_only(self): - with self.assertRaises(StackSettingsError): + with self.assertRaises(StackConfigError): StackSettings(name='foo') def test_config_with_name_only(self): - with self.assertRaises(StackSettingsError): + with self.assertRaises(StackConfigError): StackSettings(**{'name': 'foo'}) def test_config_minimum_template(self): @@ -68,7 +73,7 @@ class StackSettingsUnitTests(unittest.TestCase): self.assertEqual('foo', settings.template) self.assertIsNone(settings.template_path) self.assertIsNone(settings.env_values) - self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT, + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, settings.stack_create_timeout) def test_config_minimum_template_path(self): @@ -77,7 +82,7 @@ class StackSettingsUnitTests(unittest.TestCase): self.assertIsNone(settings.template) self.assertEqual('foo', settings.template_path) self.assertIsNone(settings.env_values) - self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT, + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, settings.stack_create_timeout) def test_minimum_template(self): @@ -86,7 +91,7 @@ class StackSettingsUnitTests(unittest.TestCase): self.assertEqual('foo', settings.template) self.assertIsNone(settings.template_path) self.assertIsNone(settings.env_values) - self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT, + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, settings.stack_create_timeout) def test_minimum_template_path(self): @@ -95,7 +100,7 @@ class StackSettingsUnitTests(unittest.TestCase): self.assertEqual('foo', settings.template_path) self.assertIsNone(settings.template) self.assertIsNone(settings.env_values) - self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT, + self.assertEqual(snaps.config.stack.STACK_COMPLETE_TIMEOUT, settings.stack_create_timeout) def test_all(self): @@ -123,14 +128,11 @@ class StackSettingsUnitTests(unittest.TestCase): class CreateStackSuccessTests(OSIntegrationTestCase): """ - Tests for the CreateStack class defined in create_stack.py + Tests for the OpenStackHeatStack class defined in create_stack.py """ def setUp(self): - """ - Instantiates the CreateStack object that is responsible for downloading - and creating an OS stack file within OpenStack - """ + super(self.__class__, self).__start__() self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) @@ -150,8 +152,8 @@ class CreateStackSuccessTests(OSIntegrationTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.admin_os_creds, - FlavorSettings(name=self.guid + '-flavor-name', ram=256, disk=10, - vcpus=1)) + FlavorConfig( + name=self.guid + '-flavor-name', ram=256, disk=10, vcpus=1)) self.flavor_creator.create() self.network_name = self.guid + '-net' @@ -199,12 +201,12 @@ class CreateStackSuccessTests(OSIntegrationTestCase): # Create Stack # Set the default stack settings, then set any custom parameters sent # from the app - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template_path=self.heat_tmplt_path, env_values=self.env_values) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) created_stack = self.stack_creator.create() self.assertIsNotNone(created_stack) @@ -222,13 +224,13 @@ class CreateStackSuccessTests(OSIntegrationTestCase): # Create Stack # Set the default stack settings, then set any custom parameters sent # from the app - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template_path=self.heat_tmplt_path, env_values=self.env_values, stack_create_timeout=0) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) with self.assertRaises(StackCreationError): self.stack_creator.create() @@ -241,12 +243,12 @@ class CreateStackSuccessTests(OSIntegrationTestCase): # from the app template_dict = heat_utils.parse_heat_template_str( file_utils.read_file(self.heat_tmplt_path)) - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template=template_dict, env_values=self.env_values) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) created_stack = self.stack_creator.create() self.assertIsNotNone(created_stack) @@ -265,12 +267,12 @@ class CreateStackSuccessTests(OSIntegrationTestCase): # Create Stack template_dict = heat_utils.parse_heat_template_str( file_utils.read_file(self.heat_tmplt_path)) - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template=template_dict, env_values=self.env_values) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) created_stack = self.stack_creator.create() self.assertIsNotNone(created_stack) @@ -280,7 +282,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): self.assertEqual(created_stack.name, retrieved_stack.name) self.assertEqual(created_stack.id, retrieved_stack.id) self.assertEqual(0, len(self.stack_creator.get_outputs())) - self.assertEqual(create_stack.STATUS_CREATE_COMPLETE, + self.assertEqual(snaps.config.stack.STATUS_CREATE_COMPLETE, self.stack_creator.get_status()) # Delete Stack manually @@ -291,7 +293,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): while time.time() < end_time: status = heat_utils.get_stack_status(self.heat_cli, retrieved_stack.id) - if status == create_stack.STATUS_DELETE_COMPLETE: + if status == snaps.config.stack.STATUS_DELETE_COMPLETE: deleted = True break @@ -309,12 +311,12 @@ class CreateStackSuccessTests(OSIntegrationTestCase): # Create Stack template_dict = heat_utils.parse_heat_template_str( file_utils.read_file(self.heat_tmplt_path)) - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template=template_dict, env_values=self.env_values) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) created_stack1 = self.stack_creator.create() retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, @@ -325,8 +327,7 @@ class CreateStackSuccessTests(OSIntegrationTestCase): self.assertEqual(0, len(self.stack_creator.get_outputs())) # Should be retrieving the instance data - stack_creator2 = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + stack_creator2 = OpenStackHeatStack(self.heat_creds, stack_settings) stack2 = stack_creator2.create() self.assertEqual(created_stack1.id, stack2.id) @@ -335,12 +336,12 @@ class CreateStackSuccessTests(OSIntegrationTestCase): Tests the creation of an OpenStack stack from Heat template file and the retrieval of the network creator. """ - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template_path=self.heat_tmplt_path, env_values=self.env_values) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) created_stack = self.stack_creator.create() self.assertIsNotNone(created_stack) @@ -356,8 +357,8 @@ class CreateStackSuccessTests(OSIntegrationTestCase): self.assertIsNotNone(neutron_utils.get_network_by_id( neutron, net_creators[0].get_network().id)) - self.assertEqual(1, len(net_creators[0].get_subnets())) - subnet = net_creators[0].get_subnets()[0] + self.assertEqual(1, len(net_creators[0].get_network().subnets)) + subnet = net_creators[0].get_network().subnets[0] subnet_by_name = neutron_utils.get_subnet( neutron, subnet_name=subnet.name) self.assertEqual(subnet, subnet_by_name) @@ -371,12 +372,12 @@ class CreateStackSuccessTests(OSIntegrationTestCase): Tests the creation of an OpenStack stack from Heat template file and the retrieval of the network creator. """ - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template_path=self.heat_tmplt_path, env_values=self.env_values) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) created_stack = self.stack_creator.create() self.assertIsNotNone(created_stack) @@ -387,23 +388,22 @@ class CreateStackSuccessTests(OSIntegrationTestCase): vm_inst_creators[0].get_vm_inst().name) nova = nova_utils.nova_client(self.admin_os_creds) + neutron = neutron_utils.neutron_client(self.admin_os_creds) vm_inst_by_name = nova_utils.get_server( - nova, server_name=vm_inst_creators[0].get_vm_inst().name) + nova, neutron, server_name=vm_inst_creators[0].get_vm_inst().name) self.assertEqual(vm_inst_creators[0].get_vm_inst(), vm_inst_by_name) self.assertIsNotNone(nova_utils.get_server_object_by_id( - nova, vm_inst_creators[0].get_vm_inst().id)) + nova, neutron, vm_inst_creators[0].get_vm_inst().id)) -class CreateComplexStackTests(OSIntegrationTestCase): +class CreateStackFloatingIpTests(OSIntegrationTestCase): """ - Tests for the CreateStack class defined in create_stack.py + Tests to ensure that floating IPs can be accessed via an + OpenStackVmInstance object obtained from the OpenStackHeatStack instance """ def setUp(self): - """ - Instantiates the CreateStack object that is responsible for downloading - and creating an OS stack file within OpenStack - """ + super(self.__class__, self).__start__() self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) @@ -424,6 +424,7 @@ class CreateComplexStackTests(OSIntegrationTestCase): self.subnet_name = self.guid + '-subnet' self.flavor1_name = self.guid + '-flavor1' self.flavor2_name = self.guid + '-flavor2' + self.sec_grp_name = self.guid + '-sec_grp' self.vm_inst1_name = self.guid + '-inst1' self.vm_inst2_name = self.guid + '-inst2' self.keypair_name = self.guid + '-kp' @@ -437,11 +438,15 @@ class CreateComplexStackTests(OSIntegrationTestCase): 'subnet_name': self.subnet_name, 'inst1_name': self.vm_inst1_name, 'inst2_name': self.vm_inst2_name, - 'keypair_name': self.keypair_name} + 'keypair_name': self.keypair_name, + 'external_net_name': self.ext_net_name, + 'security_group_name': self.sec_grp_name} self.heat_tmplt_path = pkg_resources.resource_filename( 'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml') + self.vm_inst_creators = list() + def tearDown(self): """ Cleans the stack and downloaded stack file @@ -458,6 +463,17 @@ class CreateComplexStackTests(OSIntegrationTestCase): except: pass + for vm_inst_creator in self.vm_inst_creators: + try: + keypair_settings = vm_inst_creator.keypair_settings + if keypair_settings and keypair_settings.private_filepath: + expanded_path = os.path.expanduser( + keypair_settings.private_filepath) + os.chmod(expanded_path, 0o755) + os.remove(expanded_path) + except: + pass + super(self.__class__, self).__clean__() def test_connect_via_ssh_heat_vm(self): @@ -466,22 +482,22 @@ class CreateComplexStackTests(OSIntegrationTestCase): the retrieval of two VM instance creators and attempt to connect via SSH to the first one with a floating IP. """ - stack_settings = StackSettings( + stack_settings = StackConfig( name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', template_path=self.heat_tmplt_path, env_values=self.env_values) - self.stack_creator = create_stack.OpenStackHeatStack( + self.stack_creator = OpenStackHeatStack( self.heat_creds, stack_settings, [self.image_creator.image_settings]) created_stack = self.stack_creator.create() self.assertIsNotNone(created_stack) - vm_inst_creators = self.stack_creator.get_vm_inst_creators( + self.vm_inst_creators = self.stack_creator.get_vm_inst_creators( heat_keypair_option='private_key') - self.assertIsNotNone(vm_inst_creators) - self.assertEqual(2, len(vm_inst_creators)) + self.assertIsNotNone(self.vm_inst_creators) + self.assertEqual(2, len(self.vm_inst_creators)) - for vm_inst_creator in vm_inst_creators: + for vm_inst_creator in self.vm_inst_creators: if vm_inst_creator.get_vm_inst().name == self.vm_inst1_name: self.assertTrue( create_instance_tests.validate_ssh_client(vm_inst_creator)) @@ -490,12 +506,525 @@ class CreateComplexStackTests(OSIntegrationTestCase): self.assertEqual(0, len(vm_settings.floating_ip_settings)) +class CreateStackNestedResourceTests(OSIntegrationTestCase): + """ + Tests to ensure that nested heat templates work + """ + + def setUp(self): + + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.stack_creator = None + + self.image_creator = OpenStackImage( + self.heat_creds, openstack_tests.cirros_image_settings( + name=self.guid + '-image', + image_metadata=self.image_metadata)) + self.image_creator.create() + + self.flavor_creator = OpenStackFlavor( + self.admin_os_creds, + FlavorConfig( + name=self.guid + '-flavor-name', ram=256, disk=10, vcpus=1)) + self.flavor_creator.create() + + env_values = { + 'public_network': self.ext_net_name, + 'agent_image': self.image_creator.image_settings.name, + 'agent_flavor': self.flavor_creator.flavor_settings.name, + } + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'agent-group.yaml') + heat_resource_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'agent.yaml') + + stack_settings = StackConfig( + name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', + template_path=heat_tmplt_path, + resource_files=[heat_resource_path], + env_values=env_values) + + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings, + [self.image_creator.image_settings]) + + self.vm_inst_creators = list() + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + + if self.image_creator: + try: + self.image_creator.clean() + except: + pass + + if self.flavor_creator: + try: + self.flavor_creator.clean() + except: + pass + + for vm_inst_creator in self.vm_inst_creators: + try: + keypair_settings = vm_inst_creator.keypair_settings + if keypair_settings and keypair_settings.private_filepath: + expanded_path = os.path.expanduser( + keypair_settings.private_filepath) + os.chmod(expanded_path, 0o755) + os.remove(expanded_path) + except: + pass + + super(self.__class__, self).__clean__() + + def test_nested(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of two VM instance creators and attempt to connect via + SSH to the first one with a floating IP. + """ + created_stack = self.stack_creator.create() + self.assertIsNotNone(created_stack) + + self.vm_inst_creators = self.stack_creator.get_vm_inst_creators( + heat_keypair_option='private_key') + self.assertIsNotNone(self.vm_inst_creators) + self.assertEqual(1, len(self.vm_inst_creators)) + + for vm_inst_creator in self.vm_inst_creators: + self.assertTrue( + create_instance_tests.validate_ssh_client(vm_inst_creator)) + + +class CreateStackRouterTests(OSIntegrationTestCase): + """ + Tests for the CreateStack class defined in create_stack.py where the + target is a Network, Subnet, and Router + """ + + def setUp(self): + """ + Instantiates the CreateStack object that is responsible for downloading + and creating an OS stack file within OpenStack + """ + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.neutron = neutron_utils.neutron_client(self.os_creds) + self.stack_creator = None + + self.net_name = self.guid + '-net' + self.subnet_name = self.guid + '-subnet' + self.router_name = self.guid + '-router' + + self.env_values = { + 'net_name': self.net_name, + 'subnet_name': self.subnet_name, + 'router_name': self.router_name, + 'external_net_name': self.ext_net_name} + + self.heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'router_heat_template.yaml') + + stack_settings = StackConfig( + name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', + template_path=self.heat_tmplt_path, + env_values=self.env_values) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) + self.created_stack = self.stack_creator.create() + self.assertIsNotNone(self.created_stack) + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_retrieve_router_creator(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of an OpenStackRouter creator/state machine instance + """ + router_creators = self.stack_creator.get_router_creators() + self.assertEqual(1, len(router_creators)) + + creator = router_creators[0] + self.assertEqual(self.router_name, creator.router_settings.name) + + router = creator.get_router() + + ext_net = neutron_utils.get_network( + self.neutron, network_name=self.ext_net_name) + self.assertEqual(ext_net.id, router.external_network_id) + + +class CreateStackVolumeTests(OSIntegrationTestCase): + """ + Tests to ensure that floating IPs can be accessed via an + OpenStackVolume object obtained from the OpenStackHeatStack instance + """ + + def setUp(self): + + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.stack_creator = None + + self.volume_name = self.guid + '-volume' + self.volume_type_name = self.guid + '-volume-type' + + self.env_values = { + 'volume_name': self.volume_name, + 'volume_type_name': self.volume_type_name} + + self.heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'volume_heat_template.yaml') + + stack_settings = StackConfig( + name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', + template_path=self.heat_tmplt_path, + env_values=self.env_values) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) + self.created_stack = self.stack_creator.create() + self.assertIsNotNone(self.created_stack) + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_retrieve_volume_creator(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of an OpenStackVolume creator/state machine instance + """ + volume_creators = self.stack_creator.get_volume_creators() + self.assertEqual(1, len(volume_creators)) + + creator = volume_creators[0] + self.assertEqual(self.volume_name, creator.volume_settings.name) + self.assertEqual(self.volume_name, creator.get_volume().name) + self.assertEqual(self.volume_type_name, + creator.volume_settings.type_name) + self.assertEqual(self.volume_type_name, creator.get_volume().type) + self.assertEqual(1, creator.volume_settings.size) + self.assertEqual(1, creator.get_volume().size) + + def test_retrieve_volume_type_creator(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of an OpenStackVolume creator/state machine instance + """ + volume_type_creators = self.stack_creator.get_volume_type_creators() + self.assertEqual(1, len(volume_type_creators)) + + creator = volume_type_creators[0] + self.assertIsNotNone(creator) + + volume_type = creator.get_volume_type() + self.assertIsNotNone(volume_type) + + self.assertEqual(self.volume_type_name, volume_type.name) + self.assertTrue(volume_type.public) + self.assertIsNone(volume_type.qos_spec) + + # TODO - Add encryption back and find out why it broke in Pike + # encryption = volume_type.encryption + # self.assertIsNotNone(encryption) + # self.assertIsNone(encryption.cipher) + # self.assertEqual('front-end', encryption.control_location) + # self.assertIsNone(encryption.key_size) + # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor', + # encryption.provider) + # self.assertEqual(volume_type.id, encryption.volume_type_id) + + +class CreateStackFlavorTests(OSIntegrationTestCase): + """ + Tests to ensure that floating IPs can be accessed via an + OpenStackFlavor object obtained from the OpenStackHeatStack instance + """ + + def setUp(self): + + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.stack_creator = None + + self.heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'flavor_heat_template.yaml') + + stack_settings = StackConfig( + name=self.guid + '-stack', + template_path=self.heat_tmplt_path) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) + self.created_stack = self.stack_creator.create() + self.assertIsNotNone(self.created_stack) + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_retrieve_flavor_creator(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of an OpenStackVolume creator/state machine instance + """ + flavor_creators = self.stack_creator.get_flavor_creators() + self.assertEqual(1, len(flavor_creators)) + + creator = flavor_creators[0] + self.assertTrue(creator.get_flavor().name.startswith(self.guid)) + self.assertEqual(1024, creator.get_flavor().ram) + self.assertEqual(200, creator.get_flavor().disk) + self.assertEqual(8, creator.get_flavor().vcpus) + self.assertEqual(0, creator.get_flavor().ephemeral) + self.assertIsNone(creator.get_flavor().swap) + self.assertEqual(1.0, creator.get_flavor().rxtx_factor) + self.assertTrue(creator.get_flavor().is_public) + + +class CreateStackKeypairTests(OSIntegrationTestCase): + """ + Tests to ensure that floating IPs can be accessed via an + OpenStackKeypair object obtained from the OpenStackHeatStack instance + """ + + def setUp(self): + + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.nova = nova_utils.nova_client(self.heat_creds) + self.stack_creator = None + + self.keypair_name = self.guid + '-kp' + + self.env_values = { + 'keypair_name': self.keypair_name} + + self.heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'keypair_heat_template.yaml') + + stack_settings = StackConfig( + name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', + template_path=self.heat_tmplt_path, + env_values=self.env_values) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) + self.created_stack = self.stack_creator.create() + self.assertIsNotNone(self.created_stack) + + self.keypair_creators = list() + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + for keypair_creator in self.keypair_creators: + try: + keypair_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_retrieve_keypair_creator(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of an OpenStackKeypair creator/state machine instance + """ + self.kp_creators = self.stack_creator.get_keypair_creators( + 'private_key') + self.assertEqual(1, len(self.kp_creators)) + + self.keypair_creator = self.kp_creators[0] + + self.assertEqual(self.keypair_name, + self.keypair_creator.get_keypair().name) + self.assertIsNotNone( + self.keypair_creator.keypair_settings.private_filepath) + + private_file_contents = file_utils.read_file( + self.keypair_creator.keypair_settings.private_filepath) + self.assertTrue(private_file_contents.startswith( + '-----BEGIN RSA PRIVATE KEY-----')) + + keypair = nova_utils.get_keypair_by_id( + self.nova, self.keypair_creator.get_keypair().id) + self.assertIsNotNone(keypair) + self.assertEqual(self.keypair_creator.get_keypair(), keypair) + + +class CreateStackSecurityGroupTests(OSIntegrationTestCase): + """ + Tests for the OpenStackHeatStack class to ensure it returns an + OpenStackSecurityGroup object + """ + + def setUp(self): + """ + Instantiates the CreateStack object that is responsible for downloading + and creating an OS stack file within OpenStack + """ + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.nova = nova_utils.nova_client(self.heat_creds) + self.stack_creator = None + + self.security_group_name = self.guid + '-sec-grp' + + self.env_values = { + 'security_group_name': self.security_group_name} + + self.heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'security_group_heat_template.yaml') + + stack_settings = StackConfig( + name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', + template_path=self.heat_tmplt_path, + env_values=self.env_values) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) + self.created_stack = self.stack_creator.create() + self.assertIsNotNone(self.created_stack) + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_retrieve_security_group_creator(self): + """ + Tests the creation of an OpenStack stack from Heat template file and + the retrieval of an OpenStackSecurityGroup creator/state machine + instance + """ + sec_grp_creators = self.stack_creator.get_security_group_creators() + self.assertEqual(1, len(sec_grp_creators)) + + creator = sec_grp_creators[0] + sec_grp = creator.get_security_group() + + self.assertEqual(self.security_group_name, sec_grp.name) + self.assertEqual('Test description', sec_grp.description) + self.assertEqual(2, len(sec_grp.rules)) + + has_ssh_rule = False + has_icmp_rule = False + + for rule in sec_grp.rules: + if (rule.security_group_id == sec_grp.id + and rule.direction == 'egress' + and rule.ethertype == 'IPv4' + and rule.port_range_min == 22 + and rule.port_range_max == 22 + and rule.protocol == 'tcp' + and rule.remote_group_id is None + and rule.remote_ip_prefix == '0.0.0.0/0'): + has_ssh_rule = True + if (rule.security_group_id == sec_grp.id + and rule.direction == 'ingress' + and rule.ethertype == 'IPv4' + and rule.port_range_min is None + and rule.port_range_max is None + and rule.protocol == 'icmp' + and rule.remote_group_id is None + and rule.remote_ip_prefix == '0.0.0.0/0'): + has_icmp_rule = True + + self.assertTrue(has_ssh_rule) + self.assertTrue(has_icmp_rule) + + class CreateStackNegativeTests(OSIntegrationTestCase): """ - Negative test cases for the CreateStack class + Negative test cases for the OpenStackHeatStack class with poor + configuration """ def setUp(self): + super(self.__class__, self).__start__() self.heat_creds = self.admin_os_creds @@ -515,10 +1044,10 @@ class CreateStackNegativeTests(OSIntegrationTestCase): """ Expect an StackCreationError when the stack file does not exist """ - stack_settings = StackSettings(name=self.stack_name, - template_path=self.heat_tmplt_path) - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + stack_settings = StackConfig(name=self.stack_name, + template_path=self.heat_tmplt_path) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) with self.assertRaises(HTTPBadRequest): self.stack_creator.create() @@ -526,9 +1055,119 @@ class CreateStackNegativeTests(OSIntegrationTestCase): """ Expect an StackCreationError when the stack file does not exist """ - stack_settings = StackSettings(name=self.stack_name, - template_path='foo') - self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds, - stack_settings) + stack_settings = StackConfig( + name=self.stack_name, template_path='foo') + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) with self.assertRaises(IOError): self.stack_creator.create() + + +class CreateStackFailureTests(OSIntegrationTestCase): + """ + Tests for the OpenStackHeatStack class defined in create_stack.py for + when failures occur. Failures are being triggered by allocating 1 million + CPUs. + """ + + def setUp(self): + + super(self.__class__, self).__start__() + + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.heat_creds = self.admin_os_creds + self.heat_creds.project_name = self.admin_os_creds.project_name + + self.heat_cli = heat_utils.heat_client(self.heat_creds) + self.stack_creator = None + + self.tmp_file = file_utils.save_string_to_file( + ' ', str(uuid.uuid4()) + '-bad-image') + self.image_creator = OpenStackImage( + self.heat_creds, ImageConfig( + name=self.guid + 'image', image_file=self.tmp_file.name, + image_user='foo', img_format='qcow2')) + self.image_creator.create() + + # Create Flavor + self.flavor_creator = OpenStackFlavor( + self.admin_os_creds, + FlavorConfig( + name=self.guid + '-flavor-name', ram=256, disk=10, + vcpus=1000000)) + self.flavor_creator.create() + + self.network_name = self.guid + '-net' + self.subnet_name = self.guid + '-subnet' + self.vm_inst_name = self.guid + '-inst' + + self.env_values = { + 'image_name': self.image_creator.image_settings.name, + 'flavor_name': self.flavor_creator.flavor_settings.name, + 'net_name': self.network_name, + 'subnet_name': self.subnet_name, + 'inst_name': self.vm_inst_name} + + self.heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'test_heat_template.yaml') + + def tearDown(self): + """ + Cleans the stack and downloaded stack file + """ + if self.stack_creator: + try: + self.stack_creator.clean() + except: + pass + + if self.image_creator: + try: + self.image_creator.clean() + except: + pass + + if self.flavor_creator: + try: + self.flavor_creator.clean() + except: + pass + + if self.tmp_file: + try: + os.remove(self.tmp_file.name) + except: + pass + + super(self.__class__, self).__clean__() + + def test_stack_failure(self): + """ + Tests the creation of an OpenStack stack from Heat template file that + should always fail due to too many CPU cores + """ + # Create Stack + # Set the default stack settings, then set any custom parameters sent + # from the app + stack_settings = StackConfig( + name=self.__class__.__name__ + '-' + str(self.guid) + '-stack', + template_path=self.heat_tmplt_path, + env_values=self.env_values) + self.stack_creator = OpenStackHeatStack( + self.heat_creds, stack_settings) + + with self.assertRaises(StackError): + try: + self.stack_creator.create() + except StackError: + resources = heat_utils.get_resources( + self.heat_cli, self.stack_creator.get_stack().id) + + found = False + for resource in resources: + if (resource.status == + snaps.config.stack.STATUS_CREATE_COMPLETE): + found = True + self.assertTrue(found) + raise diff --git a/snaps/openstack/tests/create_user_tests.py b/snaps/openstack/tests/create_user_tests.py index ffae596..d3eb4a6 100644 --- a/snaps/openstack/tests/create_user_tests.py +++ b/snaps/openstack/tests/create_user_tests.py @@ -15,6 +15,7 @@ import unittest import uuid +from snaps.config.user import UserConfig from snaps.openstack.create_user import OpenStackUser, UserSettings from snaps.openstack.tests.os_source_file_test import OSComponentTestCase from snaps.openstack.utils import keystone_utils @@ -102,7 +103,7 @@ class CreateUserSuccessTests(OSComponentTestCase): """ guid = str(uuid.uuid4())[:-19] guid = self.__class__.__name__ + '-' + guid - self.user_settings = UserSettings( + self.user_settings = UserConfig( name=guid + '-name', password=guid + '-password', roles={'admin': self.os_creds.project_name}, diff --git a/snaps/openstack/tests/create_volume_tests.py b/snaps/openstack/tests/create_volume_tests.py new file mode 100644 index 0000000..ca13860 --- /dev/null +++ b/snaps/openstack/tests/create_volume_tests.py @@ -0,0 +1,410 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from cinderclient.exceptions import NotFound, BadRequest + +from snaps.config.volume import VolumeConfig, VolumeConfigError +from snaps.config.volume_type import VolumeTypeConfig +from snaps.openstack.create_image import OpenStackImage +from snaps.openstack.create_volume_type import OpenStackVolumeType +from snaps.openstack.tests import openstack_tests + +try: + from urllib.request import URLError +except ImportError: + from urllib2 import URLError + +import logging +import unittest +import uuid + +from snaps.openstack.create_volume import ( + VolumeSettings, OpenStackVolume) +from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase +from snaps.openstack.utils import cinder_utils + +__author__ = 'spisarski' + +logger = logging.getLogger('create_volume_tests') + + +class VolumeSettingsUnitTests(unittest.TestCase): + """ + Tests the construction of the VolumeSettings class + """ + + def test_no_params(self): + with self.assertRaises(VolumeConfigError): + VolumeSettings() + + def test_empty_config(self): + with self.assertRaises(VolumeConfigError): + VolumeSettings(**dict()) + + def test_name_only(self): + settings = VolumeSettings(name='foo') + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.description) + self.assertEquals(1, settings.size) + self.assertIsNone(settings.image_name) + self.assertIsNone(settings.type_name) + self.assertIsNone(settings.availability_zone) + self.assertFalse(settings.multi_attach) + + def test_config_with_name_only(self): + settings = VolumeSettings(**{'name': 'foo'}) + self.assertEqual('foo', settings.name) + self.assertIsNone(settings.description) + self.assertEquals(1, settings.size) + self.assertIsNone(settings.image_name) + self.assertIsNone(settings.type_name) + self.assertIsNone(settings.availability_zone) + self.assertFalse(settings.multi_attach) + + def test_all_strings(self): + settings = VolumeSettings( + name='foo', description='desc', size='2', image_name='image', + type_name='type', availability_zone='zone1', multi_attach='true') + + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual(2, settings.size) + self.assertEqual('image', settings.image_name) + self.assertEqual('type', settings.type_name) + self.assertEqual('zone1', settings.availability_zone) + self.assertTrue(settings.multi_attach) + + def test_all_correct_type(self): + settings = VolumeSettings( + name='foo', description='desc', size=2, image_name='image', + type_name='bar', availability_zone='zone1', multi_attach=True) + + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual(2, settings.size) + self.assertEqual('image', settings.image_name) + self.assertEqual('bar', settings.type_name) + self.assertEqual('zone1', settings.availability_zone) + self.assertTrue(settings.multi_attach) + + def test_config_all(self): + settings = VolumeSettings( + **{'name': 'foo', 'description': 'desc', 'size': '2', + 'image_name': 'foo', 'type_name': 'bar', + 'availability_zone': 'zone1', 'multi_attach': 'true'}) + + self.assertEqual('foo', settings.name) + self.assertEqual('desc', settings.description) + self.assertEqual(2, settings.size) + self.assertEqual('foo', settings.image_name) + self.assertEqual('bar', settings.type_name) + self.assertEqual('zone1', settings.availability_zone) + self.assertTrue(settings.multi_attach) + + +class CreateSimpleVolumeSuccessTests(OSIntegrationTestCase): + """ + Test for the CreateVolume class defined in create_volume.py + """ + + def setUp(self): + """ + Instantiates the CreateVolume object that is responsible for + downloading and creating an OS volume file within OpenStack + """ + super(self.__class__, self).__start__() + + guid = uuid.uuid4() + self.volume_settings = VolumeConfig( + name=self.__class__.__name__ + '-' + str(guid)) + + self.cinder = cinder_utils.cinder_client(self.os_creds) + self.volume_creator = None + + def tearDown(self): + """ + Cleans the volume and downloaded volume file + """ + if self.volume_creator: + self.volume_creator.clean() + + super(self.__class__, self).__clean__() + + def test_create_volume_simple(self): + """ + Tests the creation of a simple OpenStack volume. + """ + # Create Volume + self.volume_creator = OpenStackVolume( + self.os_creds, self.volume_settings) + created_volume = self.volume_creator.create(block=True) + self.assertIsNotNone(created_volume) + + retrieved_volume = cinder_utils.get_volume( + self.cinder, volume_settings=self.volume_settings) + + self.assertIsNotNone(retrieved_volume) + self.assertEqual(created_volume.id, retrieved_volume.id) + self.assertTrue(created_volume == retrieved_volume) + + def test_create_delete_volume(self): + """ + Tests the creation then deletion of an OpenStack volume to ensure + clean() does not raise an Exception. + """ + # Create Volume + self.volume_creator = OpenStackVolume( + self.os_creds, self.volume_settings) + created_volume = self.volume_creator.create(block=True) + self.assertIsNotNone(created_volume) + + retrieved_volume = cinder_utils.get_volume( + self.cinder, volume_settings=self.volume_settings) + self.assertIsNotNone(retrieved_volume) + self.assertEqual(created_volume, retrieved_volume) + + # Delete Volume manually + self.volume_creator.clean() + + self.assertIsNone(cinder_utils.get_volume( + self.cinder, volume_settings=self.volume_settings)) + + # Must not throw an exception when attempting to cleanup non-existent + # volume + self.volume_creator.clean() + self.assertIsNone(self.volume_creator.get_volume()) + + def test_create_same_volume(self): + """ + Tests the creation of an OpenStack volume when one already exists. + """ + # Create Volume + self.volume_creator = OpenStackVolume( + self.os_creds, self.volume_settings) + volume1 = self.volume_creator.create(block=True) + + retrieved_volume = cinder_utils.get_volume( + self.cinder, volume_settings=self.volume_settings) + self.assertEqual(volume1, retrieved_volume) + + # Should be retrieving the instance data + os_volume_2 = OpenStackVolume( + self.os_creds, self.volume_settings) + volume2 = os_volume_2.create(block=True) + self.assertEqual(volume1, volume2) + + +class CreateSimpleVolumeFailureTests(OSIntegrationTestCase): + """ + Test for the CreateVolume class defined in create_volume.py + """ + + def setUp(self): + """ + Instantiates the CreateVolume object that is responsible for + downloading and creating an OS volume file within OpenStack + """ + super(self.__class__, self).__start__() + + self.guid = uuid.uuid4() + self.cinder = cinder_utils.cinder_client(self.os_creds) + self.volume_creator = None + + def tearDown(self): + """ + Cleans the volume and downloaded volume file + """ + if self.volume_creator: + self.volume_creator.clean() + + super(self.__class__, self).__clean__() + + def test_create_volume_bad_size(self): + """ + Tests the creation of an OpenStack volume with a negative size to + ensure it raises a BadRequest exception. + """ + volume_settings = VolumeConfig( + name=self.__class__.__name__ + '-' + str(self.guid), size=-1) + + # Create Volume + self.volume_creator = OpenStackVolume(self.os_creds, volume_settings) + + with self.assertRaises(BadRequest): + self.volume_creator.create(block=True) + + def test_create_volume_bad_type(self): + """ + Tests the creation of an OpenStack volume with a type that does not + exist to ensure it raises a NotFound exception. + """ + volume_settings = VolumeConfig( + name=self.__class__.__name__ + '-' + str(self.guid), + type_name='foo') + + # Create Volume + self.volume_creator = OpenStackVolume(self.os_creds, volume_settings) + + with self.assertRaises(NotFound): + self.volume_creator.create(block=True) + + def test_create_volume_bad_image(self): + """ + Tests the creation of an OpenStack volume with an image that does not + exist to ensure it raises a BadRequest exception. + """ + volume_settings = VolumeConfig( + name=self.__class__.__name__ + '-' + str(self.guid), + image_name='foo') + + # Create Volume + self.volume_creator = OpenStackVolume(self.os_creds, volume_settings) + + with self.assertRaises(BadRequest): + self.volume_creator.create(block=True) + + def test_create_volume_bad_zone(self): + """ + Tests the creation of an OpenStack volume with an availability zone + that does not exist to ensure it raises a BadRequest exception. + """ + volume_settings = VolumeConfig( + name=self.__class__.__name__ + '-' + str(self.guid), + availability_zone='foo') + + # Create Volume + self.volume_creator = OpenStackVolume(self.os_creds, volume_settings) + + with self.assertRaises(BadRequest): + self.volume_creator.create(block=True) + + +class CreateVolumeWithTypeTests(OSIntegrationTestCase): + """ + Test cases for the CreateVolume when attempting to associate it to a + Volume Type + """ + + def setUp(self): + super(self.__class__, self).__start__() + + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.volume_name = guid + '-vol' + self.volume_type_name = guid + '-vol-type' + + self.volume_type_creator = OpenStackVolumeType( + self.os_creds, VolumeTypeConfig(name=self.volume_type_name)) + self.volume_type_creator.create() + self.volume_creator = None + + def tearDown(self): + if self.volume_creator: + self.volume_creator.clean() + if self.volume_type_creator: + self.volume_type_creator.clean() + + super(self.__class__, self).__clean__() + + def test_bad_volume_type(self): + """ + Expect a NotFound to be raised when the volume type does not exist + """ + self.volume_creator = OpenStackVolume( + self.os_creds, + VolumeConfig(name=self.volume_name, type_name='foo')) + + with self.assertRaises(NotFound): + self.volume_creator.create() + + def test_valid_volume_type(self): + """ + Expect a NotFound to be raised when the volume type does not exist + """ + self.volume_creator = OpenStackVolume( + self.os_creds, + VolumeConfig( + name=self.volume_name, type_name=self.volume_type_name)) + + created_volume = self.volume_creator.create(block=True) + self.assertIsNotNone(created_volume) + self.assertEqual(self.volume_type_name, created_volume.type) + + +class CreateVolumeWithImageTests(OSIntegrationTestCase): + """ + Test cases for the CreateVolume when attempting to associate it to an Image + """ + + def setUp(self): + super(self.__class__, self).__start__() + + self.cinder = cinder_utils.cinder_client(self.os_creds) + + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.volume_name = guid + '-vol' + self.image_name = guid + '-image' + + os_image_settings = openstack_tests.cirros_image_settings( + name=self.image_name, image_metadata=self.image_metadata) + # Create Image + self.image_creator = OpenStackImage(self.os_creds, + os_image_settings) + self.image_creator.create() + self.volume_creator = None + + def tearDown(self): + if self.volume_creator: + try: + self.volume_creator.clean() + except: + pass + if self.image_creator: + try: + self.image_creator.clean() + except: + pass + + super(self.__class__, self).__clean__() + + def test_bad_image_name(self): + """ + Tests OpenStackVolume#create() method to ensure a volume is NOT created + when associating it to an invalid image name + """ + self.volume_creator = OpenStackVolume( + self.os_creds, + VolumeConfig(name=self.volume_name, image_name='foo')) + + with self.assertRaises(BadRequest): + self.volume_creator.create(block=True) + + def test_valid_volume_image(self): + """ + Tests OpenStackVolume#create() method to ensure a volume is NOT created + when associating it to an invalid image name + """ + self.volume_creator = OpenStackVolume( + self.os_creds, + VolumeConfig(name=self.volume_name, image_name=self.image_name)) + + created_volume = self.volume_creator.create(block=True) + self.assertIsNotNone(created_volume) + self.assertEqual( + self.volume_creator.volume_settings.name, created_volume.name) + self.assertTrue(self.volume_creator.volume_active()) + + retrieved_volume = cinder_utils.get_volume_by_id( + self.cinder, created_volume.id) + + self.assertEqual(created_volume, retrieved_volume) diff --git a/snaps/openstack/tests/create_volume_type_tests.py b/snaps/openstack/tests/create_volume_type_tests.py index 93e9351..70c40cc 100644 --- a/snaps/openstack/tests/create_volume_type_tests.py +++ b/snaps/openstack/tests/create_volume_type_tests.py @@ -12,7 +12,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from snaps.openstack.create_qos import QoSSettings, Consumer, OpenStackQoS +from snaps.config.volume_type import ( + VolumeTypeConfig, VolumeTypeEncryptionConfig, VolumeTypeConfigError, + ControlLocation) +from snaps.config.qos import QoSConfig, Consumer +from snaps.openstack.create_qos import OpenStackQoS try: from urllib.request import URLError @@ -25,8 +29,7 @@ import uuid from snaps.openstack import create_volume_type from snaps.openstack.create_volume_type import ( - VolumeTypeSettings, VolumeTypeSettingsError, VolumeTypeEncryptionSettings, - ControlLocation) + VolumeTypeSettings, VolumeTypeEncryptionSettings) from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase from snaps.openstack.utils import cinder_utils @@ -41,11 +44,11 @@ class VolumeTypeSettingsUnitTests(unittest.TestCase): """ def test_no_params(self): - with self.assertRaises(VolumeTypeSettingsError): + with self.assertRaises(VolumeTypeConfigError): VolumeTypeSettings() def test_empty_config(self): - with self.assertRaises(VolumeTypeSettingsError): + with self.assertRaises(VolumeTypeConfigError): VolumeTypeSettings(**dict()) def test_name_only(self): @@ -109,7 +112,7 @@ class VolumeTypeSettingsUnitTests(unittest.TestCase): class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase): """ - Test for the OpenStackVolumeType class defined in create_volume_type.py + Test for the OpenStackVolumeType class defined in py without any QoS Specs or Encryption """ @@ -121,7 +124,7 @@ class CreateSimpleVolumeTypeSuccessTests(OSIntegrationTestCase): super(self.__class__, self).__start__() guid = uuid.uuid4() - self.volume_type_settings = VolumeTypeSettings( + self.volume_type_settings = VolumeTypeConfig( name=self.__class__.__name__ + '-' + str(guid)) self.cinder = cinder_utils.cinder_client(self.os_creds) @@ -220,7 +223,7 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase): self.volume_type_name = guid + '-vol_type' self.volume_type_creator = None - qos_settings = QoSSettings( + qos_settings = QoSConfig( name=guid + '-qos-spec', consumer=Consumer.both) self.qos_creator = OpenStackQoS(self.os_creds, qos_settings) self.qos_creator.create() @@ -240,7 +243,7 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase): """ self.volume_type_creator = create_volume_type.OpenStackVolumeType( self.os_creds, - VolumeTypeSettings( + VolumeTypeConfig( name=self.volume_type_name, qos_spec_name=self.qos_creator.qos_settings.name)) @@ -264,12 +267,12 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase): """ Creates a Volume Type object with encryption """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name='foo', provider_class='bar', control_location=ControlLocation.back_end) self.volume_type_creator = create_volume_type.OpenStackVolumeType( self.os_creds, - VolumeTypeSettings( + VolumeTypeConfig( name=self.volume_type_name, encryption=encryption_settings)) @@ -293,12 +296,12 @@ class CreateVolumeTypeComplexTests(OSIntegrationTestCase): """ Creates a Volume Type object with encryption and an associated QoS Spec """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name='foo', provider_class='bar', control_location=ControlLocation.back_end) self.volume_type_creator = create_volume_type.OpenStackVolumeType( self.os_creds, - VolumeTypeSettings( + VolumeTypeConfig( name=self.volume_type_name, encryption=encryption_settings, qos_spec_name=self.qos_creator.qos_settings.name)) diff --git a/snaps/openstack/tests/heat/agent-group.yaml b/snaps/openstack/tests/heat/agent-group.yaml new file mode 100644 index 0000000..540ea93 --- /dev/null +++ b/snaps/openstack/tests/heat/agent-group.yaml @@ -0,0 +1,115 @@ +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################## +heat_template_version: 2013-05-23 + +parameters: + public_network: + type: string + constraints: + - custom_constraint: neutron.network + agent_flavor: + type: string + agent_image: + type: string + volume_size: + type: number + description: Size of the volume to be created. + default: 1 + constraints: + - range: { min: 1, max: 1024 } + description: must be between 1 and 1024 Gb. + agent_count: + type: number + default: 1 + constraints: + - range: { min: 1, max: 512 } + description: must be between 1 and 512 agents. + availability_zone: + type: string + default: nova + +resources: + slaves: + type: OS::Heat::ResourceGroup + depends_on: [subnet, network_router_interface, + open_security_group, key_pair] + properties: + count: {get_param: agent_count} + resource_def: { + type: "agent.yaml", + properties: { + public_network: {get_param: public_network}, + agent_network: {get_resource: network}, + flavor: {get_param: agent_flavor}, + image: {get_param: agent_image}, + availability_zone: {get_param: availability_zone}, + open_security_group: {get_resource: open_security_group}, + key_name: {get_resource: key_pair}, + volume_size: {get_param: volume_size} + } + } + + network: + type: OS::Neutron::Net + properties: + name: network + + subnet: + type: OS::Neutron::Subnet + properties: + network_id: { get_resource: network } + cidr: 172.16.0.0/16 + gateway_ip: 172.16.0.1 + + network_router: + type: OS::Neutron::Router + properties: + external_gateway_info: + network: { get_param: public_network } + + network_router_interface: + type: OS::Neutron::RouterInterface + properties: + router_id: { get_resource: network_router } + subnet_id: { get_resource: subnet } + + key_pair: + type: OS::Nova::KeyPair + properties: + save_private_key: true + name: agent_keypair + + open_security_group: + type: OS::Neutron::SecurityGroup + properties: + description: An open security group to allow all access to the slaves + rules: + - remote_ip_prefix: 0.0.0.0/0 + protocol: tcp + port_range_min: 22 + port_range_max: 22 + - remote_ip_prefix: 0.0.0.0/0 + protocol: icmp + +outputs: + slave_ips: { + description: "Slave addresses", + value: { get_attr: [ slaves, agent_ip] } + } + private_key: + description: "SSH Private Key" + value: { get_attr: [ key_pair, private_key ]} diff --git a/snaps/openstack/tests/heat/agent.yaml b/snaps/openstack/tests/heat/agent.yaml new file mode 100644 index 0000000..014b14f --- /dev/null +++ b/snaps/openstack/tests/heat/agent.yaml @@ -0,0 +1,110 @@ +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################## +heat_template_version: 2013-05-23 + +parameters: + flavor: + type: string + default: test + image: + type: string + default: 'Ubuntu 16.04' + key_name: + type: string + default: test_key + username: + type: string + default: test_user + open_security_group: + type: string + volume_size: + type: number + description: Size of the volume to be created. + default: 1 + constraints: + - range: { min: 1, max: 1024 } + description: must be between 1 and 1024 Gb. + agent_network: + type: string + constraints: + - custom_constraint: neutron.network + public_network: + type: string + constraints: + - custom_constraint: neutron.network + availability_zone: + type: string + default: nova + +resources: + agent: + type: "OS::Nova::Server" + properties: + name: agent + image: { get_param: image } + flavor: { get_param: flavor } + key_name: { get_param: key_name } + networks: + - port: { get_resource: agent_port } + user_data: { get_resource: agent_config } + user_data_format: RAW + availability_zone: { get_param: availability_zone} + + agent_config: + type: "OS::Heat::CloudConfig" + properties: + cloud_config: + users: + - name: { get_param: username } + groups: users + shell: /bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEbnDiqZ8RjQJJzJPf074J41XlYED+zYBzaUZ5UkkUquXzymyUmoWaFBXJP+XPu4Ns44U/S8614+JxGk96tjUdJlIjL0Ag8HP6KLtTNCabucKcEASpgJIVWqJvE3E9upZLIEiTGsF8I8S67T2qq1J1uvtxyeZmyjm7NMamjyFXE53dhR2EHqSutyKK1CK74NkRY9wr3qWUIt35kLdKSVSfrr4gOOicDALbIRu77skHIvrjt+wK1VWphBdMg6ytuq5mIE6pjWAU3Gwl4aTxOU0z43ARzCLq8HVf8s/dKjYMj8plNqaIfceMbaEUqpNHv/xbvtGNG7N0aB/a4pkUQL07 + - default + package_update: false + package_upgrade: false + manage_etc_hosts: localhost + + agent_port: + type: "OS::Neutron::Port" + properties: + network_id: { get_param: agent_network } + security_groups: + - { get_param: open_security_group } + + floating_ip: + type: OS::Neutron::FloatingIP + properties: + floating_network_id: { get_param: public_network } + port_id: { get_resource: agent_port } + + agent_volume: + type: OS::Cinder::Volume + properties: + size: { get_param: volume_size } + + agent_volume_att: + type: OS::Cinder::VolumeAttachment + properties: + instance_uuid: { get_resource: agent } + volume_id: { get_resource: agent_volume} + +outputs: + agent_ip: + description: The floating IP address of the agent on the public network + value: { get_attr: [ floating_ip, floating_ip_address ] } diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/configure_host.yml b/snaps/openstack/tests/heat/flavor_heat_template.yaml index 8df03cb..060c85c 100644 --- a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/configure_host.yml +++ b/snaps/openstack/tests/heat/flavor_heat_template.yaml @@ -1,4 +1,5 @@ -# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs") +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") # and others. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,15 +13,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ---- -- name: Configure NIC - hosts: all - become: yes - become_method: sudo - become_user: root +############################################################################## +heat_template_version: 2015-04-30 - tasks: - - name: Setup /etc/sysconfig/network-scripts/ifcfg-eth1 file - action: template owner=root group=root mode=644 src=../templates/ifcfg-interface dest=/etc/sysconfig/network-scripts/ifcfg-{{nic_name}} - - name : Restart Network - command: systemctl restart network
\ No newline at end of file +description: Simple template to deploy a single volume with encryption + +resources: + flavor: + type: OS::Nova::Flavor + properties: + ram: 1024 + vcpus: 8 + disk: 200 diff --git a/snaps/openstack/tests/heat/floating_ip_heat_template.yaml b/snaps/openstack/tests/heat/floating_ip_heat_template.yaml index e09515e..a191acc 100644 --- a/snaps/openstack/tests/heat/floating_ip_heat_template.yaml +++ b/snaps/openstack/tests/heat/floating_ip_heat_template.yaml @@ -61,6 +61,11 @@ parameters: label: Keypair name description: The name of the stack's keypair default: keypair_name + security_group_name: + type: string + label: Security Group name + description: The name of the stack's security group + default: security_group_name inst1_name: type: string label: First VM name @@ -116,6 +121,19 @@ resources: router: { get_resource: management_router } subnet: { get_resource: subnet } + server_security_group: + type: OS::Neutron::SecurityGroup + properties: + description: Add security group rules for server + name: { get_param: security_group_name } + rules: + - remote_ip_prefix: 0.0.0.0/0 + protocol: tcp + port_range_min: 22 + port_range_max: 22 + - remote_ip_prefix: 0.0.0.0/0 + protocol: icmp + floating_ip: type: OS::Neutron::FloatingIP properties: @@ -141,6 +159,7 @@ resources: image: { get_param: image1_name } flavor: { get_resource: flavor1 } key_name: {get_resource: keypair} + security_groups: [{ get_resource: server_security_group }] networks: - network: { get_resource: network } diff --git a/snaps/openstack/tests/heat/keypair_heat_template.yaml b/snaps/openstack/tests/heat/keypair_heat_template.yaml new file mode 100644 index 0000000..ffb8892 --- /dev/null +++ b/snaps/openstack/tests/heat/keypair_heat_template.yaml @@ -0,0 +1,39 @@ +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################## +heat_template_version: 2015-04-30 + +description: > + Test template that simply deploys a keypair with a generated key + +parameters: + keypair_name: + type: string + label: Keypair name + description: The name of the stack's keypair + default: keypair_name + +resources: + keypair: + type: OS::Nova::KeyPair + properties: + name: { get_param: keypair_name } + save_private_key: True + +outputs: + private_key: + description: "SSH Private Key" + value: { get_attr: [ keypair, private_key ]} diff --git a/snaps/openstack/tests/heat/router_heat_template.yaml b/snaps/openstack/tests/heat/router_heat_template.yaml new file mode 100644 index 0000000..ee7f60c --- /dev/null +++ b/snaps/openstack/tests/heat/router_heat_template.yaml @@ -0,0 +1,69 @@ +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################## +heat_template_version: 2015-04-30 + +description: > + Sample template with two VMs instantiated against different images and + flavors on the same network and the first one has a floating IP + +parameters: + net_name: + type: string + label: Test network name + description: The name of the stack's network + default: test_net + subnet_name: + type: string + label: Test subnet name + description: The name of the stack's subnet + default: test_subnet + router_name: + type: string + label: Test router name + description: The name of the stack's router + default: mgmt_router + external_net_name: + type: string + description: Name of the external network which management network will connect to + default: external + +resources: + network: + type: OS::Neutron::Net + properties: + name: { get_param: net_name } + + subnet: + type: OS::Neutron::Subnet + properties: + name: { get_param: subnet_name } + ip_version: 4 + cidr: 10.1.2.0/24 + network: { get_resource: network } + + management_router: + type: OS::Neutron::Router + properties: + name: { get_param: router_name } + external_gateway_info: + network: { get_param: external_net_name } + + management_router_interface: + type: OS::Neutron::RouterInterface + properties: + router: { get_resource: management_router } + subnet: { get_resource: subnet } diff --git a/snaps/openstack/tests/heat/security_group_heat_template.yaml b/snaps/openstack/tests/heat/security_group_heat_template.yaml new file mode 100644 index 0000000..0c4f07b --- /dev/null +++ b/snaps/openstack/tests/heat/security_group_heat_template.yaml @@ -0,0 +1,45 @@ +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################## +heat_template_version: 2015-04-30 + +description: > + Sample template for creating a single SecurityGroup + +parameters: + security_group_name: + type: string + label: Security Group name + description: The name of the stack's security group + default: security_group_name + +resources: + server_security_group: + type: OS::Neutron::SecurityGroup + properties: + description: Test description + name: { get_param: security_group_name } + rules: + - direction: egress + ethertype: IPv4 + port_range_min: 22 + port_range_max: 22 + protocol: tcp + remote_ip_prefix: 0.0.0.0/0 + - direction: ingress + ethertype: IPv4 + protocol: icmp + remote_ip_prefix: 0.0.0.0/0 diff --git a/snaps/openstack/tests/heat/volume_heat_template.yaml b/snaps/openstack/tests/heat/volume_heat_template.yaml new file mode 100644 index 0000000..1200476 --- /dev/null +++ b/snaps/openstack/tests/heat/volume_heat_template.yaml @@ -0,0 +1,52 @@ +############################################################################## +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################## +heat_template_version: 2015-04-30 + +description: Simple template to deploy a single volume with encryption + +parameters: + volume_name: + type: string + label: Volume name + description: The name of the volume + default: test-vol-name + volume_type_name: + type: string + label: Volume Type name + description: The name of the volume type + default: test-vol-type-name + +resources: + volume_type: + type: OS::Cinder::VolumeType + properties: + name: { get_param: volume_type_name } + +# encryption_vol_type: +# type: OS::Cinder::EncryptedVolumeType +# properties: +# provider: nova.volume.encryptors.luks.LuksEncryptor +# control_location: front-end +# volume_type: { get_resource: volume_type } + + volume: + type: OS::Cinder::Volume + properties: + name: { get_param: volume_name } + size: 1 +# volume_type: { get_resource: encryption_vol_type } + volume_type: { get_resource: volume_type } diff --git a/snaps/openstack/tests/openstack_tests.py b/snaps/openstack/tests/openstack_tests.py index 16fb0b5..a3dec11 100644 --- a/snaps/openstack/tests/openstack_tests.py +++ b/snaps/openstack/tests/openstack_tests.py @@ -17,9 +17,9 @@ import re import pkg_resources from snaps import file_utils -from snaps.openstack.create_image import ImageSettings -from snaps.openstack.create_network import NetworkSettings, SubnetSettings -from snaps.openstack.create_router import RouterSettings +from snaps.config.image import ImageConfig +from snaps.config.network import NetworkConfig, SubnetConfig +from snaps.config.router import RouterConfig from snaps.openstack.os_credentials import OSCreds, ProxySettings __author__ = 'spisarski' @@ -81,7 +81,7 @@ def get_credentials(os_env_file=None, proxy_settings_str=None, elif config.get('OS_INSECURE'): https_cacert = False - interface = 'admin' + interface = 'public' if config.get('OS_INTERFACE'): interface = config.get('OS_INTERFACE') @@ -171,32 +171,29 @@ def create_image_settings(image_name, image_user, image_format, metadata, logger.debug('Image metadata - ' + str(metadata)) if metadata and 'config' in metadata: - return ImageSettings(**metadata['config']) + return ImageConfig(**metadata['config']) disk_file = None - if metadata: + if metadata and ('disk_url' in metadata or 'disk_file' in metadata): disk_url = metadata.get('disk_url') disk_file = metadata.get('disk_file') elif not disk_url: disk_url = default_url - else: - disk_url = disk_url - if metadata and \ - ('kernel_file' in metadata or 'kernel_url' in metadata) and \ - kernel_settings is None: - kernel_image_settings = ImageSettings( + if (metadata + and ('kernel_file' in metadata or 'kernel_url' in metadata) + and kernel_settings is None): + kernel_image_settings = ImageConfig( name=image_name + '-kernel', image_user=image_user, - img_format=image_format, - image_file=metadata.get('kernel_file'), + img_format=image_format, image_file=metadata.get('kernel_file'), url=metadata.get('kernel_url'), public=public) else: kernel_image_settings = kernel_settings - if metadata and \ - ('ramdisk_file' in metadata or 'ramdisk_url' in metadata) and \ - ramdisk_settings is None: - ramdisk_image_settings = ImageSettings( + if (metadata + and ('ramdisk_file' in metadata or 'ramdisk_url' in metadata) + and ramdisk_settings is None): + ramdisk_image_settings = ImageConfig( name=image_name + '-ramdisk', image_user=image_user, img_format=image_format, image_file=metadata.get('ramdisk_file'), @@ -208,13 +205,13 @@ def create_image_settings(image_name, image_user, image_format, metadata, if metadata and 'extra_properties' in metadata: extra_properties = metadata['extra_properties'] - return ImageSettings(name=image_name, image_user=image_user, - img_format=image_format, image_file=disk_file, - url=disk_url, extra_properties=extra_properties, - kernel_image_settings=kernel_image_settings, - ramdisk_image_settings=ramdisk_image_settings, - public=public, - nic_config_pb_loc=nic_config_pb_loc) + return ImageConfig(name=image_name, image_user=image_user, + img_format=image_format, image_file=disk_file, + url=disk_url, extra_properties=extra_properties, + kernel_image_settings=kernel_image_settings, + ramdisk_image_settings=ramdisk_image_settings, + public=public, + nic_config_pb_loc=nic_config_pb_loc) def cirros_image_settings(name=None, url=None, image_metadata=None, @@ -248,8 +245,8 @@ def cirros_image_settings(name=None, url=None, image_metadata=None, def file_image_test_settings(name, file_path, image_user=CIRROS_USER): - return ImageSettings(name=name, image_user=image_user, - img_format=DEFAULT_IMAGE_FORMAT, image_file=file_path) + return ImageConfig(name=name, image_user=image_user, + img_format=DEFAULT_IMAGE_FORMAT, image_file=file_path) def centos_image_settings(name, url=None, image_metadata=None, @@ -274,15 +271,12 @@ def centos_image_settings(name, url=None, image_metadata=None, else: metadata = image_metadata - pb_path = pkg_resources.resource_filename( - 'snaps.provisioning.ansible_pb.centos-network-setup.playbooks', - 'configure_host.yml') return create_image_settings( image_name=name, image_user=CENTOS_USER, image_format=DEFAULT_IMAGE_FORMAT, metadata=metadata, disk_url=url, default_url=CENTOS_DEFAULT_IMAGE_URL, kernel_settings=kernel_settings, ramdisk_settings=ramdisk_settings, - public=public, nic_config_pb_loc=pb_path) + public=public) def ubuntu_image_settings(name, url=None, image_metadata=None, @@ -307,49 +301,65 @@ def ubuntu_image_settings(name, url=None, image_metadata=None, else: metadata = image_metadata - pb_path = pkg_resources.resource_filename( - 'snaps.provisioning.ansible_pb.ubuntu-network-setup.playbooks', - 'configure_host.yml') return create_image_settings( image_name=name, image_user=UBUNTU_USER, image_format=DEFAULT_IMAGE_FORMAT, metadata=metadata, disk_url=url, default_url=UBUNTU_DEFAULT_IMAGE_URL, kernel_settings=kernel_settings, ramdisk_settings=ramdisk_settings, - public=public, nic_config_pb_loc=pb_path) + public=public) def get_priv_net_config(net_name, subnet_name, router_name=None, - cidr='10.55.0.0/24', external_net=None): + cidr='10.55.0.0/24', external_net=None, + netconf_override=None): return OSNetworkConfig(net_name, subnet_name, cidr, router_name, - external_gateway=external_net) + external_gateway=external_net, + netconf_override=netconf_override) def get_pub_net_config(net_name, subnet_name=None, router_name=None, - cidr='10.55.1.0/24', external_net=None): + cidr='10.55.1.0/24', external_net=None, + netconf_override=None): return OSNetworkConfig(net_name, subnet_name, cidr, router_name, - external_gateway=external_net) + external_gateway=external_net, + netconf_override=netconf_override) class OSNetworkConfig: """ Represents the settings required for the creation of a network in OpenStack + where netconf_override is used to reconfigure the network_type, + physical_network and segmentation_id """ def __init__(self, net_name, subnet_name=None, subnet_cidr=None, - router_name=None, external_gateway=None): - + router_name=None, external_gateway=None, + netconf_override=None): + """ + :param netconf_override: dict() containing the reconfigured network_type, + physical_network and segmentation_id + """ + + network_conf = None if subnet_name and subnet_cidr: - self.network_settings = NetworkSettings( + network_conf = NetworkConfig( name=net_name, subnet_settings=[ - SubnetSettings(cidr=subnet_cidr, name=subnet_name)]) + SubnetConfig(cidr=subnet_cidr, name=subnet_name)]) else: - self.network_settings = NetworkSettings(name=net_name) + network_conf = NetworkConfig(name=net_name) + if netconf_override: + network_conf.network_type = netconf_override.get('network_type') + network_conf.physical_network = netconf_override.get( + 'physical_network') + network_conf.segmentation_id = netconf_override.get( + 'segmentation_id') + self.network_settings = network_conf if router_name: if subnet_name: - self.router_settings = RouterSettings( + self.router_settings = RouterConfig( name=router_name, external_gateway=external_gateway, internal_subnets=[subnet_name]) else: - self.router_settings = RouterSettings( + self.router_settings = RouterConfig( name=router_name, external_gateway=external_gateway) diff --git a/snaps/openstack/tests/os_source_file_test.py b/snaps/openstack/tests/os_source_file_test.py index 1617f91..7e910a4 100644 --- a/snaps/openstack/tests/os_source_file_test.py +++ b/snaps/openstack/tests/os_source_file_test.py @@ -14,14 +14,12 @@ # limitations under the License. import logging import pkg_resources -import requests -from requests.packages.urllib3.exceptions import InsecureRequestWarning import uuid import unittest from snaps import file_utils -from snaps.openstack.create_project import ProjectSettings -from snaps.openstack.create_user import UserSettings +from snaps.config.project import ProjectConfig +from snaps.config.user import UserConfig from snaps.openstack.tests import openstack_tests from snaps.openstack.utils import deploy_utils, keystone_utils @@ -29,8 +27,6 @@ from snaps.openstack.utils import deploy_utils, keystone_utils dev_os_env_file = pkg_resources.resource_filename( 'snaps.openstack.tests.conf', 'os_env.yaml') -requests.packages.urllib3.disable_warnings(InsecureRequestWarning) - class OSComponentTestCase(unittest.TestCase): @@ -84,7 +80,7 @@ class OSIntegrationTestCase(OSComponentTestCase): def __init__(self, method_name='runTest', os_creds=None, ext_net_name=None, use_keystone=True, flavor_metadata=None, image_metadata=None, - log_level=logging.DEBUG): + netconf_override=None, log_level=logging.DEBUG): """ Super for integration tests requiring a connection to OpenStack :param method_name: default 'runTest' @@ -102,12 +98,15 @@ class OSIntegrationTestCase(OSComponentTestCase): 'ramdisk_url': '{URI}/cirros-0.3.4-x86_64-initramfs'}) :param flavor_metadata: dict() to be sent directly into the Nova client generally used for page sizes + :param netconf_override: dict() containing the configured network_type, + physical_network and segmentation_id :param log_level: the logging level of your test run (default DEBUG) """ super(OSIntegrationTestCase, self).__init__( method_name=method_name, os_creds=os_creds, ext_net_name=ext_net_name, image_metadata=image_metadata, log_level=log_level) + self.netconf_override = netconf_override self.use_keystone = use_keystone self.keystone = None self.flavor_metadata = flavor_metadata @@ -115,7 +114,8 @@ class OSIntegrationTestCase(OSComponentTestCase): @staticmethod def parameterize(testcase_klass, os_creds, ext_net_name, use_keystone=False, flavor_metadata=None, - image_metadata=None, log_level=logging.DEBUG): + image_metadata=None, netconf_override=None, + log_level=logging.DEBUG): """ Create a suite containing all tests taken from the given subclass, passing them the parameter 'param'. @@ -126,7 +126,8 @@ class OSIntegrationTestCase(OSComponentTestCase): for name in test_names: suite.addTest(testcase_klass(name, os_creds, ext_net_name, use_keystone, flavor_metadata, - image_metadata, log_level)) + image_metadata, netconf_override, + log_level)) return suite """ @@ -149,12 +150,12 @@ class OSIntegrationTestCase(OSComponentTestCase): guid = self.__class__.__name__ + '-' + str(uuid.uuid4())[:-19] project_name = guid + '-proj' self.project_creator = deploy_utils.create_project( - self.admin_os_creds, ProjectSettings( + self.admin_os_creds, ProjectConfig( name=project_name, domain=self.admin_os_creds.project_domain_name)) self.user_creator = deploy_utils.create_user( - self.admin_os_creds, UserSettings( + self.admin_os_creds, UserConfig( name=guid + '-user', password=guid, project_name=project_name, roles={ 'admin': self.project_creator.project_settings.name}, diff --git a/snaps/openstack/utils/cinder_utils.py b/snaps/openstack/utils/cinder_utils.py index d13277d..c50a166 100644 --- a/snaps/openstack/utils/cinder_utils.py +++ b/snaps/openstack/utils/cinder_utils.py @@ -17,7 +17,8 @@ import logging from cinderclient.client import Client from cinderclient.exceptions import NotFound -from snaps.domain.volume import QoSSpec, VolumeType, VolumeTypeEncryption +from snaps.domain.volume import ( + QoSSpec, VolumeType, VolumeTypeEncryption, Volume) from snaps.openstack.utils import keystone_utils __author__ = 'spisarski' @@ -42,6 +43,98 @@ def cinder_client(os_creds): region_name=os_creds.region_name) +def get_volume(cinder, volume_name=None, volume_settings=None): + """ + Returns an OpenStack volume object for a given name + :param cinder: the Cinder client + :param volume_name: the volume name to lookup + :param volume_settings: the volume settings used for lookups + :return: the volume object or None + """ + if volume_settings: + volume_name = volume_settings.name + + volumes = cinder.volumes.list() + for volume in volumes: + if volume.name == volume_name: + return Volume( + name=volume.name, volume_id=volume.id, + description=volume.description, size=volume.size, + vol_type=volume.volume_type, + availability_zone=volume.availability_zone, + multi_attach=volume.multiattach, + attachments=volume.attachments) + + +def __get_os_volume_by_id(cinder, volume_id): + """ + Returns an OpenStack volume object for a given name + :param cinder: the Cinder client + :param volume_id: the volume ID to lookup + :return: the SNAPS-OO Domain Volume object or None + """ + return cinder.volumes.get(volume_id) + + +def get_volume_by_id(cinder, volume_id): + """ + Returns an OpenStack volume object for a given name + :param cinder: the Cinder client + :param volume_id: the volume ID to lookup + :return: the SNAPS-OO Domain Volume object or None + """ + volume = __get_os_volume_by_id(cinder, volume_id) + return Volume( + name=volume.name, volume_id=volume.id, description=volume.description, + size=volume.size, vol_type=volume.volume_type, + availability_zone=volume.availability_zone, + multi_attach=volume.multiattach, attachments=volume.attachments) + + +def get_volume_status(cinder, volume): + """ + Returns a new OpenStack Volume object for a given OpenStack volume object + :param cinder: the Cinder client + :param volume: the domain Volume object + :return: the OpenStack Volume object + """ + os_volume = cinder.volumes.get(volume.id) + return os_volume.status + + +def create_volume(cinder, volume_settings): + """ + Creates and returns OpenStack volume object with an external URL + :param cinder: the cinder client + :param volume_settings: the volume settings object + :return: the OpenStack volume object + :raise Exception if using a file and it cannot be found + """ + volume = cinder.volumes.create( + name=volume_settings.name, description=volume_settings.description, + size=volume_settings.size, imageRef=volume_settings.image_name, + volume_type=volume_settings.type_name, + availability_zone=volume_settings.availability_zone, + multiattach=volume_settings.multi_attach) + + return Volume( + name=volume.name, volume_id=volume.id, + description=volume.description, + size=volume.size, vol_type=volume.volume_type, + availability_zone=volume.availability_zone, + multi_attach=volume.multiattach, attachments=volume.attachments) + + +def delete_volume(cinder, volume): + """ + Deletes an volume from OpenStack + :param cinder: the cinder client + :param volume: the volume to delete + """ + logger.info('Deleting volume named - %s', volume.name) + return cinder.volumes.delete(volume.id) + + def get_volume_type(cinder, volume_type_name=None, volume_type_settings=None): """ Returns an OpenStack volume type object for a given name diff --git a/snaps/openstack/utils/deploy_utils.py b/snaps/openstack/utils/deploy_utils.py index c936c1f..8cd6dd3 100644 --- a/snaps/openstack/utils/deploy_utils.py +++ b/snaps/openstack/utils/deploy_utils.py @@ -78,7 +78,7 @@ def create_router(os_creds, router_settings, cleanup=False): """ Creates a network on which the CMTSs can attach :param os_creds: The OpenStack credentials object - :param router_settings: The RouterSettings instance + :param router_settings: The RouterConfig instance :param cleanup: Denotes whether or not this is being called for cleanup :return: A reference to the network creator objects for each network from which network elements such as the subnet, router, interface @@ -103,7 +103,7 @@ def create_keypair(os_creds, keypair_settings, cleanup=False): """ Creates a keypair that can be applied to an instance :param os_creds: The OpenStack credentials object - :param keypair_settings: The KeypairSettings object + :param keypair_settings: The KeypairConfig object :param cleanup: Denotes whether or not this is being called for cleanup :return: A reference to the keypair creator object """ @@ -121,7 +121,7 @@ def create_vm_instance(os_creds, instance_settings, image_settings, """ Creates a VM instance :param os_creds: The OpenStack credentials - :param instance_settings: Instance of VmInstanceSettings + :param instance_settings: Instance of VmInstanceConfig :param image_settings: The object containing image settings :param keypair_creator: The object responsible for creating the keypair associated with this VM instance. (optional) diff --git a/snaps/openstack/utils/heat_utils.py b/snaps/openstack/utils/heat_utils.py index 8b9395b..e440717 100644 --- a/snaps/openstack/utils/heat_utils.py +++ b/snaps/openstack/utils/heat_utils.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +import os import yaml from heatclient.client import Client @@ -23,7 +24,8 @@ from oslo_serialization import jsonutils from snaps import file_utils from snaps.domain.stack import Stack, Resource, Output -from snaps.openstack.utils import keystone_utils, neutron_utils, nova_utils +from snaps.openstack.utils import ( + keystone_utils, neutron_utils, nova_utils, cinder_utils) __author__ = 'spisarski' @@ -36,7 +38,7 @@ def heat_client(os_creds): :param os_creds: the OpenStack credentials :return: the client """ - logger.debug('Retrieving Nova Client') + logger.debug('Retrieving Heat Client') return Client(os_creds.heat_api_version, session=keystone_utils.keystone_session(os_creds), region_name=os_creds.region_name) @@ -116,6 +118,16 @@ def create_stack(heat_cli, stack_settings): if stack_settings.env_values: args['parameters'] = stack_settings.env_values + if stack_settings.resource_files: + resources = dict() + for res_file in stack_settings.resource_files: + heat_resource_contents = file_utils.read_file(res_file) + base_filename = os.path.basename(res_file) + + if heat_resource_contents and base_filename: + resources[base_filename] = heat_resource_contents + args['files'] = resources + stack = heat_cli.stacks.create(**args) return get_stack_by_id(heat_cli, stack_id=stack['stack']['id']) @@ -130,30 +142,37 @@ def delete_stack(heat_cli, stack): heat_cli.stacks.delete(stack.id) -def __get_os_resources(heat_cli, stack): +def __get_os_resources(heat_cli, res_id): """ Returns all of the OpenStack resource objects for a given stack :param heat_cli: the OpenStack heat client - :param stack: the SNAPS-OO Stack domain object + :param res_id: the resource ID :return: a list """ - return heat_cli.resources.list(stack.id) + return heat_cli.resources.list(res_id) -def get_resources(heat_cli, stack): +def get_resources(heat_cli, res_id, res_type=None): """ Returns all of the OpenStack resource objects for a given stack :param heat_cli: the OpenStack heat client - :param stack: the SNAPS-OO Stack domain object - :return: a list + :param res_id: the SNAPS-OO Stack domain object + :param res_type: the type name to filter + :return: a list of Resource domain objects """ - os_resources = __get_os_resources(heat_cli, stack) + os_resources = __get_os_resources(heat_cli, res_id) if os_resources: out = list() for os_resource in os_resources: - out.append(Resource(resource_type=os_resource.resource_type, - resource_id=os_resource.physical_resource_id)) + if ((res_type and os_resource.resource_type == res_type) + or not res_type): + out.append(Resource( + name=os_resource.resource_name, + resource_type=os_resource.resource_type, + resource_id=os_resource.physical_resource_id, + status=os_resource.resource_status, + status_reason=os_resource.resource_status_reason)) return out @@ -163,7 +182,7 @@ def get_outputs(heat_cli, stack): for given stack :param heat_cli: the OpenStack heat client :param stack: the SNAPS-OO Stack domain object - :return: a list + :return: a list of Output domain objects """ out = list() @@ -182,46 +201,183 @@ def get_outputs(heat_cli, stack): def get_stack_networks(heat_cli, neutron, stack): """ - Returns an instance of NetworkSettings for each network owned by this stack + Returns a list of Network domain objects deployed by this stack :param heat_cli: the OpenStack heat client object :param neutron: the OpenStack neutron client object :param stack: the SNAPS-OO Stack domain object - :return: a list of NetworkSettings + :return: a list of Network objects """ out = list() - resources = get_resources(heat_cli, stack) + resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Net') for resource in resources: - if resource.type == 'OS::Neutron::Net': - network = neutron_utils.get_network_by_id( - neutron, resource.id) - if network: - out.append(network) + network = neutron_utils.get_network_by_id(neutron, resource.id) + if network: + out.append(network) return out -def get_stack_servers(heat_cli, nova, stack): +def get_stack_routers(heat_cli, neutron, stack): """ - Returns an instance of NetworkSettings for each network owned by this stack + Returns a list of Network domain objects deployed by this stack :param heat_cli: the OpenStack heat client object - :param nova: the OpenStack nova client object + :param neutron: the OpenStack neutron client object :param stack: the SNAPS-OO Stack domain object - :return: a list of NetworkSettings + :return: a list of Network objects """ out = list() - resources = get_resources(heat_cli, stack) + resources = get_resources(heat_cli, stack.id, 'OS::Neutron::Router') for resource in resources: - if resource.type == 'OS::Nova::Server': - try: + router = neutron_utils.get_router_by_id(neutron, resource.id) + if router: + out.append(router) + + return out + + +def get_stack_security_groups(heat_cli, neutron, stack): + """ + Returns a list of SecurityGroup domain objects deployed by this stack + :param heat_cli: the OpenStack heat client object + :param neutron: the OpenStack neutron client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of SecurityGroup objects + """ + + out = list() + resources = get_resources(heat_cli, stack.id, 'OS::Neutron::SecurityGroup') + for resource in resources: + security_group = neutron_utils.get_security_group_by_id( + neutron, resource.id) + if security_group: + out.append(security_group) + + return out + + +def get_stack_servers(heat_cli, nova, neutron, stack): + """ + Returns a list of VMInst domain objects associated with a Stack + :param heat_cli: the OpenStack heat client object + :param nova: the OpenStack nova client object + :param neutron: the OpenStack neutron client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of VMInst domain objects + """ + + out = list() + srvr_res = get_resources(heat_cli, stack.id, 'OS::Nova::Server') + for resource in srvr_res: + try: + server = nova_utils.get_server_object_by_id( + nova, neutron, resource.id) + if server: + out.append(server) + except NotFound: + logger.warn('VmInst cannot be located with ID %s', resource.id) + + res_grps = get_resources(heat_cli, stack.id, 'OS::Heat::ResourceGroup') + for res_grp in res_grps: + res_ress = get_resources(heat_cli, res_grp.id) + for res_res in res_ress: + res_res_srvrs = get_resources( + heat_cli, res_res.id, 'OS::Nova::Server') + for res_srvr in res_res_srvrs: server = nova_utils.get_server_object_by_id( - nova, resource.id) + nova, neutron, res_srvr.id) if server: out.append(server) - except NotFound: - logger.warn( - 'VmInst cannot be located with ID %s', resource.id) + + return out + + +def get_stack_keypairs(heat_cli, nova, stack): + """ + Returns a list of Keypair domain objects associated with a Stack + :param heat_cli: the OpenStack heat client object + :param nova: the OpenStack nova client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of VMInst domain objects + """ + + out = list() + resources = get_resources(heat_cli, stack.id, 'OS::Nova::KeyPair') + for resource in resources: + try: + keypair = nova_utils.get_keypair_by_id(nova, resource.id) + if keypair: + out.append(keypair) + except NotFound: + logger.warn('Keypair cannot be located with ID %s', resource.id) + + return out + + +def get_stack_volumes(heat_cli, cinder, stack): + """ + Returns an instance of Volume domain objects created by this stack + :param heat_cli: the OpenStack heat client object + :param cinder: the OpenStack cinder client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of Volume domain objects + """ + + out = list() + resources = get_resources(heat_cli, stack.id, 'OS::Cinder::Volume') + for resource in resources: + try: + server = cinder_utils.get_volume_by_id(cinder, resource.id) + if server: + out.append(server) + except NotFound: + logger.warn('Volume cannot be located with ID %s', resource.id) + + return out + + +def get_stack_volume_types(heat_cli, cinder, stack): + """ + Returns an instance of VolumeType domain objects created by this stack + :param heat_cli: the OpenStack heat client object + :param cinder: the OpenStack cinder client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of VolumeType domain objects + """ + + out = list() + resources = get_resources(heat_cli, stack.id, 'OS::Cinder::VolumeType') + for resource in resources: + try: + vol_type = cinder_utils.get_volume_type_by_id(cinder, resource.id) + if vol_type: + out.append(vol_type) + except NotFound: + logger.warn('VolumeType cannot be located with ID %s', resource.id) + + return out + + +def get_stack_flavors(heat_cli, nova, stack): + """ + Returns an instance of Flavor SNAPS domain object for each flavor created + by this stack + :param heat_cli: the OpenStack heat client object + :param nova: the OpenStack cinder client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of Volume domain objects + """ + + out = list() + resources = get_resources(heat_cli, stack.id, 'OS::Nova::Flavor') + for resource in resources: + try: + flavor = nova_utils.get_flavor_by_id(nova, resource.id) + if flavor: + out.append(flavor) + except NotFound: + logger.warn('Flavor cannot be located with ID %s', resource.id) return out diff --git a/snaps/openstack/utils/keystone_utils.py b/snaps/openstack/utils/keystone_utils.py index 46f6fb8..b8769c0 100644 --- a/snaps/openstack/utils/keystone_utils.py +++ b/snaps/openstack/utils/keystone_utils.py @@ -114,7 +114,7 @@ def get_project(keystone=None, os_creds=None, project_settings=None, :param keystone: the Keystone client :param os_creds: the OpenStack credentials used to obtain the Keystone client if the keystone parameter is None - :param project_settings: a ProjectSettings object + :param project_settings: a ProjectConfig object :param project_name: the name to query :return: the SNAPS-OO Project domain object or None """ @@ -367,7 +367,7 @@ def grant_user_role_to_project(keystone, role, user, project): """ os_role = get_role_by_id(keystone, role.id) - logger.info('Granting role %s to project %s', role.name, project) + logger.info('Granting role %s to project %s', role.name, project.name) if keystone.version == V2_VERSION_STR: keystone.roles.add_user_role(user, os_role, tenant=project) else: diff --git a/snaps/openstack/utils/launch_utils.py b/snaps/openstack/utils/launch_utils.py new file mode 100644 index 0000000..e10cf48 --- /dev/null +++ b/snaps/openstack/utils/launch_utils.py @@ -0,0 +1,815 @@ +# +# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 utility makes it easy to create OpenStack objects +import logging +import re +import socket +import struct + +import os +import time +from keystoneauth1.exceptions import Unauthorized + +from snaps.config.flavor import FlavorConfig +from snaps.config.image import ImageConfig +from snaps.config.keypair import KeypairConfig +from snaps.config.network import PortConfig, NetworkConfig +from snaps.config.project import ProjectConfig +from snaps.config.qos import QoSConfig +from snaps.config.router import RouterConfig +from snaps.config.security_group import SecurityGroupConfig +from snaps.config.user import UserConfig +from snaps.config.vm_inst import VmInstanceConfig +from snaps.config.volume import VolumeConfig +from snaps.config.volume_type import VolumeTypeConfig +from snaps.openstack.create_flavor import OpenStackFlavor +from snaps.openstack.create_image import OpenStackImage +from snaps.openstack.create_keypairs import OpenStackKeypair +from snaps.openstack.create_network import OpenStackNetwork +from snaps.openstack.create_project import OpenStackProject +from snaps.openstack.create_qos import OpenStackQoS +from snaps.openstack.create_router import OpenStackRouter +from snaps.openstack.create_security_group import OpenStackSecurityGroup +from snaps.openstack.create_user import OpenStackUser +from snaps.openstack.create_volume import OpenStackVolume +from snaps.openstack.create_volume_type import OpenStackVolumeType +from snaps.openstack.os_credentials import OSCreds, ProxySettings +from snaps.openstack.utils import deploy_utils, neutron_utils +from snaps.provisioning import ansible_utils + +logger = logging.getLogger('lanuch_utils') +DEFAULT_CREDS_KEY = 'admin' + + +def launch_config(config, tmplt_file, deploy, clean, clean_image): + """ + Launches all objects and applies any configured ansible playbooks + :param config: the environment configuration dict object + :param tmplt_file: the path to the SNAPS-OO template file + :param deploy: when True deploy + :param clean: when True clean + :param clean_image: when True clean the image when clean is True + """ + os_config = config.get('openstack') + + creators = list() + vm_dict = dict() + images_dict = dict() + flavors_dict = dict() + networks_dict = dict() + routers_dict = dict() + os_creds_dict = dict() + + if os_config: + os_creds_dict = __get_creds_dict(os_config) + + # Create projects + projects_dict = __create_instances( + os_creds_dict, OpenStackProject, ProjectConfig, + os_config.get('projects'), 'project', clean) + creators.append(projects_dict) + + # Create users + users_dict = __create_instances( + os_creds_dict, OpenStackUser, UserConfig, + os_config.get('users'), 'user', clean) + creators.append(users_dict) + + # Associate new users to projects + if not clean: + for project_creator in projects_dict.values(): + users = project_creator.project_settings.users + for user_name in users: + user_creator = users_dict.get(user_name) + if user_creator: + project_creator.assoc_user( + user_creator.get_user()) + + # Create flavors + flavors_dict = __create_instances( + os_creds_dict, OpenStackFlavor, FlavorConfig, + os_config.get('flavors'), 'flavor', clean, users_dict) + creators.append(flavors_dict) + + # Create QoS specs + qos_dict = __create_instances( + os_creds_dict, OpenStackQoS, QoSConfig, + os_config.get('qos_specs'), 'qos_spec', clean, users_dict) + creators.append(qos_dict) + + # Create volume types + vol_type_dict = __create_instances( + os_creds_dict, OpenStackVolumeType, VolumeTypeConfig, + os_config.get('volume_types'), 'volume_type', clean, + users_dict) + creators.append(vol_type_dict) + + # Create volume types + vol_dict = __create_instances( + os_creds_dict, OpenStackVolume, VolumeConfig, + os_config.get('volumes'), 'volume', clean, users_dict) + creators.append(vol_dict) + + # Create images + images_dict = __create_instances( + os_creds_dict, OpenStackImage, ImageConfig, + os_config.get('images'), 'image', clean, users_dict) + creators.append(images_dict) + + # Create networks + networks_dict = __create_instances( + os_creds_dict, OpenStackNetwork, NetworkConfig, + os_config.get('networks'), 'network', clean, users_dict) + creators.append(networks_dict) + + # Create routers + routers_dict = __create_instances( + os_creds_dict, OpenStackRouter, RouterConfig, + os_config.get('routers'), 'router', clean, users_dict) + creators.append(routers_dict) + + # Create keypairs + keypairs_dict = __create_instances( + os_creds_dict, OpenStackKeypair, KeypairConfig, + os_config.get('keypairs'), 'keypair', clean, users_dict) + creators.append(keypairs_dict) + + # Create security groups + creators.append(__create_instances( + os_creds_dict, OpenStackSecurityGroup, + SecurityGroupConfig, + os_config.get('security_groups'), 'security_group', clean, + users_dict)) + + # Create instance + vm_dict = __create_vm_instances( + os_creds_dict, users_dict, os_config.get('instances'), + images_dict, keypairs_dict, clean) + creators.append(vm_dict) + logger.info( + 'Completed creating/retrieving all configured instances') + + # Must enter either block + if clean: + # Cleanup Environment + __cleanup(creators, clean_image) + elif deploy: + # Provision VMs + ansible_config = config.get('ansible') + if ansible_config and vm_dict: + if not __apply_ansible_playbooks( + ansible_config, os_creds_dict, vm_dict, images_dict, + flavors_dict, networks_dict, routers_dict, tmplt_file): + logger.error("Problem applying ansible playbooks") + + +def __get_creds_dict(os_conn_config): + """ + Returns a dict of OSCreds where the key is the creds name. + For backwards compatibility, credentials not contained in a list (only + one) will be returned with the key of None + :param os_conn_config: the credential configuration + :return: a dict of OSCreds objects + """ + if 'connection' in os_conn_config: + return {DEFAULT_CREDS_KEY: __get_os_credentials(os_conn_config)} + elif 'connections' in os_conn_config: + out = dict() + for os_conn_dict in os_conn_config['connections']: + config = os_conn_dict.get('connection') + if not config: + raise Exception('Invalid connection format') + + name = config.get('name') + if not name: + raise Exception('Connection config requires a name field') + + out[name] = __get_os_credentials(os_conn_dict) + return out + + +def __get_creds(os_creds_dict, os_user_dict, inst_config): + """ + Returns the appropriate credentials + :param os_creds_dict: a dictionary of OSCreds objects where the name is the + key + :param os_user_dict: a dictionary of OpenStackUser objects where the name + is the key + :param inst_config: + :return: an OSCreds instance or None + """ + os_creds = os_creds_dict.get(DEFAULT_CREDS_KEY) + if 'os_user' in inst_config: + os_user_conf = inst_config['os_user'] + if 'name' in os_user_conf: + user_creator = os_user_dict.get(os_user_conf['name']) + if user_creator: + return user_creator.get_os_creds( + project_name=os_user_conf.get('project_name')) + elif 'os_creds_name' in inst_config: + if 'os_creds_name' in inst_config: + os_creds = os_creds_dict[inst_config['os_creds_name']] + return os_creds + + +def __get_os_credentials(os_conn_config): + """ + Returns an object containing all of the information required to access + OpenStack APIs + :param os_conn_config: The configuration holding the credentials + :return: an OSCreds instance + """ + config = os_conn_config.get('connection') + if not config: + raise Exception('Invalid connection configuration') + + proxy_settings = None + http_proxy = config.get('http_proxy') + if http_proxy: + tokens = re.split(':', http_proxy) + ssh_proxy_cmd = config.get('ssh_proxy_cmd') + proxy_settings = ProxySettings(host=tokens[0], port=tokens[1], + ssh_proxy_cmd=ssh_proxy_cmd) + else: + if 'proxy_settings' in config: + host = config['proxy_settings'].get('host') + port = config['proxy_settings'].get('port') + if host and host != 'None' and port and port != 'None': + proxy_settings = ProxySettings(**config['proxy_settings']) + + if proxy_settings: + config['proxy_settings'] = proxy_settings + else: + if config.get('proxy_settings'): + del config['proxy_settings'] + + return OSCreds(**config) + + +def __parse_ports_config(config): + """ + Parses the "ports" configuration + :param config: The dictionary to parse + :return: a list of PortConfig objects + """ + out = list() + for port_config in config: + out.append(PortConfig(**port_config.get('port'))) + return out + + +def __create_instances(os_creds_dict, creator_class, config_class, config, + config_key, cleanup=False, os_users_dict=None): + """ + Returns a dictionary of SNAPS creator objects where the key is the name + :param os_creds_dict: Dictionary of OSCreds objects where the key is the + name + :param config: The list of configurations for the same type + :param config_key: The list of configurations for the same type + :param cleanup: Denotes whether or not this is being called for cleanup + :return: dictionary + """ + out = {} + + if config: + for config_dict in config: + inst_config = config_dict.get(config_key) + if inst_config: + creds = __get_creds(os_creds_dict, os_users_dict, inst_config) + if creds: + creator = creator_class( + creds, + config_class(**inst_config)) + + if creator: + if cleanup: + try: + creator.initialize() + except Unauthorized as e: + logger.warn( + 'Unable to initialize creator [%s] - %s', + creator, e) + else: + creator.create() + + out[inst_config['name']] = creator + + logger.info('Initialized configured %ss', config_key) + + return out + + +def __create_vm_instances(os_creds_dict, os_users_dict, instances_config, + image_dict, keypairs_dict, cleanup=False): + """ + Returns a dictionary of OpenStackVmInstance objects where the key is the + instance name + :param os_creds_dict: Dictionary of OSCreds objects where the key is the + name + :param os_users_dict: Dictionary of OpenStackUser objects where the key is + the username + :param instances_config: The list of VM instance configurations + :param image_dict: A dictionary of images that will probably be used to + instantiate the VM instance + :param keypairs_dict: A dictionary of keypairs that will probably be used + to instantiate the VM instance + :param cleanup: Denotes whether or not this is being called for cleanup + :return: dictionary + """ + vm_dict = {} + + if instances_config: + for instance_config in instances_config: + conf = instance_config.get('instance') + if conf: + if image_dict: + image_creator = image_dict.get(conf.get('imageName')) + if image_creator: + instance_settings = VmInstanceConfig( + **instance_config['instance']) + kp_creator = keypairs_dict.get( + conf.get('keypair_name')) + + try: + vm_dict[conf[ + 'name']] = deploy_utils.create_vm_instance( + __get_creds( + os_creds_dict, os_users_dict, conf), + instance_settings, + image_creator.image_settings, + keypair_creator=kp_creator, + init_only=cleanup) + except Unauthorized as e: + if not cleanup: + logger.warn('Unable to initialize VM - %s', e) + raise + else: + raise Exception('Image creator instance not found.' + ' Cannot instantiate') + else: + if not cleanup: + raise Exception('Image dictionary is None. Cannot ' + 'instantiate') + else: + raise Exception('Instance configuration is None. Cannot ' + 'instantiate') + logger.info('Created configured instances') + + return vm_dict + + +def __apply_ansible_playbooks(ansible_configs, os_creds_dict, vm_dict, + image_dict, flavor_dict, networks_dict, + routers_dict, tmplt_file): + """ + Applies ansible playbooks to running VMs with floating IPs + :param ansible_configs: a list of Ansible configurations + :param os_creds_dict: Dictionary of OSCreds objects where the key is the + name + :param vm_dict: the dictionary of newly instantiated VMs where the name is + the key + :param image_dict: the dictionary of newly instantiated images where the + name is the key + :param flavor_dict: the dictionary of newly instantiated flavors where the + name is the key + :param networks_dict: the dictionary of newly instantiated networks where + the name is the key + :param routers_dict: the dictionary of newly instantiated routers where + the name is the key + :param tmplt_file: the path of the SNAPS-OO template file for setting the + CWD so playbook location is relative to the deployment + file + :return: t/f - true if successful + """ + logger.info("Applying Ansible Playbooks") + if ansible_configs: + # Set CWD so the deployment file's playbook location can leverage + # relative paths + orig_cwd = os.getcwd() + env_dir = os.path.dirname(tmplt_file) + os.chdir(env_dir) + + # Apply playbooks + for ansible_config in ansible_configs: + # Ensure all hosts are accepting SSH session requests + for vm_name in ansible_config['hosts']: + vm_inst = vm_dict.get(vm_name) + if vm_inst: + if not vm_inst.vm_ssh_active(block=True): + logger.warning( + 'Timeout waiting for instance to respond to ' + 'SSH requests') + return False + + os_creds = os_creds_dict.get('admin-creds') + __apply_ansible_playbook( + ansible_config, os_creds, vm_dict, image_dict, flavor_dict, + networks_dict, routers_dict) + + # Return to original directory + os.chdir(orig_cwd) + + return True + + +def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict, + flavor_dict, networks_dict, routers_dict): + """ + Applies an Ansible configuration setting + :param ansible_config: the configuration settings + :param os_creds: the OpenStack admin credentials object + :param vm_dict: the dictionary of newly instantiated VMs where the name is + the key + :param image_dict: the dictionary of newly instantiated images where the + name is the key + :param flavor_dict: the dictionary of newly instantiated flavors where the + name is the key + :param networks_dict: the dictionary of newly instantiated networks where + the name is the key + :param routers_dict: the dictionary of newly instantiated routers where + the name is the key + """ + if ansible_config: + (remote_user, floating_ips, private_key_filepath, + proxy_settings) = __get_connection_info( + ansible_config, vm_dict) + if floating_ips: + for key, vm_creator in vm_dict.items(): + fip = vm_creator.get_floating_ip() + if fip and fip.ip in floating_ips: + if not vm_creator.cloud_init_complete(block=True): + raise Exception( + 'Cannot apply playbooks as cloud-init has not ' + 'completed') + + variables = __get_variables( + ansible_config.get('variables'), os_creds, vm_dict, image_dict, + flavor_dict, networks_dict, routers_dict) + + retval = ansible_utils.apply_playbook( + ansible_config['playbook_location'], floating_ips, remote_user, + private_key_filepath, + variables=variables, + proxy_setting=proxy_settings) + if retval != 0: + # Not a fatal type of event + raise Exception( + 'Error applying playbook found at location - %s', + ansible_config.get('playbook_location')) + elif ansible_config.get('post_processing'): + post_proc_config = ansible_config['post_processing'] + if 'sleep' in post_proc_config: + time.sleep(post_proc_config['sleep']) + if 'reboot' in post_proc_config: + for vm_name in post_proc_config['reboot']: + if vm_name in vm_dict: + logger.info('Rebooting VM - %s', vm_name) + vm_dict[vm_name].reboot() + + return retval + + +def __get_connection_info(ansible_config, vm_dict): + """ + Returns a tuple of data required for connecting to the running VMs + (remote_user, [floating_ips], private_key_filepath, proxy_settings) + :param ansible_config: the configuration settings + :param vm_dict: the dictionary of VMs where the VM name is the key + :return: tuple where the first element is the user and the second is a list + of floating IPs and the third is the + private key file location and the fourth is an instance of the + snaps.ProxySettings class + (note: in order to work, each of the hosts need to have the same sudo_user + and private key file location values) + """ + if ansible_config.get('hosts'): + hosts = ansible_config['hosts'] + if len(hosts) > 0: + floating_ips = list() + remote_user = None + pk_file = None + proxy_settings = None + for host in hosts: + vm = vm_dict.get(host) + if vm: + fip = vm.get_floating_ip() + if fip: + remote_user = vm.get_image_user() + + if fip: + floating_ips.append(fip.ip) + else: + raise Exception( + 'Could not find floating IP for VM - ' + + vm.name) + + pk_file = vm.keypair_settings.private_filepath + proxy_settings = vm.get_os_creds().proxy_settings + else: + logger.error('Could not locate VM with name - ' + host) + + return remote_user, floating_ips, pk_file, proxy_settings + return None + + +def __get_variables(var_config, os_creds, vm_dict, image_dict, flavor_dict, + networks_dict, routers_dict): + """ + Returns a dictionary of substitution variables to be used for Ansible + templates + :param var_config: the variable configuration settings + :param os_creds: the OpenStack admin credentials object + :param vm_dict: the dictionary of newly instantiated VMs where the name is + the key + :param image_dict: the dictionary of newly instantiated images where the + name is the key + :param flavor_dict: the dictionary of newly instantiated flavors where the + name is the key + :param networks_dict: the dictionary of newly instantiated networks where + the name is the key + :param routers_dict: the dictionary of newly instantiated routers where + the name is the key + :return: dictionary or None + """ + if var_config and vm_dict and len(vm_dict) > 0: + variables = dict() + for key, value in var_config.items(): + value = __get_variable_value( + value, os_creds, vm_dict, image_dict, flavor_dict, + networks_dict, routers_dict) + if key and value: + variables[key] = value + logger.info( + "Set Jinga2 variable with key [%s] the value [%s]", + key, value) + else: + raise Exception( + 'Key - [' + str(key) + '] or Value [' + str(value) + + '] must not be None') + return variables + return None + + +def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict, + flavor_dict, networks_dict, routers_dict): + """ + Returns the associated variable value for use by Ansible for substitution + purposes + :param var_config_values: the configuration dictionary + :param os_creds: the OpenStack admin credentials object + :param vm_dict: the dictionary of newly instantiated VMs where the name is + the key + :param image_dict: the dictionary of newly instantiated images where the + name is the key + :param flavor_dict: the dictionary of newly instantiated flavors where the + name is the key + :param networks_dict: the dictionary of newly instantiated networks where + the name is the key + :param routers_dict: the dictionary of newly instantiated routers where + the name is the key + :return: + """ + if var_config_values['type'] == 'string': + return __get_string_variable_value(var_config_values) + if var_config_values['type'] == 'vm-attr': + return __get_vm_attr_variable_value(var_config_values, vm_dict) + if var_config_values['type'] == 'os_creds': + return __get_os_creds_variable_value(var_config_values, os_creds) + if var_config_values['type'] == 'network': + return __get_network_variable_value(var_config_values, networks_dict) + if var_config_values['type'] == 'router': + return __get_router_variable_value(var_config_values, routers_dict, + os_creds) + if var_config_values['type'] == 'port': + return __get_vm_port_variable_value(var_config_values, vm_dict) + if var_config_values['type'] == 'floating_ip': + return __get_vm_fip_variable_value(var_config_values, vm_dict) + if var_config_values['type'] == 'image': + return __get_image_variable_value(var_config_values, image_dict) + if var_config_values['type'] == 'flavor': + return __get_flavor_variable_value(var_config_values, flavor_dict) + return None + + +def __get_string_variable_value(var_config_values): + """ + Returns the associated string value + :param var_config_values: the configuration dictionary + :return: the value contained in the dictionary with the key 'value' + """ + return var_config_values['value'] + + +def __get_vm_attr_variable_value(var_config_values, vm_dict): + """ + Returns the associated value contained on a VM instance + :param var_config_values: the configuration dictionary + :param vm_dict: the dictionary containing all VMs where the key is the VM's + name + :return: the value + """ + vm = vm_dict.get(var_config_values['vm_name']) + if vm: + if var_config_values['value'] == 'floating_ip': + return vm.get_floating_ip().ip + if var_config_values['value'] == 'image_user': + return vm.get_image_user() + + +def __get_os_creds_variable_value(var_config_values, os_creds): + """ + Returns the associated OS credentials value + :param var_config_values: the configuration dictionary + :param os_creds: the admin OpenStack OSCreds object + :return: the value + """ + if os_creds: + if var_config_values['value'] == 'username': + logger.info("Returning OS username") + return os_creds.username + elif var_config_values['value'] == 'password': + logger.info("Returning OS password") + return os_creds.password + elif var_config_values['value'] == 'auth_url': + logger.info("Returning OS auth_url") + return os_creds.auth_url + elif var_config_values['value'] == 'project_name': + logger.info("Returning OS project_name") + return os_creds.project_name + + +def __get_network_variable_value(var_config_values, networks_dict): + """ + Returns the associated network value + :param var_config_values: the configuration dictionary + :param networks_dict: the dictionary containing all networks where the key + is the network name + :return: the value + """ + net_name = var_config_values.get('network_name') + + if net_name and networks_dict.get(net_name): + network_creator = networks_dict[net_name] + + if 'subnet_name' in var_config_values: + subnet_name = var_config_values.get('subnet_name') + if subnet_name: + for subnet in network_creator.get_network().subnets: + if subnet_name == subnet.name: + if 'value' in var_config_values: + if 'gateway_ip' == var_config_values['value']: + return subnet.gateway_ip + if 'ip_range' == var_config_values['value']: + return subnet.start + ' ' + subnet.end + if 'cidr_ip' == var_config_values['value']: + cidr_split = subnet.cidr.split('/') + return cidr_split[0] + if 'netmask' == var_config_values['value']: + cidr_split = subnet.cidr.split('/') + cidr_bits = 32 - int(cidr_split[1]) + netmask = socket.inet_ntoa( + struct.pack( + '!I', (1 << 32) - (1 << cidr_bits))) + return netmask + if 'broadcast_ip' == var_config_values['value']: + end_split = subnet.end.split('.') + broadcast_ip = ( + end_split[0] + '.' + end_split[1] + '.' + + end_split[2] + '.255') + return broadcast_ip + + +def __get_router_variable_value(var_config_values, routers_dict, os_creds): + """ + Returns the associated network value + :param var_config_values: the configuration dictionary + :param routers_dict: the dictionary containing all networks where the key + is the network name + :param os_creds: the admin OpenStack credentials + :return: the value + """ + router_name = var_config_values.get('router_name') + router_creator = routers_dict[router_name] + + if router_creator: + if 'external_fixed_ip' == var_config_values.get('attr'): + neutron = neutron_utils.neutron_client(os_creds) + ext_nets = neutron_utils.get_external_networks(neutron) + + subnet_name = var_config_values.get('subnet_name') + + for ext_net in ext_nets: + for subnet in ext_net.subnets: + if subnet_name == subnet.name: + router = router_creator.get_router() + for fixed_ips in router.external_fixed_ips: + if subnet.id == fixed_ips['subnet_id']: + return fixed_ips['ip_address'] + + +def __get_vm_port_variable_value(var_config_values, vm_dict): + """ + Returns the associated OS credentials value + :param var_config_values: the configuration dictionary + :param vm_dict: the dictionary containing all VMs where the key is the VM's + name + :return: the value + """ + port_name = var_config_values.get('port_name') + vm_name = var_config_values.get('vm_name') + + if port_name and vm_name: + vm = vm_dict.get(vm_name) + if vm: + for vm_port in vm.get_vm_inst().ports: + if vm_port.name == port_name: + port_value_id = var_config_values.get('port_value') + if port_value_id: + if port_value_id == 'mac_address': + return vm_port.mac_address + if port_value_id == 'ip_address': + return vm_port.ips[0]['ip_address'] + + +def __get_vm_fip_variable_value(var_config_values, vm_dict): + """ + Returns the floating IP value if found + :param var_config_values: the configuration dictionary + :param vm_dict: the dictionary containing all VMs where the key is the VM's + name + :return: the floating IP string value or None + """ + fip_name = var_config_values.get('fip_name') + vm_name = var_config_values.get('vm_name') + + if vm_name: + vm = vm_dict.get(vm_name) + if vm: + fip = vm.get_floating_ip(fip_name) + if fip: + return fip.ip + + +def __get_image_variable_value(var_config_values, image_dict): + """ + Returns the associated image value + :param var_config_values: the configuration dictionary + :param image_dict: the dictionary containing all images where the key is + the name + :return: the value + """ + if image_dict: + if var_config_values.get('image_name'): + image_creator = image_dict.get(var_config_values['image_name']) + if image_creator: + if (var_config_values.get('value') + and var_config_values['value'] == 'id'): + return image_creator.get_image().id + if (var_config_values.get('value') + and var_config_values['value'] == 'user'): + return image_creator.image_settings.image_user + + +def __get_flavor_variable_value(var_config_values, flavor_dict): + """ + Returns the associated flavor value + :param var_config_values: the configuration dictionary + :param flavor_dict: the dictionary containing all flavor creators where the + key is the name + :return: the value or None + """ + if flavor_dict: + if var_config_values.get('flavor_name'): + flavor_creator = flavor_dict.get(var_config_values['flavor_name']) + if flavor_creator: + if (var_config_values.get('value') + and var_config_values['value'] == 'id'): + return flavor_creator.get_flavor().id + + +def __cleanup(creators, clean_image=False): + """ + Cleans up environment + :param creators: the list of creators by type + :param clean_image: when true + :return: + """ + for creator_dict in reversed(creators): + for key, creator in creator_dict.items(): + if ((isinstance(creator, OpenStackImage) and clean_image) + or not isinstance(creator, OpenStackImage)): + creator.clean() diff --git a/snaps/openstack/utils/magnum_utils.py b/snaps/openstack/utils/magnum_utils.py new file mode 100644 index 0000000..96ba6d1 --- /dev/null +++ b/snaps/openstack/utils/magnum_utils.py @@ -0,0 +1,126 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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 magnumclient.client import Client + +from snaps.domain.cluster_template import ClusterTemplate +from snaps.openstack.utils import keystone_utils + +__author__ = 'spisarski' + +logger = logging.getLogger('magnum_utils') + + +def magnum_client(os_creds): + """ + Retrieves the Magnum client + :param os_creds: the OpenStack credentialsf + :return: the client + """ + logger.debug('Retrieving Magnum Client') + return Client(str(os_creds.magnum_api_version), + session=keystone_utils.keystone_session(os_creds)) + + +def get_cluster_template(magnum, template_config=None, template_name=None): + """ + Returns the first ClusterTemplate domain object that matches the parameters + :param magnum: the Magnum client + :param template_config: a ClusterTemplateConfig object (optional) + :param template_name: the name of the template to lookup + :return: ClusterTemplate object or None + """ + name = None + if template_config: + name = template_config.name + elif template_name: + name = template_name + + os_templates = magnum.cluster_templates.list() + for os_template in os_templates: + if os_template.name == name: + return __map_os_cluster_template(os_template) + + +def get_cluster_template_by_id(magnum, tmplt_id): + """ + Returns the first ClusterTemplate domain object that matches the parameters + :param magnum: the Magnum client + :param tmplt_id: the template's ID + :return: ClusterTemplate object or None + """ + return __map_os_cluster_template(magnum.cluster_templates.get(tmplt_id)) + + +def create_cluster_template(magnum, cluster_template_config): + """ + Creates a Magnum Cluster Template object in OpenStack + :param magnum: the Magnum client + :param cluster_template_config: a ClusterTemplateConfig object + :return: a SNAPS ClusterTemplate domain object + """ + config_dict = cluster_template_config.magnum_dict() + os_cluster_template = magnum.cluster_templates.create(**config_dict) + logger.info('Creating cluster template named [%s]', + cluster_template_config.name) + return __map_os_cluster_template(os_cluster_template) + + +def delete_cluster_template(magnum, tmplt_id): + """ + Deletes a Cluster Template from OpenStack + :param magnum: the Magnum client + :param tmplt_id: the cluster template ID to delete + """ + logger.info('Deleting cluster template with ID [%s]', tmplt_id) + magnum.cluster_templates.delete(tmplt_id) + + +def __map_os_cluster_template(os_tmplt): + """ + Returns a SNAPS ClusterTemplate object from an OpenStack ClusterTemplate + object + :param os_tmplt: the OpenStack ClusterTemplate object + :return: SNAPS ClusterTemplate object + """ + return ClusterTemplate( + id=os_tmplt.uuid, + name=os_tmplt.name, + image=os_tmplt.image_id, + keypair=os_tmplt.keypair_id, + network_driver=os_tmplt.network_driver, + external_net=os_tmplt.external_network_id, + floating_ip_enabled=os_tmplt.floating_ip_enabled, + docker_volume_size=os_tmplt.docker_volume_size, + server_type=os_tmplt.server_type, + flavor=os_tmplt.flavor_id, + master_flavor=os_tmplt.master_flavor_id, + coe=os_tmplt.coe, + fixed_net=os_tmplt.fixed_network, + fixed_subnet=os_tmplt.fixed_subnet, + registry_enabled=os_tmplt.registry_enabled, + insecure_registry=os_tmplt.insecure_registry, + docker_storage_driver=os_tmplt.docker_storage_driver, + dns_nameserver=os_tmplt.dns_nameserver, + public=os_tmplt.public, + tls_disabled=os_tmplt.tls_disabled, + http_proxy=os_tmplt.http_proxy, + https_proxy=os_tmplt.https_proxy, + no_proxy=os_tmplt.no_proxy, + volume_driver=os_tmplt.volume_driver, + master_lb_enabled=os_tmplt.master_lb_enabled, + labels=os_tmplt.labels + ) diff --git a/snaps/openstack/utils/neutron_utils.py b/snaps/openstack/utils/neutron_utils.py index 806bb53..e94a40e 100644 --- a/snaps/openstack/utils/neutron_utils.py +++ b/snaps/openstack/utils/neutron_utils.py @@ -53,15 +53,33 @@ def create_network(neutron, os_creds, network_settings): :param network_settings: A dictionary containing the network configuration and is responsible for creating the network request JSON body - :return: a SNAPS-OO Network domain object + :return: a SNAPS-OO Network domain object if found else None """ - if neutron and network_settings: - logger.info('Creating network with name ' + network_settings.name) - json_body = network_settings.dict_for_neutron(os_creds) - os_network = neutron.create_network(body=json_body) - return Network(**os_network['network']) - else: - raise NeutronException('Failded to create network') + logger.info('Creating network with name ' + network_settings.name) + json_body = network_settings.dict_for_neutron(os_creds) + os_network = neutron.create_network(body=json_body) + + if os_network: + network = get_network_by_id(neutron, os_network['network']['id']) + + subnets = list() + for subnet_settings in network_settings.subnet_settings: + try: + subnets.append( + create_subnet(neutron, subnet_settings, os_creds, network)) + except: + logger.error( + 'Unexpected error creating subnet [%s] for network [%s]', + subnet_settings.name, network.name) + + for subnet in subnets: + delete_subnet(neutron, subnet) + + delete_network(neutron, network) + + raise + + return get_network_by_id(neutron, network.id) def delete_network(neutron, network): @@ -71,6 +89,14 @@ def delete_network(neutron, network): :param network: a SNAPS-OO Network domain object """ if neutron and network: + if network.subnets: + for subnet in network.subnets: + logger.info('Deleting subnet with name ' + subnet.name) + try: + delete_subnet(neutron, subnet) + except NotFound: + pass + logger.info('Deleting network with name ' + network.name) neutron.delete_network(network.id) @@ -83,7 +109,7 @@ def get_network(neutron, network_settings=None, network_name=None, else the query will use just the name from the network_name parameter. When the project_id is included, that will be added to the query filter. :param neutron: the client - :param network_settings: the NetworkSettings object used to create filter + :param network_settings: the NetworkConfig object used to create filter :param network_name: the name of the network to retrieve :param project_id: the id of the network's project :return: a SNAPS-OO Network domain object @@ -100,12 +126,13 @@ def get_network(neutron, network_settings=None, network_name=None, networks = neutron.list_networks(**net_filter) for network, netInsts in networks.items(): for inst in netInsts: - return Network(**inst) + return __map_network(neutron, inst) -def get_network_by_id(neutron, network_id): +def __get_os_network_by_id(neutron, network_id): """ - Returns the network object (dictionary) with the given ID else None + Returns the OpenStack network object (dictionary) with the given ID else + None :param neutron: the client :param network_id: the id of the network to retrieve :return: a SNAPS-OO Network domain object @@ -113,18 +140,42 @@ def get_network_by_id(neutron, network_id): networks = neutron.list_networks(**{'id': network_id}) for network in networks['networks']: if network['id'] == network_id: - return Network(**network) + return network + + +def get_network_by_id(neutron, network_id): + """ + Returns the SNAPS Network domain object for the given ID else None + :param neutron: the client + :param network_id: the id of the network to retrieve + :return: a SNAPS-OO Network domain object + """ + os_network = __get_os_network_by_id(neutron, network_id) + if os_network: + return __map_network(neutron, os_network) -def create_subnet(neutron, subnet_settings, os_creds, network=None): +def __map_network(neutron, os_network): + """ + Returns the network object (dictionary) with the given ID else None + :param neutron: the client + :param os_network: the OpenStack Network dict + :return: a SNAPS-OO Network domain object + """ + subnets = get_subnets_by_network_id(neutron, os_network['id']) + os_network['subnets'] = subnets + return Network(**os_network) + + +def create_subnet(neutron, subnet_settings, os_creds, network): """ Creates a network subnet for OpenStack :param neutron: the client - :param network: the network object :param subnet_settings: A dictionary containing the subnet configuration and is responsible for creating the subnet request JSON body :param os_creds: the OpenStack credentials + :param network: the network object :return: a SNAPS-OO Subnet domain object """ if neutron and network and subnet_settings: @@ -207,9 +258,19 @@ def get_subnets_by_network(neutron, network): :param network: the SNAPS-OO Network domain object :return: a list of Subnet objects """ + return get_subnets_by_network_id(neutron, network.id) + + +def get_subnets_by_network_id(neutron, network_id): + """ + Returns a list of SNAPS-OO Subnet domain objects + :param neutron: the OpenStack neutron client + :param network_id: the subnet's ID + :return: a list of Subnet objects + """ out = list() - os_subnets = neutron.list_subnets(network_id=network.id) + os_subnets = neutron.list_subnets(network_id=network_id) for os_subnet in os_subnets['subnets']: out.append(Subnet(**os_subnet)) @@ -231,7 +292,7 @@ def create_router(neutron, os_creds, router_settings): json_body = router_settings.dict_for_neutron(neutron, os_creds) logger.info('Creating router with name - ' + router_settings.name) os_router = neutron.create_router(json_body) - return Router(**os_router['router']) + return __map_router(neutron, os_router['router']) else: logger.error("Failed to create router.") raise NeutronException('Failed to create router') @@ -257,7 +318,7 @@ def get_router_by_id(neutron, router_id): """ router = neutron.show_router(router_id) if router: - return Router(**router['router']) + return __map_router(neutron, router['router']) def get_router(neutron, router_settings=None, router_name=None): @@ -266,7 +327,7 @@ def get_router(neutron, router_settings=None, router_name=None): values if not None, else finds the first with the value of the router_name parameter, else None :param neutron: the client - :param router_settings: the RouterSettings object + :param router_settings: the RouterConfig object :param router_name: the name of the network to retrieve :return: a SNAPS-OO Router domain object """ @@ -281,11 +342,41 @@ def get_router(neutron, router_settings=None, router_name=None): return None routers = neutron.list_routers(**router_filter) + for routerInst in routers['routers']: - return Router(**routerInst) + return __map_router(neutron, routerInst) + return None +def __map_router(neutron, os_router): + """ + Takes an OpenStack router instance and maps it to a SNAPS Router domain + object + :param neutron: the neutron client + :param os_router: the OpenStack Router object + :return: + """ + device_ports = neutron.list_ports( + **{'device_id': os_router['id']})['ports'] + port_subnets = list() + + # Order by create date + sorted_ports = sorted( + device_ports, key=lambda dev_port: dev_port['created_at']) + + for port in sorted_ports: + subnets = list() + for fixed_ip in port['fixed_ips']: + subnet = get_subnet_by_id(neutron, fixed_ip['subnet_id']) + if subnet and subnet.network_id == port['network_id']: + subnets.append(subnet) + port_subnets.append((Port(**port), subnets)) + + os_router['port_subnets'] = port_subnets + return Router(**os_router) + + def add_interface_router(neutron, router, subnet=None, port=None): """ Adds an interface router for OpenStack for either a subnet or port. @@ -389,7 +480,7 @@ def get_port(neutron, port_settings=None, port_name=None): """ Returns the first port object (dictionary) found for the given query :param neutron: the client - :param port_settings: the PortSettings object used for generating the query + :param port_settings: the PortConfig object used for generating the query :param port_name: if port_settings is None, this name is the value to place into the query :return: a SNAPS-OO Port domain object @@ -408,7 +499,8 @@ def get_port(neutron, port_settings=None, port_name=None): if port_settings.network_name: network = get_network(neutron, network_name=port_settings.network_name) - port_filter['network_id'] = network.id + if network: + port_filter['network_id'] = network.id elif port_name: port_filter['name'] = port_name @@ -467,7 +559,7 @@ def create_security_group(neutron, keystone, sec_grp_settings): sec_grp_settings.name) os_group = neutron.create_security_group( sec_grp_settings.dict_for_neutron(keystone)) - return SecurityGroup(**os_group['security_group']) + return __map_os_security_group(neutron, os_group['security_group']) def delete_security_group(neutron, sec_grp): @@ -488,7 +580,7 @@ def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None, the security group will be used, else if the query parameters are None then None will be returned :param neutron: the client - :param sec_grp_settings: an instance of SecurityGroupSettings config object + :param sec_grp_settings: an instance of SecurityGroupConfig object :param sec_grp_name: the name of security group object to retrieve :param project_id: the ID of the project/tentant object that owns the secuity group to retrieve @@ -511,7 +603,20 @@ def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None, groups = neutron.list_security_groups(**sec_grp_filter) for group in groups['security_groups']: - return SecurityGroup(**group) + return __map_os_security_group(neutron, group) + + +def __map_os_security_group(neutron, os_sec_grp): + """ + Creates a SecurityGroup SNAPS domain object from an OpenStack Security + Group dict + :param neutron: the neutron client for performing rule lookups + :param os_sec_grp: the OpenStack Security Group dict object + :return: a SecurityGroup object + """ + os_sec_grp['rules'] = get_rules_by_security_group_id( + neutron, os_sec_grp['id']) + return SecurityGroup(**os_sec_grp) def get_security_group_by_id(neutron, sec_grp_id): @@ -526,13 +631,13 @@ def get_security_group_by_id(neutron, sec_grp_id): groups = neutron.list_security_groups(**{'id': sec_grp_id}) for group in groups['security_groups']: if group['id'] == sec_grp_id: - return SecurityGroup(**group) + return __map_os_security_group(neutron, group) return None def create_security_group_rule(neutron, sec_grp_rule_settings): """ - Creates a security group object in OpenStack + Creates a security group rule in OpenStack :param neutron: the client :param sec_grp_rule_settings: the security group rule settings :return: a SNAPS-OO SecurityGroupRule domain object @@ -546,7 +651,7 @@ def create_security_group_rule(neutron, sec_grp_rule_settings): def delete_security_group_rule(neutron, sec_grp_rule): """ - Deletes a security group object from OpenStack + Deletes a security group rule object from OpenStack :param neutron: the client :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete """ @@ -561,20 +666,29 @@ def get_rules_by_security_group(neutron, sec_grp): :param neutron: the client :param sec_grp: a list of SNAPS SecurityGroupRule domain objects """ + return get_rules_by_security_group_id(neutron, sec_grp.id) + + +def get_rules_by_security_group_id(neutron, sec_grp_id): + """ + Retrieves all of the rules for a given security group by it's ID + :param neutron: the client + :param sec_grp_id: the ID of the associated security group + """ logger.info('Retrieving security group rules associate with the ' - 'security group - %s', sec_grp.name) + 'security group with ID - %s', sec_grp_id) out = list() rules = neutron.list_security_group_rules( - **{'security_group_id': sec_grp.id}) + **{'security_group_id': sec_grp_id}) for rule in rules['security_group_rules']: - if rule['security_group_id'] == sec_grp.id: + if rule['security_group_id'] == sec_grp_id: out.append(SecurityGroupRule(**rule)) return out def get_rule_by_id(neutron, sec_grp, rule_id): """ - Deletes a security group object from OpenStack + Returns a SecurityGroupRule object from OpenStack :param neutron: the client :param sec_grp: the SNAPS SecurityGroup domain object :param rule_id: the rule's ID @@ -598,7 +712,7 @@ def get_external_networks(neutron): out = list() for network in neutron.list_networks( **{'router:external': True})['networks']: - out.append(Network(**network)) + out.append(__map_network(neutron, network)) return out @@ -614,7 +728,7 @@ def get_floating_ips(neutron, ports=None): :param ports: a list of tuple 2 where index 0 is the port name and index 1 is the SNAPS-OO Port object :return: a list of tuple 2 (port_id, SNAPS FloatingIp) objects when ports - is not None else a list of Port objects + is not None else a list of FloatingIp objects """ out = list() fips = neutron.list_floatingips() @@ -698,7 +812,7 @@ def delete_floating_ip(neutron, floating_ip): def get_network_quotas(neutron, project_id): """ - Returns a list of all available keypairs + Returns a list of NetworkQuotas objects :param neutron: the neutron client :param project_id: the project's ID of the quotas to lookup :return: an object of type NetworkQuotas or None if not found diff --git a/snaps/openstack/utils/nova_utils.py b/snaps/openstack/utils/nova_utils.py index 1665fd0..e15484c 100644 --- a/snaps/openstack/utils/nova_utils.py +++ b/snaps/openstack/utils/nova_utils.py @@ -15,12 +15,14 @@ import logging +import enum import os +import time from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from novaclient.client import Client -from novaclient.exceptions import NotFound +from novaclient.exceptions import NotFound, ClientException from snaps import file_utils from snaps.domain.flavor import Flavor @@ -51,22 +53,22 @@ def nova_client(os_creds): region_name=os_creds.region_name) -def create_server(nova, neutron, glance, instance_settings, image_settings, - keypair_settings=None): +def create_server(nova, neutron, glance, instance_config, image_config, + keypair_config=None): """ Creates a VM instance :param nova: the nova client (required) :param neutron: the neutron client for retrieving ports (required) :param glance: the glance client (required) - :param instance_settings: the VM instance settings object (required) - :param image_settings: the VM's image settings object (required) - :param keypair_settings: the VM's keypair settings object (optional) + :param instance_config: the VMInstConfig object (required) + :param image_config: the VM's ImageConfig object (required) + :param keypair_config: the VM's KeypairConfig object (optional) :return: a snaps.domain.VmInst object """ ports = list() - for port_setting in instance_settings.port_settings: + for port_setting in instance_config.port_settings: ports.append(neutron_utils.get_port( neutron, port_settings=port_setting)) nics = [] @@ -75,56 +77,57 @@ def create_server(nova, neutron, glance, instance_settings, image_settings, kv['port-id'] = port.id nics.append(kv) - logger.info('Creating VM with name - ' + instance_settings.name) + logger.info('Creating VM with name - ' + instance_config.name) keypair_name = None - if keypair_settings: - keypair_name = keypair_settings.name + if keypair_config: + keypair_name = keypair_config.name - flavor = get_flavor_by_name(nova, instance_settings.flavor) + flavor = get_flavor_by_name(nova, instance_config.flavor) if not flavor: raise NovaException( - 'Flavor not found with name - %s', instance_settings.flavor) + 'Flavor not found with name - %s', instance_config.flavor) - image = glance_utils.get_image(glance, image_settings=image_settings) + image = glance_utils.get_image(glance, image_settings=image_config) if image: userdata = None - if instance_settings.userdata: - if isinstance(instance_settings.userdata, str): - userdata = instance_settings.userdata + '\n' - elif (isinstance(instance_settings.userdata, dict) and - 'script_file' in instance_settings.userdata): + if instance_config.userdata: + if isinstance(instance_config.userdata, str): + userdata = instance_config.userdata + '\n' + elif (isinstance(instance_config.userdata, dict) and + 'script_file' in instance_config.userdata): try: userdata = file_utils.read_file( - instance_settings.userdata['script_file']) + instance_config.userdata['script_file']) except Exception as e: logger.warn('error reading userdata file %s - %s', - instance_settings.userdata, e) - args = {'name': instance_settings.name, + instance_config.userdata, e) + args = {'name': instance_config.name, 'flavor': flavor, 'image': image, 'nics': nics, 'key_name': keypair_name, 'security_groups': - instance_settings.security_group_names, + instance_config.security_group_names, 'userdata': userdata} - if instance_settings.availability_zone: - args['availability_zone'] = instance_settings.availability_zone + if instance_config.availability_zone: + args['availability_zone'] = instance_config.availability_zone server = nova.servers.create(**args) - return __map_os_server_obj_to_vm_inst(server) + return __map_os_server_obj_to_vm_inst(neutron, server) else: raise NovaException( 'Cannot create instance, image cannot be located with name %s', - image_settings.name) + image_config.name) -def get_server(nova, vm_inst_settings=None, server_name=None): +def get_server(nova, neutron, vm_inst_settings=None, server_name=None): """ Returns a VmInst object for the first server instance found. :param nova: the Nova client - :param vm_inst_settings: the VmInstanceSettings object from which to build + :param neutron: the Neutron client + :param vm_inst_settings: the VmInstanceConfig object from which to build the query if not None :param server_name: the server with this name to return if vm_inst_settings is not None @@ -138,12 +141,34 @@ def get_server(nova, vm_inst_settings=None, server_name=None): servers = nova.servers.list(search_opts=search_opts) for server in servers: - return __map_os_server_obj_to_vm_inst(server) + return __map_os_server_obj_to_vm_inst(neutron, server) -def __map_os_server_obj_to_vm_inst(os_server): +def get_server_connection(nova, vm_inst_settings=None, server_name=None): + """ + Returns a VmInst object for the first server instance found. + :param nova: the Nova client + :param vm_inst_settings: the VmInstanceConfig object from which to build + the query if not None + :param server_name: the server with this name to return if vm_inst_settings + is not None + :return: a snaps.domain.VmInst object or None if not found + """ + search_opts = dict() + if vm_inst_settings: + search_opts['name'] = vm_inst_settings.name + elif server_name: + search_opts['name'] = server_name + + servers = nova.servers.list(search_opts=search_opts) + for server in servers: + return server.links[0] + + +def __map_os_server_obj_to_vm_inst(neutron, os_server): """ Returns a VmInst object for an OpenStack Server object + :param neutron: the Neutron client (when None, ports will be empty) :param os_server: the OpenStack server object :return: an equivalent SNAPS-OO VmInst domain object """ @@ -154,11 +179,23 @@ def __map_os_server_obj_to_vm_inst(os_server): if sec_group.get('name'): sec_grp_names.append(sec_group.get('name')) + out_ports = list() + if len(os_server.networks) > 0: + for net_name, ips in os_server.networks.items(): + network = neutron_utils.get_network(neutron, network_name=net_name) + ports = neutron_utils.get_ports(neutron, network, ips) + for port in ports: + out_ports.append(port) + + volumes = None + if hasattr(os_server, 'os-extended-volumes:volumes_attached'): + volumes = getattr(os_server, 'os-extended-volumes:volumes_attached') + return VmInst( name=os_server.name, inst_id=os_server.id, image_id=os_server.image['id'], flavor_id=os_server.flavor['id'], - networks=os_server.networks, keypair_name=os_server.key_name, - sec_grp_names=sec_grp_names) + ports=out_ports, keypair_name=os_server.key_name, + sec_grp_names=sec_grp_names, volume_ids=volumes) def __get_latest_server_os_object(nova, server): @@ -207,26 +244,28 @@ def get_server_console_output(nova, server): return None -def get_latest_server_object(nova, server): +def get_latest_server_object(nova, neutron, server): """ Returns a server with a given id :param nova: the Nova client + :param neutron: the Neutron client :param server: the old server object :return: the list of servers or None if not found """ server = __get_latest_server_os_object(nova, server) - return __map_os_server_obj_to_vm_inst(server) + return __map_os_server_obj_to_vm_inst(neutron, server) -def get_server_object_by_id(nova, server_id): +def get_server_object_by_id(nova, neutron, server_id): """ Returns a server with a given id :param nova: the Nova client + :param neutron: the Neutron client :param server_id: the server's id :return: an SNAPS-OO VmInst object or None if not found """ server = __get_latest_server_os_object_by_id(nova, server_id) - return __map_os_server_obj_to_vm_inst(server) + return __map_os_server_obj_to_vm_inst(neutron, server) def get_server_security_group_names(nova, server): @@ -256,6 +295,22 @@ def get_server_info(nova, server): return None +def reboot_server(nova, server, reboot_type=None): + """ + Returns a dictionary of a VMs info as returned by OpenStack + :param nova: the Nova client + :param server: the old server object + :param reboot_type: Acceptable values 'SOFT', 'HARD' + (api uses SOFT as the default) + :return: a dict of the info if VM exists else None + """ + vm = __get_latest_server_os_object(nova, server) + if vm: + vm.reboot(reboot_type=reboot_type.value) + else: + raise ServerNotFoundError('Cannot locate server') + + def create_keys(key_size=2048): """ Generates public and private keys @@ -393,6 +448,18 @@ def get_keypair_by_name(nova, name): return None +def get_keypair_by_id(nova, kp_id): + """ + Returns a list of all available keypairs + :param nova: the Nova client + :param kp_id: the ID of the keypair to return + :return: the keypair object + """ + keypair = nova.keypairs.get(kp_id) + return Keypair(name=keypair.name, kp_id=keypair.id, + public_key=keypair.public_key) + + def delete_keypair(nova, key): """ Deletes a keypair object from OpenStack @@ -421,6 +488,21 @@ def get_availability_zone_hosts(nova, zone_name='nova'): return out +def get_hypervisor_hosts(nova): + """ + Returns the host names of all nova nodes with active hypervisors + :param nova: the Nova client + :return: a list of hypervisor host names + """ + out = list() + hypervisors = nova.hypervisors.list() + for hypervisor in hypervisors: + if hypervisor.state == "up": + out.append(hypervisor.hypervisor_hostname) + + return out + + def delete_vm_instance(nova, vm_inst): """ Deletes a VM instance @@ -567,7 +649,18 @@ def add_security_group(nova, vm, security_group_name): :param vm: the OpenStack server object (VM) to alter :param security_group_name: the name of the security group to add """ - nova.servers.add_security_group(str(vm.id), security_group_name) + try: + nova.servers.add_security_group(str(vm.id), security_group_name) + except ClientException as e: + sec_grp_names = get_server_security_group_names(nova, vm) + if security_group_name in sec_grp_names: + logger.warn('Security group [%s] already added to VM [%s]', + security_group_name, vm.name) + return + + logger.error('Unexpected error while adding security group [%s] - %s', + security_group_name, e) + raise def remove_security_group(nova, vm, security_group): @@ -618,7 +711,8 @@ def update_quotas(nova, project_id, compute_quotas): update_values['cores'] = compute_quotas.cores update_values['instances'] = compute_quotas.instances update_values['injected_files'] = compute_quotas.injected_files - update_values['injected_file_content_bytes'] = compute_quotas.injected_file_content_bytes + update_values['injected_file_content_bytes'] = ( + compute_quotas.injected_file_content_bytes) update_values['ram'] = compute_quotas.ram update_values['fixed_ips'] = compute_quotas.fixed_ips update_values['key_pairs'] = compute_quotas.key_pairs @@ -626,7 +720,78 @@ def update_quotas(nova, project_id, compute_quotas): return nova.quotas.update(project_id, **update_values) +def attach_volume(nova, neutron, server, volume, timeout=None): + """ + Attaches a volume to a server + :param nova: the nova client + :param neutron: the neutron client + :param server: the VMInst domain object + :param volume: the Volume domain object + :param timeout: denotes the amount of time to block to determine if the + has been properly attached. When None, do not wait. + :return: the value from the nova call + """ + nova.volumes.create_server_volume(server.id, volume.id) + + if timeout: + start_time = time.time() + while time.time() < start_time + timeout: + vm = get_server_object_by_id(nova, neutron, server.id) + for vol_dict in vm.volume_ids: + if volume.id == vol_dict['id']: + return vm + + return None + else: + return get_server_object_by_id(nova, neutron, server.id) + + +def detach_volume(nova, neutron, server, volume, timeout=None): + """ + Attaches a volume to a server + :param nova: the nova client + :param neutron: the neutron client + :param server: the VMInst domain object + :param volume: the Volume domain object + :param timeout: denotes the amount of time to block to determine if the + has been properly detached. When None, do not wait. + :return: the value from the nova call + """ + nova.volumes.delete_server_volume(server.id, volume.id) + + if timeout: + start_time = time.time() + while time.time() < start_time + timeout: + vm = get_server_object_by_id(nova, neutron, server.id) + found = False + for vol_dict in vm.volume_ids: + if volume.id == vol_dict['id']: + found = True + + if not found: + return vm + + return None + else: + return get_server_object_by_id(nova, neutron, server.id) + + +class RebootType(enum.Enum): + """ + A rule's direction + """ + soft = 'SOFT' + hard = 'HARD' + + class NovaException(Exception): """ Exception when calls to the Keystone client cannot be served properly """ + + +class ServerNotFoundError(Exception): + """ + Exception when operations to a VM/Server is requested and the OpenStack + Server instance cannot be located + """ diff --git a/snaps/openstack/utils/settings_utils.py b/snaps/openstack/utils/settings_utils.py index 7f00075..2cf6047 100644 --- a/snaps/openstack/utils/settings_utils.py +++ b/snaps/openstack/utils/settings_utils.py @@ -15,30 +15,59 @@ import uuid from snaps import file_utils -from snaps.openstack.create_instance import ( - VmInstanceSettings, FloatingIpSettings) -from snaps.openstack.create_keypairs import KeypairSettings -from snaps.openstack.create_network import ( - PortSettings, SubnetSettings, NetworkSettings) +from snaps.config.flavor import FlavorConfig +from snaps.config.keypair import KeypairConfig +from snaps.config.network import SubnetConfig, PortConfig, NetworkConfig +from snaps.config.router import RouterConfig +from snaps.config.security_group import ( + SecurityGroupRuleConfig, SecurityGroupConfig) +from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig +from snaps.config.volume import VolumeConfig +from snaps.config.volume_type import ( + ControlLocation, VolumeTypeEncryptionConfig, VolumeTypeConfig) from snaps.openstack.utils import ( neutron_utils, nova_utils, heat_utils, glance_utils) -def create_network_settings(neutron, network): +def create_network_config(neutron, network): """ - Returns a NetworkSettings object + Returns a NetworkConfig object :param neutron: the neutron client :param network: a SNAPS-OO Network domain object :return: """ - return NetworkSettings( + return NetworkConfig( name=network.name, network_type=network.type, - subnet_settings=create_subnet_settings(neutron, network)) + subnet_settings=create_subnet_config(neutron, network)) -def create_subnet_settings(neutron, network): +def create_security_group_config(neutron, security_group): """ - Returns a list of SubnetSettings objects for a given network + Returns a SecurityGroupConfig object + :param neutron: the neutron client + :param security_group: a SNAPS-OO SecurityGroup domain object + :return: + """ + rules = neutron_utils.get_rules_by_security_group(neutron, security_group) + + rule_settings = list() + for rule in rules: + rule_settings.append(SecurityGroupRuleConfig( + sec_grp_name=security_group.name, description=rule.description, + direction=rule.direction, ethertype=rule.ethertype, + port_range_min=rule.port_range_min, + port_range_max=rule.port_range_max, protocol=rule.protocol, + remote_group_id=rule.remote_group_id, + remote_ip_prefix=rule.remote_ip_prefix)) + + return SecurityGroupConfig( + name=security_group.name, description=security_group.description, + rule_settings=rule_settings) + + +def create_subnet_config(neutron, network): + """ + Returns a list of SubnetConfig objects for a given network :param neutron: the OpenStack neutron client :param network: the SNAPS-OO Network domain object :return: a list @@ -59,13 +88,147 @@ def create_subnet_settings(neutron, network): kwargs['host_routes'] = subnet.host_routes kwargs['ipv6_ra_mode'] = subnet.ipv6_ra_mode kwargs['ipv6_address_mode'] = subnet.ipv6_address_mode - out.append(SubnetSettings(**kwargs)) + out.append(SubnetConfig(**kwargs)) return out -def create_vm_inst_settings(nova, neutron, server): +def create_router_config(neutron, router): + """ + Returns a RouterConfig object + :param neutron: the neutron client + :param router: a SNAPS-OO Router domain object + :return: + """ + ext_net_name = None + + if router.external_network_id: + network = neutron_utils.get_network_by_id( + neutron, router.external_network_id) + if network: + ext_net_name = network.name + + out_ports = list() + if router.port_subnets: + for port, subnets in router.port_subnets: + network = neutron_utils.get_network_by_id( + neutron, port.network_id) + + ip_addrs = list() + if network and router.external_fixed_ips: + for ext_fixed_ips in router.external_fixed_ips: + for subnet in subnets: + if ext_fixed_ips['subnet_id'] == subnet.id: + ip_addrs.append(ext_fixed_ips['ip_address']) + else: + for ip in port.ips: + ip_addrs.append(ip['ip_address']) + + ports = neutron_utils.get_ports(neutron, network, ip_addrs) + for out_port in ports: + out_ports.append(out_port) + + port_settings = __create_port_configs(neutron, out_ports) + + filtered_settings = list() + for port_setting in port_settings: + if port_setting.network_name != ext_net_name: + filtered_settings.append(port_setting) + + return RouterConfig( + name=router.name, external_gateway=ext_net_name, + admin_state_up=router.admin_state_up, + port_settings=filtered_settings) + + +def create_volume_config(volume): + """ + Returns a VolumeConfig object + :param volume: a SNAPS-OO Volume object + """ + + return VolumeConfig( + name=volume.name, description=volume.description, + size=volume.size, type_name=volume.type, + availability_zone=volume.availability_zone, + multi_attach=volume.multi_attach) + + +def create_volume_type_config(volume_type): """ - Returns a NetworkSettings object + Returns a VolumeTypeConfig object + :param volume_type: a SNAPS-OO VolumeType object + """ + + control = None + if volume_type.encryption: + if (volume_type.encryption.control_location + == ControlLocation.front_end.value): + control = ControlLocation.front_end + else: + control = ControlLocation.back_end + + if volume_type and volume_type.encryption: + encrypt_settings = VolumeTypeEncryptionConfig( + name=volume_type.encryption.__class__, + provider_class=volume_type.encryption.provider, + control_location=control, + cipher=volume_type.encryption.cipher, + key_size=volume_type.encryption.key_size) + else: + encrypt_settings = None + + qos_spec_name = None + if volume_type.qos_spec: + qos_spec_name = volume_type.qos_spec.name + + return VolumeTypeConfig( + name=volume_type.name, encryption=encrypt_settings, + qos_spec_name=qos_spec_name, public=volume_type.public) + + +def create_flavor_config(flavor): + """ + Returns a FlavorConfig object + :param flavor: a FlavorConfig object + """ + return FlavorConfig( + name=flavor.name, flavor_id=flavor.id, ram=flavor.ram, + disk=flavor.disk, vcpus=flavor.vcpus, ephemeral=flavor.ephemeral, + swap=flavor.swap, rxtx_factor=flavor.rxtx_factor, + is_public=flavor.is_public) + + +def create_keypair_config(heat_cli, stack, keypair, pk_output_key): + """ + Instantiates a KeypairConfig object from a Keypair domain objects + :param heat_cli: the heat client + :param stack: the Stack domain object + :param keypair: the Keypair SNAPS domain object + :param pk_output_key: the key to the heat template's outputs for retrieval + of the private key file + :return: a KeypairConfig object + """ + if pk_output_key: + outputs = heat_utils.get_outputs(heat_cli, stack) + for output in outputs: + if output.key == pk_output_key: + # Save to file + guid = uuid.uuid4() + key_file = file_utils.save_string_to_file( + output.value, str(guid), 0o400) + + # Use outputs, file and resources for the KeypairConfig + return KeypairConfig( + name=keypair.name, private_filepath=key_file.name) + + return KeypairConfig(name=keypair.name) + + +def create_vm_inst_config(nova, neutron, server): + """ + Returns a VmInstanceConfig object + note: if the server instance is not active, the PortSettings objects will + not be generated resulting in an invalid configuration :param nova: the nova client :param neutron: the neutron client :param server: a SNAPS-OO VmInst domain object @@ -77,29 +240,34 @@ def create_vm_inst_settings(nova, neutron, server): kwargs = dict() kwargs['name'] = server.name kwargs['flavor'] = flavor_name - kwargs['port_settings'] = __create_port_settings( - neutron, server.networks) + + kwargs['port_settings'] = __create_port_configs(neutron, server.ports) kwargs['security_group_names'] = server.sec_grp_names - kwargs['floating_ip_settings'] = __create_floatingip_settings( + kwargs['floating_ip_settings'] = __create_floatingip_config( neutron, kwargs['port_settings']) - return VmInstanceSettings(**kwargs) + return VmInstanceConfig(**kwargs) -def __create_port_settings(neutron, networks): +def __create_port_configs(neutron, ports): """ - Returns a list of port settings based on the networks parameter + Returns a list of PortConfig objects based on the networks parameter :param neutron: the neutron client - :param networks: a dict where the key is the network name and the value - is a list of IP addresses + :param ports: a list of SNAPS-OO Port domain objects :return: """ out = list() - for net_name, ips in networks.items(): - network = neutron_utils.get_network(neutron, network_name=net_name) - ports = neutron_utils.get_ports(neutron, network, ips) - for port in ports: + for port in ports: + if port.device_owner != 'network:dhcp': + ip_addrs = list() + for ip_dict in port.ips: + subnet = neutron_utils.get_subnet_by_id( + neutron, ip_dict['subnet_id']) + ip_addrs.append({'subnet_name': subnet.name, + 'ip': ip_dict['ip_address']}) + + network = neutron_utils.get_network_by_id(neutron, port.network_id) kwargs = dict() if port.name: kwargs['name'] = port.name @@ -107,18 +275,19 @@ def __create_port_settings(neutron, networks): kwargs['mac_address'] = port.mac_address kwargs['allowed_address_pairs'] = port.allowed_address_pairs kwargs['admin_state_up'] = port.admin_state_up - out.append(PortSettings(**kwargs)) + kwargs['ip_addrs'] = ip_addrs + out.append(PortConfig(**kwargs)) return out -def __create_floatingip_settings(neutron, port_settings): +def __create_floatingip_config(neutron, port_settings): """ - Returns a list of FloatingIPSettings objects as they pertain to an + Returns a list of FloatingIpConfig objects as they pertain to an existing deployed server instance :param neutron: the neutron client - :param port_settings: list of SNAPS-OO PortSettings objects - :return: a list of FloatingIPSettings objects or an empty list if no + :param port_settings: list of SNAPS-OO PortConfig objects + :return: a list of FloatingIpConfig objects or an empty list if no floating IPs have been created """ base_fip_name = 'fip-' @@ -160,21 +329,21 @@ def __create_floatingip_settings(neutron, port_settings): if subnet: kwargs['subnet_name'] = subnet.name - out.append(FloatingIpSettings(**kwargs)) + out.append(FloatingIpConfig(**kwargs)) fip_ctr += 1 return out -def determine_image_settings(glance, server, image_settings): +def determine_image_config(glance, server, image_settings): """ - Returns a ImageSettings object from the list that matches the name in one + Returns a ImageConfig object from the list that matches the name in one of the image_settings parameter :param glance: the glance client :param server: a SNAPS-OO VmInst domain object - :param image_settings: list of ImageSettings objects - :return: ImageSettings or None + :param image_settings: list of ImageConfig objects + :return: ImageConfig or None """ if image_settings: for image_setting in image_settings: @@ -183,20 +352,20 @@ def determine_image_settings(glance, server, image_settings): return image_setting -def determine_keypair_settings(heat_cli, stack, server, keypair_settings=None, - priv_key_key=None): +def determine_keypair_config(heat_cli, stack, server, keypair_settings=None, + priv_key_key=None): """ - Returns a KeypairSettings object from the list that matches the + Returns a KeypairConfig object from the list that matches the server.keypair_name value in the keypair_settings parameter if not None, else if the output_key is not None, the output's value when contains the string 'BEGIN RSA PRIVATE KEY', this value will be stored into a file and - encoded into the KeypairSettings object returned + encoded into the KeypairConfig object returned :param heat_cli: the OpenStack heat client :param stack: a SNAPS-OO Stack domain object :param server: a SNAPS-OO VmInst domain object - :param keypair_settings: list of KeypairSettings objects + :param keypair_settings: list of KeypairConfig objects :param priv_key_key: the stack options that holds the private key value - :return: KeypairSettings or None + :return: KeypairConfig or None """ # Existing keypair being used by Heat Template if keypair_settings: @@ -214,6 +383,6 @@ def determine_keypair_settings(heat_cli, stack, server, keypair_settings=None, key_file = file_utils.save_string_to_file( output.value, str(guid), 0o400) - # Use outputs, file and resources for the KeypairSettings - return KeypairSettings( + # Use outputs, file and resources for the KeypairConfig + return KeypairConfig( name=server.keypair_name, private_filepath=key_file.name) diff --git a/snaps/openstack/utils/tests/cinder_utils_tests.py b/snaps/openstack/utils/tests/cinder_utils_tests.py index a45167e..b624b09 100644 --- a/snaps/openstack/utils/tests/cinder_utils_tests.py +++ b/snaps/openstack/utils/tests/cinder_utils_tests.py @@ -15,11 +15,15 @@ import logging import uuid +import time from cinderclient.exceptions import NotFound, BadRequest -from snaps.openstack.create_qos import QoSSettings, Consumer -from snaps.openstack.create_volume_type import ( - VolumeTypeSettings, VolumeTypeEncryptionSettings, ControlLocation) +from snaps.config.volume import VolumeConfig +from snaps.config.volume_type import ( + VolumeTypeConfig, ControlLocation, VolumeTypeEncryptionConfig) +from snaps.config.qos import Consumer, QoSConfig +from snaps.openstack import create_volume +from snaps.openstack.create_qos import Consumer from snaps.openstack.tests import validation_utils from snaps.openstack.tests.os_source_file_test import OSComponentTestCase from snaps.openstack.utils import cinder_utils @@ -57,6 +61,113 @@ class CinderSmokeTests(OSComponentTestCase): cinder.volumes.list() +class CinderUtilsVolumeTests(OSComponentTestCase): + """ + Test for the CreateVolume class defined in create_volume.py + """ + + def setUp(self): + """ + Instantiates the CreateVolume object that is responsible for + downloading and creating an OS volume file within OpenStack + """ + guid = uuid.uuid4() + self.volume_name = self.__class__.__name__ + '-' + str(guid) + self.volume = None + self.cinder = cinder_utils.cinder_client(self.os_creds) + + def tearDown(self): + """ + Cleans the remote OpenStack objects + """ + if self.volume: + try: + cinder_utils.delete_volume(self.cinder, self.volume) + except NotFound: + pass + + self.assertTrue(volume_deleted(self.cinder, self.volume)) + + def test_create_simple_volume(self): + """ + Tests the cinder_utils.create_volume() + """ + volume_settings = VolumeConfig(name=self.volume_name) + self.volume = cinder_utils.create_volume( + self.cinder, volume_settings) + self.assertIsNotNone(self.volume) + self.assertEqual(self.volume_name, self.volume.name) + + self.assertTrue(volume_active(self.cinder, self.volume)) + + volume = cinder_utils.get_volume( + self.cinder, volume_settings=volume_settings) + self.assertIsNotNone(volume) + validation_utils.objects_equivalent(self.volume, volume) + + def test_create_delete_volume(self): + """ + Tests the cinder_utils.create_volume() + """ + volume_settings = VolumeConfig(name=self.volume_name) + self.volume = cinder_utils.create_volume( + self.cinder, volume_settings) + self.assertIsNotNone(self.volume) + self.assertEqual(self.volume_name, self.volume.name) + + self.assertTrue(volume_active(self.cinder, self.volume)) + + volume = cinder_utils.get_volume( + self.cinder, volume_settings=volume_settings) + self.assertIsNotNone(volume) + validation_utils.objects_equivalent(self.volume, volume) + + cinder_utils.delete_volume(self.cinder, self.volume) + self.assertTrue(volume_deleted(self.cinder, self.volume)) + self.assertIsNone( + cinder_utils.get_volume(self.cinder, volume_settings)) + + +def volume_active(cinder, volume): + """ + Returns true if volume becomes active + :param cinder: + :param volume: + :return: + """ + end_time = time.time() + create_volume.VOLUME_ACTIVE_TIMEOUT + while time.time() < end_time: + status = cinder_utils.get_volume_status(cinder, volume) + if status == create_volume.STATUS_ACTIVE: + return True + elif status == create_volume.STATUS_FAILED: + return False + time.sleep(3) + + return False + + +def volume_deleted(cinder, volume): + """ + Returns true if volume becomes active + :param cinder: + :param volume: + :return: + """ + end_time = time.time() + create_volume.VOLUME_ACTIVE_TIMEOUT + while time.time() < end_time: + try: + status = cinder_utils.get_volume_status(cinder, volume) + if status == create_volume.STATUS_DELETED: + return True + except NotFound: + return True + + time.sleep(3) + + return False + + class CinderUtilsQoSTests(OSComponentTestCase): """ Test for the CreateQos class defined in create_qos.py @@ -86,8 +197,8 @@ class CinderUtilsQoSTests(OSComponentTestCase): """ Tests the cinder_utils.create_qos() """ - qos_settings = QoSSettings(name=self.qos_name, specs=self.specs, - consumer=Consumer.both) + qos_settings = QoSConfig( + name=self.qos_name, specs=self.specs, consumer=Consumer.both) self.qos = cinder_utils.create_qos(self.cinder, qos_settings) self.assertIsNotNone(self.qos) @@ -103,8 +214,8 @@ class CinderUtilsQoSTests(OSComponentTestCase): """ Tests the cinder_utils.create_qos() """ - qos_settings = QoSSettings(name=self.qos_name, specs=self.specs, - consumer=Consumer.front_end) + qos_settings = QoSConfig( + name=self.qos_name, specs=self.specs, consumer=Consumer.front_end) self.qos = cinder_utils.create_qos(self.cinder, qos_settings) self.assertIsNotNone(self.qos) @@ -120,8 +231,8 @@ class CinderUtilsQoSTests(OSComponentTestCase): """ Tests the cinder_utils.create_qos() """ - qos_settings = QoSSettings(name=self.qos_name, specs=self.specs, - consumer=Consumer.back_end) + qos_settings = QoSConfig( + name=self.qos_name, specs=self.specs, consumer=Consumer.back_end) self.qos = cinder_utils.create_qos(self.cinder, qos_settings) self.assertIsNotNone(self.qos) @@ -137,7 +248,7 @@ class CinderUtilsQoSTests(OSComponentTestCase): """ Tests the cinder_utils.create_qos() """ - qos_settings = QoSSettings(name=self.qos_name, consumer=Consumer.both) + qos_settings = QoSConfig(name=self.qos_name, consumer=Consumer.both) self.qos = cinder_utils.create_qos(self.cinder, qos_settings) self.assertIsNotNone(self.qos) self.assertEqual(self.qos_name, self.qos.name) @@ -165,7 +276,7 @@ class CinderUtilsSimpleVolumeTypeTests(OSComponentTestCase): """ guid = uuid.uuid4() volume_type_name = self.__class__.__name__ + '-' + str(guid) - self.volume_type_settings = VolumeTypeSettings(name=volume_type_name) + self.volume_type_settings = VolumeTypeConfig(name=volume_type_name) self.volume_type = None self.cinder = cinder_utils.cinder_client(self.os_creds) @@ -239,7 +350,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase): volume_type_name = self.__class__.__name__ + '-' + str(guid) + '-type' self.volume_type = cinder_utils.create_volume_type( - self.cinder, VolumeTypeSettings(name=volume_type_name)) + self.cinder, VolumeTypeConfig(name=volume_type_name)) def tearDown(self): """ @@ -263,7 +374,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase): Tests the cinder_utils.create_volume_encryption(), get_volume_encryption(), and get_volume_encryption_by_id() """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name=self.encryption_name, provider_class='foo', control_location=ControlLocation.front_end) self.encryption = cinder_utils.create_volume_encryption( @@ -281,7 +392,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase): """ Primarily tests the cinder_utils.delete_volume_type_encryption() """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name=self.encryption_name, provider_class='LuksEncryptor', control_location=ControlLocation.back_end) self.encryption = cinder_utils.create_volume_encryption( @@ -307,7 +418,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase): Tests the cinder_utils.create_volume_encryption() with all valid settings """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name=self.encryption_name, provider_class='foo', cipher='bar', control_location=ControlLocation.back_end, key_size=1) @@ -329,7 +440,7 @@ class CinderUtilsAddEncryptionTests(OSComponentTestCase): Tests the cinder_utils.create_volume_encryption() raises an exception when the provider class does not exist """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name=self.encryption_name, provider_class='foo', cipher='bar', control_location=ControlLocation.back_end, key_size=-1) @@ -353,8 +464,8 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase): self.vol_type_name = self.__class__.__name__ + '-' + str(guid) self.specs = {'foo': 'bar'} self.cinder = cinder_utils.cinder_client(self.os_creds) - qos_settings = QoSSettings(name=self.qos_name, specs=self.specs, - consumer=Consumer.both) + qos_settings = QoSConfig( + name=self.qos_name, specs=self.specs, consumer=Consumer.both) self.qos = cinder_utils.create_qos(self.cinder, qos_settings) self.volume_type = None @@ -385,10 +496,10 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase): Tests the cinder_utils.create_volume_type() where encryption has been configured """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name='foo', provider_class='bar', control_location=ControlLocation.back_end) - volume_type_settings = VolumeTypeSettings( + volume_type_settings = VolumeTypeConfig( name=self.vol_type_name, encryption=encryption_settings) self.volume_type = cinder_utils.create_volume_type( self.cinder, volume_type_settings) @@ -404,7 +515,7 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase): """ Tests the cinder_utils.create_volume_type() with an associated QoS Spec """ - volume_type_settings = VolumeTypeSettings( + volume_type_settings = VolumeTypeConfig( name=self.vol_type_name, qos_spec_name=self.qos_name) self.volume_type = cinder_utils.create_volume_type( self.cinder, volume_type_settings) @@ -419,7 +530,7 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase): Tests the cinder_utils.create_volume_type() when the QoS Spec name does not exist """ - volume_type_settings = VolumeTypeSettings( + volume_type_settings = VolumeTypeConfig( name=self.vol_type_name, qos_spec_name='foo') self.volume_type = cinder_utils.create_volume_type( @@ -432,10 +543,10 @@ class CinderUtilsVolumeTypeCompleteTests(OSComponentTestCase): Tests the cinder_utils.create_volume_type() with encryption and an associated QoS Spec """ - encryption_settings = VolumeTypeEncryptionSettings( + encryption_settings = VolumeTypeEncryptionConfig( name='foo', provider_class='bar', control_location=ControlLocation.back_end) - volume_type_settings = VolumeTypeSettings( + volume_type_settings = VolumeTypeConfig( name=self.vol_type_name, qos_spec_name=self.qos_name, encryption=encryption_settings) self.volume_type = cinder_utils.create_volume_type( diff --git a/snaps/openstack/utils/tests/heat_utils_tests.py b/snaps/openstack/utils/tests/heat_utils_tests.py index 44235ff..67fbdec 100644 --- a/snaps/openstack/utils/tests/heat_utils_tests.py +++ b/snaps/openstack/utils/tests/heat_utils_tests.py @@ -13,21 +13,25 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +import os + import pkg_resources import uuid import time -from snaps.openstack import create_stack -from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings +import snaps.config.stack as stack_config +from snaps.config.flavor import FlavorConfig +from snaps.openstack.create_flavor import OpenStackFlavor from snaps.openstack.create_image import OpenStackImage from snaps.openstack.create_instance import OpenStackVmInstance -from snaps.openstack.create_stack import StackSettings +from snaps.openstack.create_stack import StackConfig from snaps.openstack.tests import openstack_tests from snaps.openstack.tests.os_source_file_test import OSComponentTestCase from snaps.openstack.utils import ( - heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils) + heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils, + cinder_utils) __author__ = 'spisarski' @@ -93,7 +97,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): # Create Flavor self.flavor_creator = OpenStackFlavor( self.os_creds, - FlavorSettings(name=guid + '-flavor', ram=256, disk=10, vcpus=1)) + FlavorConfig(name=guid + '-flavor', ram=256, disk=10, vcpus=1)) self.flavor_creator.create() env_values = {'image_name': self.image_creator.image_settings.name, @@ -103,10 +107,10 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): 'inst_name': self.vm_inst_name} heat_tmplt_path = pkg_resources.resource_filename( 'snaps.openstack.tests.heat', 'test_heat_template.yaml') - self.stack_settings1 = StackSettings( + self.stack_settings1 = StackConfig( name=stack_name1, template_path=heat_tmplt_path, env_values=env_values) - self.stack_settings2 = StackSettings( + self.stack_settings2 = StackConfig( name=stack_name2, template_path=heat_tmplt_path, env_values=env_values) self.stack1 = None @@ -115,7 +119,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): def tearDown(self): """ - Cleans the image and downloaded image file + Cleans the stack and image """ if self.stack1: try: @@ -160,7 +164,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): self.stack1.id) self.assertEqual(self.stack1, stack_query_3) - resources = heat_utils.get_resources(self.heat_client, self.stack1) + resources = heat_utils.get_resources(self.heat_client, self.stack1.id) self.assertIsNotNone(resources) self.assertEqual(4, len(resources)) @@ -168,22 +172,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): self.assertIsNotNone(outputs) self.assertEqual(0, len(outputs)) - # Wait until stack deployment has completed - end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT - is_active = False - while time.time() < end_time: - status = heat_utils.get_stack_status(self.heat_client, - self.stack1.id) - if status == create_stack.STATUS_CREATE_COMPLETE: - is_active = True - break - elif status == create_stack.STATUS_CREATE_FAILED: - is_active = False - break - - time.sleep(3) - - self.assertTrue(is_active) + self.assertTrue(stack_active(self.heat_client, self.stack1)) neutron = neutron_utils.neutron_client(self.os_creds) networks = heat_utils.get_stack_networks( @@ -198,7 +187,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): nova = nova_utils.nova_client(self.os_creds) servers = heat_utils.get_stack_servers( - self.heat_client, nova, self.stack1) + self.heat_client, nova, neutron, self.stack1) self.assertIsNotNone(servers) self.assertEqual(1, len(servers)) self.assertEqual(self.vm_inst_name, servers[0].name) @@ -222,21 +211,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): self.stack1.id) self.assertEqual(self.stack1, stack1_query_3) - end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT - is_active = False - while time.time() < end_time: - status = heat_utils.get_stack_status(self.heat_client, - self.stack1.id) - if status == create_stack.STATUS_CREATE_COMPLETE: - is_active = True - break - elif status == create_stack.STATUS_CREATE_FAILED: - is_active = False - break - - time.sleep(3) - - self.assertTrue(is_active) + self.assertTrue(stack_active(self.heat_client, self.stack1)) self.stack2 = heat_utils.create_stack(self.heat_client, self.stack_settings2) @@ -253,22 +228,7 @@ class HeatUtilsCreateSimpleStackTests(OSComponentTestCase): self.stack2.id) self.assertEqual(self.stack2, stack2_query_3) - end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT - - is_active = False - while time.time() < end_time: - status = heat_utils.get_stack_status(self.heat_client, - self.stack2.id) - if status == create_stack.STATUS_CREATE_COMPLETE: - is_active = True - break - elif status == create_stack.STATUS_CREATE_FAILED: - is_active = False - break - - time.sleep(3) - - self.assertTrue(is_active) + self.assertTrue(stack_active(self.heat_client, self.stack2)) class HeatUtilsCreateComplexStackTests(OSComponentTestCase): @@ -312,45 +272,35 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): 'external_net_name': self.ext_net_name} heat_tmplt_path = pkg_resources.resource_filename( 'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml') - stack_settings = StackSettings( + stack_settings = StackConfig( name=stack_name, template_path=heat_tmplt_path, env_values=env_values) self.heat_client = heat_utils.heat_client(self.os_creds) self.stack = heat_utils.create_stack(self.heat_client, stack_settings) - # Wait until stack deployment has completed - end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT - is_active = False - while time.time() < end_time: - status = heat_utils.get_stack_status(self.heat_client, - self.stack.id) - if status == create_stack.STATUS_CREATE_COMPLETE: - is_active = True - break - elif status == create_stack.STATUS_CREATE_FAILED: - is_active = False - break + self.assertTrue(stack_active(self.heat_client, self.stack)) - time.sleep(3) - self.assertTrue(is_active) + self.keypair1_settings = None + self.keypair2_settings = None def tearDown(self): """ - Cleans the image and downloaded image file + Cleans the stack and image """ if self.stack: try: heat_utils.delete_stack(self.heat_client, self.stack) # Wait until stack deployment has completed - end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT + end_time = (time.time() + + stack_config.STACK_COMPLETE_TIMEOUT) is_deleted = False while time.time() < end_time: status = heat_utils.get_stack_status(self.heat_client, self.stack.id) - if status == create_stack.STATUS_DELETE_COMPLETE: + if status == stack_config.STATUS_DELETE_COMPLETE: is_deleted = True break - elif status == create_stack.STATUS_DELETE_FAILED: + elif status == stack_config.STATUS_DELETE_FAILED: is_deleted = False break @@ -361,11 +311,11 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): neutron = neutron_utils.neutron_client(self.os_creds) glance = glance_utils.glance_client(self.os_creds) servers = heat_utils.get_stack_servers( - self.heat_client, nova, self.stack) + self.heat_client, nova, neutron, self.stack) for server in servers: - vm_settings = settings_utils.create_vm_inst_settings( + vm_settings = settings_utils.create_vm_inst_config( nova, neutron, server) - img_settings = settings_utils.determine_image_settings( + img_settings = settings_utils.determine_image_config( glance, server, [self.image_creator1.image_settings, self.image_creator2.image_settings]) @@ -392,14 +342,26 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): except: pass + if self.keypair1_settings: + expanded_path = os.path.expanduser( + self.keypair1_settings.private_filepath) + os.chmod(expanded_path, 0o755) + os.remove(expanded_path) + + if self.keypair2_settings: + expanded_path = os.path.expanduser( + self.keypair2_settings.private_filepath) + os.chmod(expanded_path, 0o755) + os.remove(expanded_path) + def test_get_settings_from_stack(self): """ Tests that a heat template with floating IPs and can have the proper settings derived from settings_utils.py. """ - resources = heat_utils.get_resources(self.heat_client, self.stack) + resources = heat_utils.get_resources(self.heat_client, self.stack.id) self.assertIsNotNone(resources) - self.assertEqual(11, len(resources)) + self.assertEqual(12, len(resources)) options = heat_utils.get_outputs(self.heat_client, self.stack) self.assertIsNotNone(options) @@ -412,7 +374,7 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): self.assertEqual(1, len(networks)) self.assertEqual(self.network_name, networks[0].name) - network_settings = settings_utils.create_network_settings( + network_settings = settings_utils.create_network_config( neutron, networks[0]) self.assertIsNotNone(network_settings) self.assertEqual(self.network_name, network_settings.name) @@ -421,11 +383,11 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): glance = glance_utils.glance_client(self.os_creds) servers = heat_utils.get_stack_servers( - self.heat_client, nova, self.stack) + self.heat_client, nova, neutron, self.stack) self.assertIsNotNone(servers) self.assertEqual(2, len(servers)) - image_settings = settings_utils.determine_image_settings( + image_settings = settings_utils.determine_image_config( glance, servers[0], [self.image_creator1.image_settings, self.image_creator2.image_settings]) @@ -438,7 +400,7 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): self.assertEqual( self.image_creator2.image_settings.name, image_settings.name) - image_settings = settings_utils.determine_image_settings( + image_settings = settings_utils.determine_image_config( glance, servers[1], [self.image_creator1.image_settings, self.image_creator2.image_settings]) @@ -449,14 +411,398 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): self.assertEqual( self.image_creator2.image_settings.name, image_settings.name) - keypair1_settings = settings_utils.determine_keypair_settings( + self.keypair1_settings = settings_utils.determine_keypair_config( self.heat_client, self.stack, servers[0], priv_key_key='private_key') - self.assertIsNotNone(keypair1_settings) - self.assertEqual(self.keypair_name, keypair1_settings.name) + self.assertIsNotNone(self.keypair1_settings) + self.assertEqual(self.keypair_name, self.keypair1_settings.name) - keypair2_settings = settings_utils.determine_keypair_settings( + self.keypair2_settings = settings_utils.determine_keypair_config( self.heat_client, self.stack, servers[1], priv_key_key='private_key') - self.assertIsNotNone(keypair2_settings) - self.assertEqual(self.keypair_name, keypair2_settings.name) + self.assertIsNotNone(self.keypair2_settings) + self.assertEqual(self.keypair_name, self.keypair2_settings.name) + + +class HeatUtilsRouterTests(OSComponentTestCase): + """ + Test Heat volume functionality + """ + + def setUp(self): + """ + Instantiates OpenStack instances that cannot be spawned by Heat + """ + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + stack_name = guid + '-stack' + + self.net_name = guid + '-net' + self.subnet_name = guid + '-subnet' + self.router_name = guid + '-router' + + env_values = { + 'net_name': self.net_name, + 'subnet_name': self.subnet_name, + 'router_name': self.router_name, + 'external_net_name': self.ext_net_name} + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'router_heat_template.yaml') + self.stack_settings = StackConfig( + name=stack_name, template_path=heat_tmplt_path, + env_values=env_values) + self.stack = None + self.heat_client = heat_utils.heat_client(self.os_creds) + self.neutron = neutron_utils.neutron_client(self.os_creds) + + def tearDown(self): + """ + Cleans the image and downloaded image file + """ + if self.stack: + try: + heat_utils.delete_stack(self.heat_client, self.stack) + except: + pass + + def test_create_router_with_stack(self): + """ + Tests the creation of an OpenStack router with Heat and the retrieval + of the Router Domain objects from heat_utils#get_stack_routers(). + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + + # Wait until stack deployment has completed + end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT + is_active = False + while time.time() < end_time: + status = heat_utils.get_stack_status(self.heat_client, + self.stack.id) + if status == stack_config.STATUS_CREATE_COMPLETE: + is_active = True + break + elif status == stack_config.STATUS_CREATE_FAILED: + is_active = False + break + + time.sleep(3) + + self.assertTrue(is_active) + + routers = heat_utils.get_stack_routers( + self.heat_client, self.neutron, self.stack) + + self.assertEqual(1, len(routers)) + + router = routers[0] + self.assertEqual(self.router_name, router.name) + + ext_net = neutron_utils.get_network( + self.neutron, network_name=self.ext_net_name) + self.assertEqual(ext_net.id, router.external_network_id) + + +class HeatUtilsVolumeTests(OSComponentTestCase): + """ + Test Heat volume functionality + """ + + def setUp(self): + """ + Instantiates OpenStack instances that cannot be spawned by Heat + """ + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + stack_name = guid + '-stack' + self.volume_name = guid + '-vol' + self.volume_type_name = guid + '-vol-type' + + env_values = { + 'volume_name': self.volume_name, + 'volume_type_name': self.volume_type_name} + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'volume_heat_template.yaml') + self.stack_settings = StackConfig( + name=stack_name, template_path=heat_tmplt_path, + env_values=env_values) + self.stack = None + self.heat_client = heat_utils.heat_client(self.os_creds) + self.cinder = cinder_utils.cinder_client(self.os_creds) + + def tearDown(self): + """ + Cleans the stack + """ + if self.stack: + try: + heat_utils.delete_stack(self.heat_client, self.stack) + except: + pass + + def test_create_vol_with_stack(self): + """ + Tests the creation of an OpenStack volume with Heat. + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + self.assertTrue(stack_active(self.heat_client, self.stack)) + + volumes = heat_utils.get_stack_volumes( + self.heat_client, self.cinder, self.stack) + + self.assertEqual(1, len(volumes)) + + volume = volumes[0] + self.assertEqual(self.volume_name, volume.name) + self.assertEqual(self.volume_type_name, volume.type) + self.assertEqual(1, volume.size) + self.assertEqual(False, volume.multi_attach) + + def test_create_vol_types_with_stack(self): + """ + Tests the creation of an OpenStack volume with Heat. + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + self.assertTrue(stack_active(self.heat_client, self.stack)) + + volume_types = heat_utils.get_stack_volume_types( + self.heat_client, self.cinder, self.stack) + + self.assertEqual(1, len(volume_types)) + + volume_type = volume_types[0] + + self.assertEqual(self.volume_type_name, volume_type.name) + self.assertTrue(volume_type.public) + self.assertIsNone(volume_type.qos_spec) + + # TODO - Add encryption back and find out why it broke in Pike + # encryption = volume_type.encryption + # self.assertIsNotNone(encryption) + # self.assertIsNone(encryption.cipher) + # self.assertEqual('front-end', encryption.control_location) + # self.assertIsNone(encryption.key_size) + # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor', + # encryption.provider) + # self.assertEqual(volume_type.id, encryption.volume_type_id) + + +class HeatUtilsFlavorTests(OSComponentTestCase): + """ + Test Heat volume functionality + """ + + def setUp(self): + """ + Instantiates OpenStack instances that cannot be spawned by Heat + """ + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.name_prefix = guid + stack_name = guid + '-stack' + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'flavor_heat_template.yaml') + self.stack_settings = StackConfig( + name=stack_name, template_path=heat_tmplt_path) + self.stack = None + self.heat_client = heat_utils.heat_client(self.os_creds) + self.nova = nova_utils.nova_client(self.os_creds) + + def tearDown(self): + """ + Cleans the stack + """ + if self.stack: + try: + heat_utils.delete_stack(self.heat_client, self.stack) + except: + pass + + def test_create_flavor_with_stack(self): + """ + Tests the creation of an OpenStack volume with Heat. + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + + self.assertTrue(stack_active(self.heat_client, self.stack)) + + flavors = heat_utils.get_stack_flavors( + self.heat_client, self.nova, self.stack) + + self.assertEqual(1, len(flavors)) + + flavor = flavors[0] + self.assertTrue(flavor.name.startswith(self.name_prefix)) + self.assertEqual(1024, flavor.ram) + self.assertEqual(200, flavor.disk) + self.assertEqual(8, flavor.vcpus) + self.assertEqual(0, flavor.ephemeral) + self.assertIsNone(flavor.swap) + self.assertEqual(1.0, flavor.rxtx_factor) + self.assertTrue(flavor.is_public) + + +class HeatUtilsKeypairTests(OSComponentTestCase): + """ + Test Heat volume functionality + """ + + def setUp(self): + """ + Instantiates OpenStack instances that cannot be spawned by Heat + """ + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + stack_name = guid + '-stack' + self.keypair_name = guid + '-kp' + + env_values = {'keypair_name': self.keypair_name} + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'keypair_heat_template.yaml') + self.stack_settings = StackConfig( + name=stack_name, template_path=heat_tmplt_path, + env_values=env_values) + self.stack = None + self.heat_client = heat_utils.heat_client(self.os_creds) + self.nova = nova_utils.nova_client(self.os_creds) + + def tearDown(self): + """ + Cleans the stack + """ + if self.stack: + try: + heat_utils.delete_stack(self.heat_client, self.stack) + except: + pass + + def test_create_keypair_with_stack(self): + """ + Tests the creation of an OpenStack keypair with Heat. + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + self.assertTrue(stack_active(self.heat_client, self.stack)) + + keypairs = heat_utils.get_stack_keypairs( + self.heat_client, self.nova, self.stack) + + self.assertEqual(1, len(keypairs)) + keypair = keypairs[0] + + self.assertEqual(self.keypair_name, keypair.name) + + outputs = heat_utils.get_outputs(self.heat_client, self.stack) + + for output in outputs: + if output.key == 'private_key': + self.assertTrue(output.value.startswith( + '-----BEGIN RSA PRIVATE KEY-----')) + + keypair = nova_utils.get_keypair_by_id(self.nova, keypair.id) + self.assertIsNotNone(keypair) + + self.assertEqual(self.keypair_name, keypair.name) + + +class HeatUtilsSecurityGroupTests(OSComponentTestCase): + """ + Test Heat volume functionality + """ + + def setUp(self): + """ + Instantiates OpenStack instances that cannot be spawned by Heat + """ + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + stack_name = guid + '-stack' + self.sec_grp_name = guid + '-sec-grp' + + env_values = {'security_group_name': self.sec_grp_name} + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'security_group_heat_template.yaml') + self.stack_settings = StackConfig( + name=stack_name, template_path=heat_tmplt_path, + env_values=env_values) + self.stack = None + self.heat_client = heat_utils.heat_client(self.os_creds) + self.neutron = neutron_utils.neutron_client(self.os_creds) + + def tearDown(self): + """ + Cleans the stack + """ + if self.stack: + try: + heat_utils.delete_stack(self.heat_client, self.stack) + except: + pass + + def test_create_security_group_with_stack(self): + """ + Tests the creation of an OpenStack SecurityGroup with Heat. + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + self.assertTrue(stack_active(self.heat_client, self.stack)) + + sec_grp = heat_utils.get_stack_security_groups( + self.heat_client, self.neutron, self.stack)[0] + + self.assertEqual(self.sec_grp_name, sec_grp.name) + self.assertEqual('Test description', sec_grp.description) + self.assertEqual(2, len(sec_grp.rules)) + + has_ssh_rule = False + has_icmp_rule = False + + for rule in sec_grp.rules: + if (rule.security_group_id == sec_grp.id + and rule.direction == 'egress' + and rule.ethertype == 'IPv4' + and rule.port_range_min == 22 + and rule.port_range_max == 22 + and rule.protocol == 'tcp' + and rule.remote_group_id is None + and rule.remote_ip_prefix == '0.0.0.0/0'): + has_ssh_rule = True + if (rule.security_group_id == sec_grp.id + and rule.direction == 'ingress' + and rule.ethertype == 'IPv4' + and rule.port_range_min is None + and rule.port_range_max is None + and rule.protocol == 'icmp' + and rule.remote_group_id is None + and rule.remote_ip_prefix == '0.0.0.0/0'): + has_icmp_rule = True + + self.assertTrue(has_ssh_rule) + self.assertTrue(has_icmp_rule) + + +def stack_active(heat_cli, stack): + """ + Blocks until stack application has successfully completed or failed + :param heat_cli: the Heat client + :param stack: the Stack domain object + :return: T/F + """ + # Wait until stack deployment has completed + end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT + is_active = False + while time.time() < end_time: + status = heat_utils.get_stack_status(heat_cli, stack.id) + if status == stack_config.STATUS_CREATE_COMPLETE: + is_active = True + break + elif status == stack_config.STATUS_CREATE_FAILED: + is_active = False + break + + time.sleep(3) + + return is_active diff --git a/snaps/openstack/utils/tests/keystone_utils_tests.py b/snaps/openstack/utils/tests/keystone_utils_tests.py index bd0086b..b7f024d 100644 --- a/snaps/openstack/utils/tests/keystone_utils_tests.py +++ b/snaps/openstack/utils/tests/keystone_utils_tests.py @@ -14,8 +14,8 @@ # limitations under the License. import uuid -from snaps.openstack.create_project import ProjectSettings -from snaps.openstack.create_user import UserSettings +from snaps.config.project import ProjectConfig +from snaps.config.user import UserConfig from snaps.openstack.tests.os_source_file_test import OSComponentTestCase from snaps.openstack.utils import keystone_utils, neutron_utils @@ -96,7 +96,7 @@ class KeystoneUtilsTests(OSComponentTestCase): """ Tests the keystone_utils.create_user() function """ - user_settings = UserSettings( + user_settings = UserConfig( name=self.username, password=str(uuid.uuid4()), domain_name=self.os_creds.user_domain_name) @@ -111,7 +111,7 @@ class KeystoneUtilsTests(OSComponentTestCase): """ Tests the keyston_utils.create_project() funtion """ - project_settings = ProjectSettings( + project_settings = ProjectConfig( name=self.project_name, domain=self.os_creds.project_domain_name) self.project = keystone_utils.create_project(self.keystone, project_settings) @@ -180,13 +180,13 @@ class KeystoneUtilsTests(OSComponentTestCase): Tests the keystone_utils function grant_user_role_to_project() :return: """ - user_settings = UserSettings( + user_settings = UserConfig( name=self.username, password=str(uuid.uuid4()), domain_name=self.os_creds.user_domain_name) self.user = keystone_utils.create_user(self.keystone, user_settings) self.assertEqual(self.username, self.user.name) - project_settings = ProjectSettings( + project_settings = ProjectConfig( name=self.project_name, domain=self.os_creds.project_domain_name) self.project = keystone_utils.create_project(self.keystone, project_settings) diff --git a/snaps/openstack/utils/tests/magnum_utils_tests.py b/snaps/openstack/utils/tests/magnum_utils_tests.py new file mode 100644 index 0000000..f841c48 --- /dev/null +++ b/snaps/openstack/utils/tests/magnum_utils_tests.py @@ -0,0 +1,299 @@ +# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs") +# and others. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES 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 uuid + +from magnumclient.common.apiclient.exceptions import BadRequest + +from snaps.config.cluster_template import ( + ClusterTemplateConfig, ServerType, ContainerOrchestrationEngine, + DockerStorageDriver) +from snaps.config.flavor import FlavorConfig +from snaps.config.keypair import KeypairConfig +from snaps.openstack.create_flavor import OpenStackFlavor +from snaps.openstack.create_image import OpenStackImage +from snaps.openstack.create_keypairs import OpenStackKeypair +from snaps.openstack.os_credentials import OSCreds +from snaps.openstack.tests import openstack_tests +from snaps.openstack.tests.os_source_file_test import OSComponentTestCase +from snaps.openstack.utils import magnum_utils + +__author__ = 'spisarski' + +logger = logging.getLogger('magnum_utils_tests') + + +class MagnumSmokeTests(OSComponentTestCase): + """ + Tests to ensure that the magnum client can communicate with the cloud + """ + + def test_connect_success(self): + """ + Tests to ensure that the proper credentials can connect. + """ + magnum = magnum_utils.magnum_client(self.os_creds) + + # This should not throw an exception + self.assertIsNotNone(magnum.clusters.list()) + + def test_nova_connect_fail(self): + """ + Tests to ensure that the improper credentials cannot connect. + """ + + with self.assertRaises(RuntimeError): + magnum_utils.magnum_client( + OSCreds(username='user', password='pass', + auth_url=self.os_creds.auth_url, + project_name=self.os_creds.project_name, + proxy_settings=self.os_creds.proxy_settings)) + + +class MagnumUtilsClusterTypeTests(OSComponentTestCase): + """ + Tests individual functions within magnum_utils.py + """ + + def setUp(self): + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.cluster_type_name = self.guid + '-cluster-type' + self.magnum = magnum_utils.magnum_client(self.os_creds) + + metadata = self.image_metadata + if not metadata: + metadata = dict() + if 'extra_properties' not in metadata: + metadata['extra_properties'] = dict() + metadata['extra_properties']['os_distro'] = 'cirros' + + os_image_settings = openstack_tests.cirros_image_settings( + name=self.guid + '-image', image_metadata=metadata) + + self.image_creator = OpenStackImage(self.os_creds, os_image_settings) + + self.flavor_creator = OpenStackFlavor( + self.os_creds, FlavorConfig( + name=self.guid + '-flavor', ram=512, disk=10, vcpus=1)) + + keypair_priv_filepath = 'tmp/' + self.guid + keypair_pub_filepath = keypair_priv_filepath + '.pub' + + self.keypair_creator = OpenStackKeypair( + self.os_creds, KeypairConfig( + name=self.guid + '-keypair', + public_filepath=keypair_pub_filepath, + private_filepath=keypair_priv_filepath)) + + self.cluster_template = None + + try: + self.image_creator.create() + self.flavor_creator.create() + self.keypair_creator.create() + except: + self.tearDown() + raise + + def tearDown(self): + if self.cluster_template: + try: + magnum_utils.delete_cluster_template( + self.magnum, self.cluster_template.id) + except: + pass + if self.keypair_creator: + try: + self.keypair_creator.clean() + except: + pass + if self.flavor_creator: + try: + self.flavor_creator.clean() + except: + pass + if self.image_creator: + try: + self.image_creator.clean() + except: + pass + + def test_create_cluster_template_simple(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name) + + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + self.assertIsNotNone(self.cluster_template) + self.assertTrue( + validate_cluster_template(config, self.cluster_template)) + + template_by_name = magnum_utils.get_cluster_template( + self.magnum, template_name=config.name) + self.assertEqual(self.cluster_template, template_by_name) + template_by_id = magnum_utils.get_cluster_template_by_id( + self.magnum, self.cluster_template.id) + self.assertEqual(self.cluster_template, template_by_id) + + def test_create_cluster_template_all(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + network_driver='flannel', external_net=self.ext_net_name, + floating_ip_enabled=True, docker_volume_size=100, + server_type=ServerType.vm, + flavor=self.flavor_creator.flavor_settings.name, + master_flavor=self.flavor_creator.flavor_settings.name, + coe=ContainerOrchestrationEngine.kubernetes, + fixed_net='foo', fixed_subnet='bar', + registry_enabled=True, insecure_registry='localhost', + docker_storage_driver=DockerStorageDriver.overlay, + dns_nameserver='8.8.4.4', public=True, tls_disabled=True, + http_proxy=None, https_proxy=None, volume_driver='cinder', + master_lb_enabled=False, labels={'foo': 'bar'}) + + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + self.assertIsNotNone(self.cluster_template) + self.assertTrue( + validate_cluster_template(config, self.cluster_template)) + + template_by_name = magnum_utils.get_cluster_template( + self.magnum, template_name=config.name) + self.assertEqual(self.cluster_template, template_by_name) + template_by_id = magnum_utils.get_cluster_template_by_id( + self.magnum, self.cluster_template.id) + self.assertEqual(self.cluster_template, template_by_id) + + def test_create_cluster_template_bad_image(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image='foo', + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name) + + with self.assertRaises(BadRequest): + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + + def test_create_cluster_template_bad_ext_net(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net='foo', + flavor=self.flavor_creator.flavor_settings.name) + + with self.assertRaises(BadRequest): + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + + def test_create_cluster_template_bad_flavor(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor='foo') + + with self.assertRaises(BadRequest): + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + + def test_create_cluster_template_bad_master_flavor(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + flavor=self.flavor_creator.flavor_settings.name, + master_flavor='foo') + + with self.assertRaises(BadRequest): + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + + def test_create_cluster_template_bad_network_driver(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + network_driver='foo') + + with self.assertRaises(BadRequest): + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + + def test_create_cluster_template_bad_volume_driver(self): + config = ClusterTemplateConfig( + name=self.cluster_type_name, + image=self.image_creator.image_settings.name, + keypair=self.keypair_creator.keypair_settings.name, + external_net=self.ext_net_name, + volume_driver='foo') + + with self.assertRaises(BadRequest): + self.cluster_template = magnum_utils.create_cluster_template( + self.magnum, config) + + +def validate_cluster_template(tmplt_config, tmplt_obj): + """ + Returns true if the configuration matches the ClusterTemplate object + :param tmplt_config: the ClusterTemplateConfig object + :param tmplt_obj: the ClusterTemplate domain object + :return: T/F + """ + if not tmplt_config.network_driver: + network_driver = 'flannel' + else: + network_driver = tmplt_config.network_driver + + return ( + tmplt_config.coe.value == tmplt_obj.coe and + tmplt_config.dns_nameserver == tmplt_obj.dns_nameserver and + tmplt_config.docker_storage_driver.value + == tmplt_obj.docker_storage_driver and + tmplt_config.docker_volume_size == tmplt_obj.docker_volume_size and + tmplt_config.external_net == tmplt_obj.external_net and + tmplt_config.fixed_net == tmplt_obj.fixed_net and + tmplt_config.fixed_subnet == tmplt_obj.fixed_subnet and + tmplt_config.flavor == tmplt_obj.flavor and + tmplt_config.floating_ip_enabled == tmplt_obj.floating_ip_enabled and + tmplt_config.http_proxy == tmplt_obj.http_proxy and + tmplt_config.https_proxy == tmplt_obj.https_proxy and + tmplt_config.no_proxy == tmplt_obj.no_proxy and + tmplt_config.image == tmplt_obj.image and + tmplt_config.insecure_registry == tmplt_obj.insecure_registry and + tmplt_config.keypair == tmplt_obj.keypair and + tmplt_config.labels == tmplt_obj.labels and + tmplt_config.master_flavor == tmplt_obj.master_flavor and + tmplt_config.master_lb_enabled == tmplt_obj.master_lb_enabled and + tmplt_config.name == tmplt_obj.name and + network_driver == tmplt_obj.network_driver and + tmplt_config.no_proxy == tmplt_obj.no_proxy and + tmplt_config.public == tmplt_obj.public and + tmplt_config.registry_enabled == tmplt_obj.registry_enabled and + tmplt_config.server_type.value == tmplt_obj.server_type and + tmplt_config.tls_disabled == tmplt_obj.tls_disabled and + tmplt_config.volume_driver == tmplt_obj.volume_driver + ) diff --git a/snaps/openstack/utils/tests/neutron_utils_tests.py b/snaps/openstack/utils/tests/neutron_utils_tests.py index 05d508d..38faf71 100644 --- a/snaps/openstack/utils/tests/neutron_utils_tests.py +++ b/snaps/openstack/utils/tests/neutron_utils_tests.py @@ -14,11 +14,11 @@ # limitations under the License. import uuid -from snaps.openstack import create_router -from snaps.openstack.create_network import NetworkSettings, SubnetSettings, \ - PortSettings -from snaps.openstack.create_security_group import SecurityGroupSettings, \ - SecurityGroupRuleSettings, Direction +from neutronclient.common.exceptions import NotFound, BadRequest + +from snaps.config.network import NetworkConfig, SubnetConfig, PortConfig +from snaps.config.security_group import ( + SecurityGroupConfig, SecurityGroupRuleConfig, Direction) from snaps.openstack.tests import openstack_tests from snaps.openstack.tests import validation_utils from snaps.openstack.tests.os_source_file_test import OSComponentTestCase @@ -102,7 +102,7 @@ class NeutronUtilsNetworkTests(OSComponentTestCase): def test_create_network(self): """ - Tests the neutron_utils.create_neutron_net() function + Tests the neutron_utils.create_network() function """ self.network = neutron_utils.create_network( self.neutron, self.os_creds, self.net_config.network_settings) @@ -110,26 +110,28 @@ class NeutronUtilsNetworkTests(OSComponentTestCase): self.network.name) self.assertTrue(validate_network( self.neutron, self.net_config.network_settings.name, True)) + self.assertEqual(len(self.net_config.network_settings.subnet_settings), + len(self.network.subnets)) def test_create_network_empty_name(self): """ - Tests the neutron_utils.create_neutron_net() function with an empty + Tests the neutron_utils.create_network() function with an empty network name """ with self.assertRaises(Exception): self.network = neutron_utils.create_network( self.neutron, self.os_creds, - network_settings=NetworkSettings(name='')) + network_settings=NetworkConfig(name='')) def test_create_network_null_name(self): """ - Tests the neutron_utils.create_neutron_net() function when the network + Tests the neutron_utils.create_network() function when the network name is None """ with self.assertRaises(Exception): self.network = neutron_utils.create_network( self.neutron, self.os_creds, - network_settings=NetworkSettings()) + network_settings=NetworkConfig()) class NeutronUtilsSubnetTests(OSComponentTestCase): @@ -142,7 +144,6 @@ class NeutronUtilsSubnetTests(OSComponentTestCase): self.port_name = str(guid) + '-port' self.neutron = neutron_utils.neutron_client(self.os_creds) self.network = None - self.subnet = None self.net_config = openstack_tests.get_pub_net_config( net_name=guid + '-pub-net', subnet_name=guid + '-pub-subnet', external_net=self.ext_net_name) @@ -151,11 +152,6 @@ class NeutronUtilsSubnetTests(OSComponentTestCase): """ Cleans the remote OpenStack objects """ - if self.subnet: - try: - neutron_utils.delete_subnet(self.neutron, self.subnet) - except: - pass if self.network: try: neutron_utils.delete_network(self.neutron, self.network) @@ -164,7 +160,7 @@ class NeutronUtilsSubnetTests(OSComponentTestCase): def test_create_subnet(self): """ - Tests the neutron_utils.create_neutron_net() function + Tests the neutron_utils.create_network() function """ self.network = neutron_utils.create_network( self.neutron, self.os_creds, self.net_config.network_settings) @@ -174,20 +170,18 @@ class NeutronUtilsSubnetTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, self.os_creds, network=self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) subnet_query1 = neutron_utils.get_subnet( self.neutron, subnet_name=subnet_setting.name) - self.assertEqual(self.subnet, subnet_query1) + self.assertEqual(self.network.subnets[0], subnet_query1) subnet_query2 = neutron_utils.get_subnets_by_network(self.neutron, self.network) self.assertIsNotNone(subnet_query2) self.assertEqual(1, len(subnet_query2)) - self.assertEqual(self.subnet, subnet_query2[0]) + self.assertEqual(self.network.subnets[0], subnet_query2[0]) def test_create_subnet_null_name(self): """ @@ -202,11 +196,11 @@ class NeutronUtilsSubnetTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) with self.assertRaises(Exception): - SubnetSettings(cidr=self.net_config.subnet_cidr) + SubnetConfig(cidr=self.net_config.subnet_cidr) def test_create_subnet_empty_name(self): """ - Tests the neutron_utils.create_neutron_net() function with an empty + Tests the neutron_utils.create_network() function with an empty name """ self.network = neutron_utils.create_network( @@ -217,8 +211,6 @@ class NeutronUtilsSubnetTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, self.os_creds, network=self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) self.assertFalse(validate_subnet( @@ -226,49 +218,265 @@ class NeutronUtilsSubnetTests(OSComponentTestCase): subnet_query1 = neutron_utils.get_subnet( self.neutron, subnet_name=subnet_setting.name) - self.assertEqual(self.subnet, subnet_query1) + self.assertEqual(self.network.subnets[0], subnet_query1) subnet_query2 = neutron_utils.get_subnets_by_network(self.neutron, self.network) self.assertIsNotNone(subnet_query2) self.assertEqual(1, len(subnet_query2)) - self.assertEqual(self.subnet, subnet_query2[0]) + self.assertEqual(self.network.subnets[0], subnet_query2[0]) def test_create_subnet_null_cidr(self): """ Tests the neutron_utils.create_neutron_subnet() function for an Exception when the subnet CIDR value is None """ - self.network = neutron_utils.create_network( - self.neutron, self.os_creds, self.net_config.network_settings) - self.assertEqual(self.net_config.network_settings.name, - self.network.name) - self.assertTrue(validate_network( - self.neutron, self.net_config.network_settings.name, True)) - + self.net_config.network_settings.subnet_settings[0].cidr = None with self.assertRaises(Exception): - sub_sets = SubnetSettings( - cidr=None, name=self.net_config.subnet_name) - neutron_utils.create_subnet( - self.neutron, sub_sets, self.os_creds, network=self.network) + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.net_config.network_settings) def test_create_subnet_empty_cidr(self): """ Tests the neutron_utils.create_neutron_subnet() function for an Exception when the subnet CIDR value is empty """ + self.net_config.network_settings.subnet_settings[0].cidr = '' + with self.assertRaises(Exception): + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.net_config.network_settings) + + +class NeutronUtilsIPv6Tests(OSComponentTestCase): + """ + Test for creating IPv6 networks with subnets via neutron_utils.py + """ + + def setUp(self): + self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + self.neutron = neutron_utils.neutron_client(self.os_creds) + self.network = None + + def tearDown(self): + """ + Cleans the remote OpenStack objects + """ + if self.network: + try: + neutron_utils.delete_network(self.neutron, self.network) + except: + pass + + def test_create_network_slaac(self): + """ + Tests the neutron_utils.create_network() with an IPv6 subnet where DHCP + is True and IPv6 modes are slaac + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'], + gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff', + enable_dhcp=True, ipv6_ra_mode='slaac', ipv6_address_mode='slaac') + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + self.network = neutron_utils.create_network( - self.neutron, self.os_creds, self.net_config.network_settings) - self.assertEqual(self.net_config.network_settings.name, - self.network.name) - self.assertTrue(validate_network( - self.neutron, self.net_config.network_settings.name, True)) + self.neutron, self.os_creds, self.network_settings) + self.assertEqual(self.network_settings.name, self.network.name) + + subnet_settings = self.network_settings.subnet_settings[0] + self.assertEqual(1, len(self.network.subnets)) + subnet = self.network.subnets[0] + + self.assertEqual(self.network.id, subnet.network_id) + self.assertEqual(subnet_settings.name, subnet.name) + self.assertEqual(subnet_settings.start, subnet.start) + self.assertEqual(subnet_settings.end, subnet.end) + self.assertEqual('1:1::/64', subnet.cidr) + self.assertEqual(6, subnet.ip_version) + self.assertEqual(1, len(subnet.dns_nameservers)) + self.assertEqual( + sub_setting.dns_nameservers[0], subnet.dns_nameservers[0]) + self.assertTrue(subnet.enable_dhcp) + self.assertEqual( + subnet_settings.ipv6_ra_mode.value, subnet.ipv6_ra_mode) + self.assertEqual( + subnet_settings.ipv6_address_mode.value, subnet.ipv6_address_mode) - with self.assertRaises(Exception): - sub_sets = SubnetSettings( - cidr='', name=self.net_config.subnet_name) - neutron_utils.create_subnet(self.neutron, sub_sets, self.os_creds, - network=self.network) + def test_create_network_stateful(self): + """ + Tests the neutron_utils.create_network() with an IPv6 subnet where DHCP + is True and IPv6 modes are stateful + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'], + gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff', + enable_dhcp=True, ipv6_ra_mode='dhcpv6-stateful', + ipv6_address_mode='dhcpv6-stateful') + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) + + self.assertEqual(self.network_settings.name, self.network.name) + + subnet_settings = self.network_settings.subnet_settings[0] + self.assertEqual(1, len(self.network.subnets)) + subnet = self.network.subnets[0] + + self.assertEqual(self.network.id, subnet.network_id) + self.assertEqual(subnet_settings.name, subnet.name) + self.assertEqual(subnet_settings.start, subnet.start) + self.assertEqual(subnet_settings.end, subnet.end) + self.assertEqual('1:1::/64', subnet.cidr) + self.assertEqual(6, subnet.ip_version) + self.assertEqual(1, len(subnet.dns_nameservers)) + self.assertEqual( + sub_setting.dns_nameservers[0], subnet.dns_nameservers[0]) + self.assertTrue(subnet.enable_dhcp) + self.assertEqual( + subnet_settings.ipv6_ra_mode.value, subnet.ipv6_ra_mode) + self.assertEqual( + subnet_settings.ipv6_address_mode.value, subnet.ipv6_address_mode) + + def test_create_network_stateless(self): + """ + Tests the neutron_utils.create_network() when DHCP is enabled and + the RA and address modes are both 'slaac' + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'], + gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff', + enable_dhcp=True, ipv6_ra_mode='dhcpv6-stateless', + ipv6_address_mode='dhcpv6-stateless') + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) + + self.assertEqual(self.network_settings.name, self.network.name) + + subnet_settings = self.network_settings.subnet_settings[0] + self.assertEqual(1, len(self.network.subnets)) + subnet = self.network.subnets[0] + + self.assertEqual(self.network.id, subnet.network_id) + self.assertEqual(subnet_settings.name, subnet.name) + self.assertEqual(subnet_settings.start, subnet.start) + self.assertEqual(subnet_settings.end, subnet.end) + self.assertEqual('1:1::/64', subnet.cidr) + self.assertEqual(6, subnet.ip_version) + self.assertEqual(1, len(subnet.dns_nameservers)) + self.assertEqual( + sub_setting.dns_nameservers[0], subnet.dns_nameservers[0]) + self.assertTrue(subnet.enable_dhcp) + self.assertEqual( + subnet_settings.ipv6_ra_mode.value, subnet.ipv6_ra_mode) + self.assertEqual( + subnet_settings.ipv6_address_mode.value, subnet.ipv6_address_mode) + + def test_create_network_no_dhcp_slaac(self): + """ + Tests the neutron_utils.create_network() for a BadRequest when + DHCP is not enabled and the RA and address modes are both 'slaac' + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1:0:0:0:0:0:0/64', + ip_version=6, dns_nameservers=['2620:0:ccc:0:0:0:0:2'], + gateway_ip='1:1:0:0:0:0:0:1', start='1:1::ff', end='1:1::ffff', + enable_dhcp=False, ipv6_ra_mode='slaac', ipv6_address_mode='slaac') + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + with self.assertRaises(BadRequest): + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) + + def test_create_network_invalid_start_ip(self): + """ + Tests the neutron_utils.create_network() that contains one IPv6 subnet + with an invalid start IP to ensure Neutron assigns it the smallest IP + possible + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6, + start='foo') + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) + + self.assertEqual('1:1::2', self.network.subnets[0].start) + self.assertEqual( + '1:1:0:ffff:ffff:ffff:ffff:ffff', self.network.subnets[0].end) + + def test_create_network_invalid_end_ip(self): + """ + Tests the neutron_utils.create_network() that contains one IPv6 subnet + with an invalid end IP to ensure Neutron assigns it the largest IP + possible + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6, + end='bar') + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) + + self.assertEqual('1:1::2', self.network.subnets[0].start) + self.assertEqual( + '1:1:0:ffff:ffff:ffff:ffff:ffff', self.network.subnets[0].end) + + def test_create_network_with_bad_cidr(self): + """ + Tests the neutron_utils.create_network() for a BadRequest when + the subnet CIDR is invalid + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1:1:/48', ip_version=6) + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + with self.assertRaises(BadRequest): + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) + + def test_create_network_invalid_gateway_ip(self): + """ + Tests the neutron_utils.create_network() for a BadRequest when + the subnet gateway IP is invalid + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6, + gateway_ip='1:2::1') + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + with self.assertRaises(BadRequest): + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) + + def test_create_network_with_bad_dns(self): + """ + Tests the neutron_utils.create_network() for a BadRequest when + the DNS IP is invalid + """ + sub_setting = SubnetConfig( + name=self.guid + '-subnet', cidr='1:1::/48', ip_version=6, + dns_nameservers=['foo']) + self.network_settings = NetworkConfig( + name=self.guid + '-net', subnet_settings=[sub_setting]) + + with self.assertRaises(BadRequest): + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.network_settings) class NeutronUtilsRouterTests(OSComponentTestCase): @@ -281,7 +489,6 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.port_name = str(guid) + '-port' self.neutron = neutron_utils.neutron_client(self.os_creds) self.network = None - self.subnet = None self.port = None self.router = None self.interface_router = None @@ -294,8 +501,8 @@ class NeutronUtilsRouterTests(OSComponentTestCase): Cleans the remote OpenStack objects """ if self.interface_router: - neutron_utils.remove_interface_router(self.neutron, self.router, - self.subnet) + neutron_utils.remove_interface_router( + self.neutron, self.router, self.network.subnets[0]) if self.router: try: @@ -310,22 +517,12 @@ class NeutronUtilsRouterTests(OSComponentTestCase): except: pass - if self.subnet: - try: - neutron_utils.delete_subnet(self.neutron, self.subnet) - except: - pass - if self.network: - try: - neutron_utils.delete_network(self.neutron, self.network) - except: - pass + neutron_utils.delete_network(self.neutron, self.network) def test_create_router_simple(self): """ - Tests the neutron_utils.create_neutron_net() function when an external - gateway is requested + Tests the neutron_utils.create_router() """ self.router = neutron_utils.create_router( self.neutron, self.os_creds, self.net_config.router_settings) @@ -334,8 +531,7 @@ class NeutronUtilsRouterTests(OSComponentTestCase): def test_create_router_with_public_interface(self): """ - Tests the neutron_utils.create_neutron_net() function when an external - gateway is requested + Tests the neutron_utils.create_router() function with a pubic interface """ subnet_setting = self.net_config.network_settings.subnet_settings[0] self.net_config = openstack_tests.OSNetworkConfig( @@ -351,30 +547,7 @@ class NeutronUtilsRouterTests(OSComponentTestCase): ext_net = neutron_utils.get_network( self.neutron, network_name=self.ext_net_name) - self.assertEqual( - self.router.external_gateway_info['network_id'], ext_net.id) - - def test_create_router_empty_name(self): - """ - Tests the neutron_utils.create_neutron_net() function - """ - with self.assertRaises(Exception): - this_router_settings = create_router.RouterSettings(name='') - self.router = neutron_utils.create_router(self.neutron, - self.os_creds, - this_router_settings) - - def test_create_router_null_name(self): - """ - Tests the neutron_utils.create_neutron_subnet() function when the - subnet CIDR value is None - """ - with self.assertRaises(Exception): - this_router_settings = create_router.RouterSettings() - self.router = neutron_utils.create_router(self.neutron, - self.os_creds, - this_router_settings) - validate_router(self.neutron, None, True) + self.assertEqual(self.router.external_network_id, ext_net.id) def test_add_interface_router(self): """ @@ -388,9 +561,6 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, - self.os_creds, self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) @@ -400,9 +570,9 @@ class NeutronUtilsRouterTests(OSComponentTestCase): True) self.interface_router = neutron_utils.add_interface_router( - self.neutron, self.router, self.subnet) + self.neutron, self.router, self.network.subnets[0]) validate_interface_router(self.interface_router, self.router, - self.subnet) + self.network.subnets[0]) def test_add_interface_router_null_router(self): """ @@ -417,15 +587,12 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, - self.os_creds, self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) with self.assertRaises(NeutronException): self.interface_router = neutron_utils.add_interface_router( - self.neutron, self.router, self.subnet) + self.neutron, self.router, self.network.subnets[0]) def test_add_interface_router_null_subnet(self): """ @@ -446,7 +613,31 @@ class NeutronUtilsRouterTests(OSComponentTestCase): with self.assertRaises(NeutronException): self.interface_router = neutron_utils.add_interface_router( - self.neutron, self.router, self.subnet) + self.neutron, self.router, None) + + def test_add_interface_router_missing_subnet(self): + """ + Tests the neutron_utils.add_interface_router() function for an + Exception when the subnet object has been deleted + """ + self.network = neutron_utils.create_network( + self.neutron, self.os_creds, self.net_config.network_settings) + self.assertEqual(self.net_config.network_settings.name, + self.network.name) + self.assertTrue(validate_network( + self.neutron, self.net_config.network_settings.name, True)) + + self.router = neutron_utils.create_router( + self.neutron, self.os_creds, self.net_config.router_settings) + validate_router(self.neutron, self.net_config.router_settings.name, + True) + + for subnet in self.network.subnets: + neutron_utils.delete_subnet(self.neutron, subnet) + + with self.assertRaises(NotFound): + self.interface_router = neutron_utils.add_interface_router( + self.neutron, self.router, self.network.subnets[0]) def test_create_port(self): """ @@ -460,13 +651,11 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, self.os_creds, self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) self.port = neutron_utils.create_port( - self.neutron, self.os_creds, PortSettings( + self.neutron, self.os_creds, PortConfig( name=self.port_name, ip_addrs=[{ 'subnet_name': subnet_setting.name, @@ -486,13 +675,11 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, self.os_creds, self.network) self.assertTrue(validate_subnet(self.neutron, subnet_setting.name, subnet_setting.cidr, True)) self.port = neutron_utils.create_port( - self.neutron, self.os_creds, PortSettings( + self.neutron, self.os_creds, PortConfig( name=self.port_name, network_name=self.net_config.network_settings.name, ip_addrs=[{ @@ -512,15 +699,12 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, - self.os_creds, self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) self.port = neutron_utils.create_port( self.neutron, self.os_creds, - PortSettings( + PortConfig( network_name=self.net_config.network_settings.name, ip_addrs=[{ 'subnet_name': subnet_setting.name, @@ -537,7 +721,7 @@ class NeutronUtilsRouterTests(OSComponentTestCase): with self.assertRaises(Exception): self.port = neutron_utils.create_port( self.neutron, self.os_creds, - PortSettings( + PortConfig( name=self.port_name, network_name=self.net_config.network_settings.name, ip_addrs=[{ @@ -559,16 +743,13 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, - self.os_creds, self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) with self.assertRaises(Exception): self.port = neutron_utils.create_port( self.neutron, self.os_creds, - PortSettings( + PortConfig( name=self.port_name, network_name=self.net_config.network_settings.name, ip_addrs=[{ @@ -588,15 +769,13 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, self.os_creds, self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) with self.assertRaises(Exception): self.port = neutron_utils.create_port( self.neutron, self.os_creds, - PortSettings( + PortConfig( name=self.port_name, network_name=self.net_config.network_settings.name, ip_addrs=[{ @@ -616,15 +795,13 @@ class NeutronUtilsRouterTests(OSComponentTestCase): self.neutron, self.net_config.network_settings.name, True)) subnet_setting = self.net_config.network_settings.subnet_settings[0] - self.subnet = neutron_utils.create_subnet( - self.neutron, subnet_setting, self.os_creds, self.network) self.assertTrue(validate_subnet( self.neutron, subnet_setting.name, subnet_setting.cidr, True)) with self.assertRaises(Exception): self.port = neutron_utils.create_port( self.neutron, self.os_creds, - PortSettings( + PortConfig( name=self.port_name, network_name=self.net_config.network_settings.name, ip_addrs=[{ @@ -664,7 +841,7 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase): """ Tests the neutron_utils.create_security_group() function """ - sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name) + sec_grp_settings = SecurityGroupConfig(name=self.sec_grp_name) security_group = neutron_utils.create_security_group(self.neutron, self.keystone, sec_grp_settings) @@ -684,13 +861,13 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase): def test_create_sec_grp_no_name(self): """ - Tests the SecurityGroupSettings constructor and + Tests the SecurityGroupConfig constructor and neutron_utils.create_security_group() function to ensure that attempting to create a security group without a name will raise an exception """ with self.assertRaises(Exception): - sec_grp_settings = SecurityGroupSettings() + sec_grp_settings = SecurityGroupConfig() self.security_groups.append( neutron_utils.create_security_group(self.neutron, self.keystone, @@ -700,8 +877,8 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase): """ Tests the neutron_utils.create_security_group() function """ - sec_grp_settings = SecurityGroupSettings(name=self.sec_grp_name, - description='hello group') + sec_grp_settings = SecurityGroupConfig( + name=self.sec_grp_name, description='hello group') self.security_groups.append( neutron_utils.create_security_group(self.neutron, self.keystone, sec_grp_settings)) @@ -719,9 +896,9 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase): Tests the neutron_utils.create_security_group() function """ - sec_grp_rule_settings = SecurityGroupRuleSettings( + sec_grp_rule_settings = SecurityGroupRuleConfig( sec_grp_name=self.sec_grp_name, direction=Direction.ingress) - sec_grp_settings = SecurityGroupSettings( + sec_grp_settings = SecurityGroupConfig( name=self.sec_grp_name, description='hello group', rule_settings=[sec_grp_rule_settings]) @@ -762,12 +939,12 @@ class NeutronUtilsSecurityGroupTests(OSComponentTestCase): self.security_groups.append(neutron_utils.create_security_group( self.neutron, self.keystone, - SecurityGroupSettings(name=self.sec_grp_name + '-1', - description='hello group'))) + SecurityGroupConfig( + name=self.sec_grp_name + '-1', description='hello group'))) self.security_groups.append(neutron_utils.create_security_group( self.neutron, self.keystone, - SecurityGroupSettings(name=self.sec_grp_name + '-2', - description='hello group'))) + SecurityGroupConfig( + name=self.sec_grp_name + '-2', description='hello group'))) sec_grp_1b = neutron_utils.get_security_group_by_id( self.neutron, self.security_groups[0].id) diff --git a/snaps/openstack/utils/tests/nova_utils_tests.py b/snaps/openstack/utils/tests/nova_utils_tests.py index c5b29b5..8cb0812 100644 --- a/snaps/openstack/utils/tests/nova_utils_tests.py +++ b/snaps/openstack/utils/tests/nova_utils_tests.py @@ -13,20 +13,26 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +import time import uuid import os -import time from snaps import file_utils +from snaps.config.flavor import FlavorConfig +from snaps.config.network import PortConfig +from snaps.config.vm_inst import VmInstanceConfig +from snaps.config.volume import VolumeConfig from snaps.openstack import create_instance -from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor +from snaps.openstack.create_flavor import OpenStackFlavor from snaps.openstack.create_image import OpenStackImage -from snaps.openstack.create_instance import VmInstanceSettings -from snaps.openstack.create_network import OpenStackNetwork, PortSettings +from snaps.openstack.create_instance import OpenStackVmInstance +from snaps.openstack.create_network import OpenStackNetwork +from snaps.openstack.create_volume import OpenStackVolume from snaps.openstack.tests import openstack_tests from snaps.openstack.tests.os_source_file_test import OSComponentTestCase -from snaps.openstack.utils import nova_utils, neutron_utils, glance_utils +from snaps.openstack.utils import ( + nova_utils, neutron_utils, glance_utils, cinder_utils) __author__ = 'spisarski' @@ -47,6 +53,16 @@ class NovaSmokeTests(OSComponentTestCase): # This should not throw an exception nova.flavors.list() + def test_nova_get_hypervisor_hosts(self): + """ + Tests to ensure that get_hypervisors() function works. + """ + nova = nova_utils.nova_client(self.os_creds) + + hosts = nova_utils.get_hypervisor_hosts(nova) + # This should not throw an exception + self.assertGreaterEqual(len(hosts), 1) + def test_nova_connect_fail(self): """ Tests to ensure that the improper credentials cannot connect. @@ -156,11 +172,9 @@ class NovaUtilsFlavorTests(OSComponentTestCase): and creating an OS image file within OpenStack """ guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) - self.flavor_settings = FlavorSettings(name=guid + '-name', - flavor_id=guid + '-id', ram=1, - disk=1, vcpus=1, - ephemeral=1, swap=2, - rxtx_factor=3.0, is_public=False) + self.flavor_settings = FlavorConfig( + name=guid + '-name', flavor_id=guid + '-id', ram=1, disk=1, + vcpus=1, ephemeral=1, swap=2, rxtx_factor=3.0, is_public=False) self.nova = nova_utils.nova_client(self.os_creds) self.flavor = None @@ -252,16 +266,16 @@ class NovaUtilsInstanceTests(OSComponentTestCase): self.flavor_creator = OpenStackFlavor( self.os_creds, - FlavorSettings( + FlavorConfig( name=guid + '-flavor-name', ram=256, disk=10, vcpus=1)) self.flavor_creator.create() - port_settings = PortSettings(name=guid + '-port', - network_name=network_settings.name) + port_settings = PortConfig( + name=guid + '-port', network_name=network_settings.name) self.port = neutron_utils.create_port( self.neutron, self.os_creds, port_settings) - self.instance_settings = VmInstanceSettings( + self.instance_settings = VmInstanceConfig( name=guid + '-vm_inst', flavor=self.flavor_creator.flavor_settings.name, port_settings=[port_settings]) @@ -325,7 +339,148 @@ class NovaUtilsInstanceTests(OSComponentTestCase): iters += 1 self.assertTrue(active) - vm_inst = nova_utils.get_latest_server_object(self.nova, self.vm_inst) + vm_inst = nova_utils.get_latest_server_object( + self.nova, self.neutron, self.vm_inst) self.assertEqual(self.vm_inst.name, vm_inst.name) self.assertEqual(self.vm_inst.id, vm_inst.id) + + +class NovaUtilsInstanceVolumeTests(OSComponentTestCase): + """ + Tests the creation of VM instances via nova_utils.py + """ + + def setUp(self): + """ + Setup objects required by VM instances + :return: + """ + + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + + self.nova = nova_utils.nova_client(self.os_creds) + self.cinder = cinder_utils.cinder_client(self.os_creds) + + self.image_creator = None + self.network_creator = None + self.flavor_creator = None + self.volume_creator = None + self.instance_creator = None + + try: + image_settings = openstack_tests.cirros_image_settings( + name=guid + '-image', image_metadata=self.image_metadata) + self.image_creator = OpenStackImage( + self.os_creds, image_settings=image_settings) + self.image_creator.create() + + network_settings = openstack_tests.get_priv_net_config( + guid + '-net', guid + '-subnet').network_settings + self.network_creator = OpenStackNetwork( + self.os_creds, network_settings) + self.network_creator.create() + + self.flavor_creator = OpenStackFlavor( + self.os_creds, + FlavorConfig( + name=guid + '-flavor-name', ram=256, disk=10, vcpus=1)) + self.flavor_creator.create() + + # Create Volume + volume_settings = VolumeConfig( + name=self.__class__.__name__ + '-' + str(guid)) + self.volume_creator = OpenStackVolume( + self.os_creds, volume_settings) + self.volume_creator.create(block=True) + + port_settings = PortConfig( + name=guid + '-port', network_name=network_settings.name) + instance_settings = VmInstanceConfig( + name=guid + '-vm_inst', + flavor=self.flavor_creator.flavor_settings.name, + port_settings=[port_settings]) + self.instance_creator = OpenStackVmInstance( + self.os_creds, instance_settings, image_settings) + self.instance_creator.create(block=True) + except: + self.tearDown() + raise + + def tearDown(self): + """ + Cleanup deployed resources + :return: + """ + if self.instance_creator: + try: + self.instance_creator.clean() + except: + pass + if self.volume_creator: + try: + self.volume_creator.clean() + except: + pass + if self.flavor_creator: + try: + self.flavor_creator.clean() + except: + pass + if self.network_creator: + try: + self.network_creator.clean() + except: + pass + if self.image_creator: + try: + self.image_creator.clean() + except: + pass + + def test_add_remove_volume(self): + """ + Tests the nova_utils.create_server() method + :return: + """ + + self.assertIsNotNone(self.volume_creator.get_volume()) + self.assertEqual(0, len(self.volume_creator.get_volume().attachments)) + + # Attach volume to VM + neutron = neutron_utils.neutron_client(self.os_creds) + nova_utils.attach_volume( + self.nova, neutron, self.instance_creator.get_vm_inst(), + self.volume_creator.get_volume()) + + time.sleep(10) + + vol_attach = cinder_utils.get_volume_by_id( + self.cinder, self.volume_creator.get_volume().id) + vm_attach = nova_utils.get_server_object_by_id( + self.nova, neutron, self.instance_creator.get_vm_inst().id) + + # Detach volume to VM + nova_utils.detach_volume( + self.nova, neutron, self.instance_creator.get_vm_inst(), + self.volume_creator.get_volume()) + + time.sleep(10) + + vol_detach = cinder_utils.get_volume_by_id( + self.cinder, self.volume_creator.get_volume().id) + vm_detach = nova_utils.get_server_object_by_id( + self.nova, neutron, self.instance_creator.get_vm_inst().id) + + # Validate Attachment + self.assertIsNotNone(vol_attach) + self.assertEqual(self.volume_creator.get_volume().id, vol_attach.id) + self.assertEqual(1, len(vol_attach.attachments)) + self.assertEqual(vm_attach.volume_ids[0]['id'], + vol_attach.attachments[0]['volume_id']) + + # Validate Detachment + self.assertIsNotNone(vol_detach) + self.assertEqual(self.volume_creator.get_volume().id, vol_detach.id) + self.assertEqual(0, len(vol_detach.attachments)) + self.assertEqual(0, len(vm_detach.volume_ids)) diff --git a/snaps/openstack/utils/tests/settings_utils_tests.py b/snaps/openstack/utils/tests/settings_utils_tests.py index f84e6a0..cbd78d8 100644 --- a/snaps/openstack/utils/tests/settings_utils_tests.py +++ b/snaps/openstack/utils/tests/settings_utils_tests.py @@ -13,17 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +import unittest + import os import uuid +from snaps.config.network import SubnetConfig, NetworkConfig, PortConfig +from snaps.config.flavor import FlavorConfig +from snaps.config.keypair import KeypairConfig +from snaps.config.qos import Consumer +from snaps.config.security_group import ( + SecurityGroupRuleConfig, Direction, Protocol, SecurityGroupConfig) +from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig +from snaps.domain.flavor import Flavor +from snaps.domain.volume import ( + Volume, VolumeType, VolumeTypeEncryption, QoSSpec) from snaps.openstack import ( create_image, create_network, create_router, create_flavor, create_keypairs, create_instance) -from snaps.openstack.create_network import ( - NetworkSettings, OpenStackNetwork, SubnetSettings) -from snaps.openstack.create_security_group import ( - SecurityGroupRuleSettings, Direction, Protocol, OpenStackSecurityGroup, - SecurityGroupSettings) +from snaps.openstack.create_qos import Consumer +from snaps.openstack.create_network import OpenStackNetwork +from snaps.openstack.create_security_group import OpenStackSecurityGroup from snaps.openstack.tests import openstack_tests from snaps.openstack.tests.os_source_file_test import OSComponentTestCase from snaps.openstack.utils import ( @@ -36,7 +46,7 @@ logger = logging.getLogger('nova_utils_tests') class SettingsUtilsNetworkingTests(OSComponentTestCase): """ - Tests the ability to reverse engineer NetworkSettings objects from existing + Tests the ability to reverse engineer NetworkConfig objects from existing networks deployed to OpenStack """ @@ -62,16 +72,16 @@ class SettingsUtilsNetworkingTests(OSComponentTestCase): def test_derive_net_settings_no_subnet(self): """ - Validates the utility function settings_utils#create_network_settings - returns an acceptable NetworkSettings object and ensures that the + Validates the utility function settings_utils#create_network_config + returns an acceptable NetworkConfig object and ensures that the new settings object will not cause the new OpenStackNetwork instance to create another network """ - net_settings = NetworkSettings(name=self.network_name) + net_settings = NetworkConfig(name=self.network_name) self.net_creator = OpenStackNetwork(self.os_creds, net_settings) network = self.net_creator.create() - derived_settings = settings_utils.create_network_settings( + derived_settings = settings_utils.create_network_config( self.neutron, network) self.assertIsNotNone(derived_settings) @@ -89,18 +99,18 @@ class SettingsUtilsNetworkingTests(OSComponentTestCase): def test_derive_net_settings_two_subnets(self): """ - Validates the utility function settings_utils#create_network_settings - returns an acceptable NetworkSettings object + Validates the utility function settings_utils#create_network_config + returns an acceptable NetworkConfig object """ subnet_settings = list() - subnet_settings.append(SubnetSettings(name='sub1', cidr='10.0.0.0/24')) - subnet_settings.append(SubnetSettings(name='sub2', cidr='10.0.1.0/24')) - net_settings = NetworkSettings(name=self.network_name, - subnet_settings=subnet_settings) + subnet_settings.append(SubnetConfig(name='sub1', cidr='10.0.0.0/24')) + subnet_settings.append(SubnetConfig(name='sub2', cidr='10.0.1.0/24')) + net_settings = NetworkConfig( + name=self.network_name, subnet_settings=subnet_settings) self.net_creator = OpenStackNetwork(self.os_creds, net_settings) network = self.net_creator.create() - derived_settings = settings_utils.create_network_settings( + derived_settings = settings_utils.create_network_config( self.neutron, network) self.assertIsNotNone(derived_settings) @@ -135,7 +145,7 @@ class SettingsUtilsNetworkingTests(OSComponentTestCase): class SettingsUtilsVmInstTests(OSComponentTestCase): """ - Tests the ability to reverse engineer VmInstanceSettings objects from + Tests the ability to reverse engineer VmInstanceConfig objects from existing VMs/servers deployed to OpenStack """ @@ -144,8 +154,6 @@ class SettingsUtilsVmInstTests(OSComponentTestCase): Instantiates the CreateImage object that is responsible for downloading and creating an OS image file within OpenStack """ - # super(self.__class__, self).__start__() - self.nova = nova_utils.nova_client(self.os_creds) self.glance = glance_utils.glance_client(self.os_creds) self.neutron = neutron_utils.neutron_client(self.os_creds) @@ -196,13 +204,13 @@ class SettingsUtilsVmInstTests(OSComponentTestCase): # Create Flavor self.flavor_creator = create_flavor.OpenStackFlavor( self.os_creds, - create_flavor.FlavorSettings(name=guid + '-flavor-name', - ram=256, disk=1, vcpus=1)) + FlavorConfig( + name=guid + '-flavor-name', ram=256, disk=1, vcpus=1)) self.flavor_creator.create() # Create Key/Pair self.keypair_creator = create_keypairs.OpenStackKeypair( - self.os_creds, create_keypairs.KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.keypair_pub_filepath, private_filepath=self.keypair_priv_filepath)) @@ -210,32 +218,30 @@ class SettingsUtilsVmInstTests(OSComponentTestCase): # Create Security Group sec_grp_name = guid + '-sec-grp' - rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.icmp) - rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.tcp, - port_range_min=22, - port_range_max=22) + rule1 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.icmp) + rule2 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.tcp, port_range_min=22, port_range_max=22) self.sec_grp_creator = OpenStackSecurityGroup( self.os_creds, - SecurityGroupSettings(name=sec_grp_name, - rule_settings=[rule1, rule2])) + SecurityGroupConfig( + name=sec_grp_name, rule_settings=[rule1, rule2])) self.sec_grp_creator.create() # Create instance ports_settings = list() ports_settings.append( - create_network.PortSettings( + PortConfig( name=self.port_1_name, network_name=self.pub_net_config.network_settings.name)) - instance_settings = create_instance.VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=ports_settings, - floating_ip_settings=[create_instance.FloatingIpSettings( + floating_ip_settings=[FloatingIpConfig( name=self.floating_ip_name, port_name=self.port_1_name, router_name=self.pub_net_config.router_settings.name)]) @@ -310,16 +316,17 @@ class SettingsUtilsVmInstTests(OSComponentTestCase): # super(self.__class__, self).__clean__() - def test_derive_vm_inst_settings(self): + def test_derive_vm_inst_config(self): """ - Validates the utility function settings_utils#create_vm_inst_settings - returns an acceptable VmInstanceSettings object + Validates the utility function settings_utils#create_vm_inst_config + returns an acceptable VmInstanceConfig object """ self.inst_creator.create(block=True) server = nova_utils.get_server( - self.nova, vm_inst_settings=self.inst_creator.instance_settings) - derived_vm_settings = settings_utils.create_vm_inst_settings( + self.nova, self.neutron, + vm_inst_settings=self.inst_creator.instance_settings) + derived_vm_settings = settings_utils.create_vm_inst_config( self.nova, self.neutron, server) self.assertIsNotNone(derived_vm_settings) self.assertIsNotNone(derived_vm_settings.port_settings) @@ -328,14 +335,72 @@ class SettingsUtilsVmInstTests(OSComponentTestCase): def test_derive_image_settings(self): """ Validates the utility function settings_utils#create_image_settings - returns an acceptable ImageSettings object + returns an acceptable ImageConfig object """ self.inst_creator.create(block=True) server = nova_utils.get_server( - self.nova, vm_inst_settings=self.inst_creator.instance_settings) - derived_image_settings = settings_utils.determine_image_settings( + self.nova, self.neutron, + vm_inst_settings=self.inst_creator.instance_settings) + derived_image_settings = settings_utils.determine_image_config( self.glance, server, [self.image_creator.image_settings]) self.assertIsNotNone(derived_image_settings) self.assertEqual(self.image_creator.image_settings.name, derived_image_settings.name) + + +class SettingsUtilsUnitTests(unittest.TestCase): + """ + Exercises the settings_utils.py functions around volumes + """ + + def test_vol_settings_from_vol(self): + volume = Volume( + name='vol-name', volume_id='vol-id', description='desc', size=99, + vol_type='vol-type', availability_zone='zone1', multi_attach=True) + settings = settings_utils.create_volume_config(volume) + self.assertEqual(volume.name, settings.name) + self.assertEqual(volume.description, settings.description) + self.assertEqual(volume.size, settings.size) + self.assertEqual(volume.type, settings.type_name) + self.assertEqual(volume.availability_zone, settings.availability_zone) + self.assertEqual(volume.multi_attach, settings.multi_attach) + + def test_vol_type_settings_from_vol(self): + encryption = VolumeTypeEncryption( + volume_encryption_id='vol-encrypt-id', volume_type_id='vol-typ-id', + control_location='front-end', provider='FooClass', cipher='1', + key_size=1) + qos_spec = QoSSpec(name='qos-spec-name', spec_id='qos-spec-id', + consumer=Consumer.back_end) + volume_type = VolumeType( + name='vol-type-name', volume_type_id='vol-type-id', public=True, + encryption=encryption, qos_spec=qos_spec) + + settings = settings_utils.create_volume_type_config(volume_type) + self.assertEqual(volume_type.name, settings.name) + self.assertEqual(volume_type.public, settings.public) + + encrypt_settings = settings.encryption + self.assertIsNotNone(encrypt_settings) + self.assertEqual(encryption.control_location, + encrypt_settings.control_location.value) + self.assertEqual(encryption.cipher, encrypt_settings.cipher) + self.assertEqual(encryption.key_size, encrypt_settings.key_size) + + self.assertEqual(qos_spec.name, settings.qos_spec_name) + + def test_flavor_settings_from_flavor(self): + flavor = Flavor( + name='flavor-name', flavor_id='flavor-id', ram=99, disk=101, + vcpus=9, ephemeral=3, swap=5, rxtx_factor=7, is_public=False) + settings = settings_utils.create_flavor_config(flavor) + self.assertEqual(flavor.name, settings.name) + self.assertEqual(flavor.id, settings.flavor_id) + self.assertEqual(flavor.ram, settings.ram) + self.assertEqual(flavor.disk, settings.disk) + self.assertEqual(flavor.vcpus, settings.vcpus) + self.assertEqual(flavor.ephemeral, settings.ephemeral) + self.assertEqual(flavor.swap, settings.swap) + self.assertEqual(flavor.rxtx_factor, settings.rxtx_factor) + self.assertEqual(flavor.is_public, settings.is_public) diff --git a/snaps/playbook_runner.py b/snaps/playbook_runner.py index 4dba550..87321f5 100644 --- a/snaps/playbook_runner.py +++ b/snaps/playbook_runner.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import argparse +import ast import logging import re @@ -45,9 +46,15 @@ def main(parsed_args): if ssh: ssh.close() + vars = dict() + if args.vars: + vars = ast.literal_eval(args.vars) + if not isinstance(vars, dict): + vars = dict() + retval = ansible_utils.apply_playbook( parsed_args.playbook, [parsed_args.ip_addr], parsed_args.host_user, - parsed_args.priv_key, variables={'name': 'Foo'}, + parsed_args.priv_key, variables=vars, proxy_setting=proxy_settings) exit(retval) @@ -66,6 +73,9 @@ if __name__ == '__main__': required=False, help='<host>:<port>') parser.add_argument('-s', '--ssh-proxy-cmd', dest='ssh_proxy_cmd', required=False) + parser.add_argument('-v', '--vars', dest='vars', + required=False) args = parser.parse_args() main(args) + diff --git a/snaps/provisioning/ansible_pb/__init__.py b/snaps/provisioning/ansible_pb/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/snaps/provisioning/ansible_pb/__init__.py +++ /dev/null diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/__init__.py b/snaps/provisioning/ansible_pb/centos-network-setup/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/snaps/provisioning/ansible_pb/centos-network-setup/__init__.py +++ /dev/null diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/__init__.py b/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/snaps/provisioning/ansible_pb/centos-network-setup/playbooks/__init__.py +++ /dev/null diff --git a/snaps/provisioning/ansible_pb/centos-network-setup/templates/ifcfg-interface b/snaps/provisioning/ansible_pb/centos-network-setup/templates/ifcfg-interface deleted file mode 100644 index 47aa3fa..0000000 --- a/snaps/provisioning/ansible_pb/centos-network-setup/templates/ifcfg-interface +++ /dev/null @@ -1,14 +0,0 @@ -DEVICE={{ nic_name }} -NAME={{ nic_name }} -IPADDR={{ nic_ip }} - -DEFROUTE=no -NETMASK=255.255.255.0 -NM_CONTROLLED=no -IPV6INIT=yes -IPV6_AUTOCONF=yes -IPV6_DEFROUTE=yes -IPV6_PEERDNS=yes -IPV6_PEERROUTES=yes -IPV6_FAILURE_FATAL=no -ONBOOT=yes
\ No newline at end of file diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/__init__.py b/snaps/provisioning/ansible_pb/ubuntu-network-setup/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/__init__.py +++ /dev/null diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/__init__.py b/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/playbooks/__init__.py +++ /dev/null diff --git a/snaps/provisioning/ansible_pb/ubuntu-network-setup/templates/ethN.cfg b/snaps/provisioning/ansible_pb/ubuntu-network-setup/templates/ethN.cfg deleted file mode 100644 index 3fa7708..0000000 --- a/snaps/provisioning/ansible_pb/ubuntu-network-setup/templates/ethN.cfg +++ /dev/null @@ -1,2 +0,0 @@ -auto {{ nic_name }} -iface {{ nic_name }} inet dhcp diff --git a/snaps/provisioning/tests/ansible_utils_tests.py b/snaps/provisioning/tests/ansible_utils_tests.py index da056b2..7600002 100644 --- a/snaps/provisioning/tests/ansible_utils_tests.py +++ b/snaps/provisioning/tests/ansible_utils_tests.py @@ -18,15 +18,21 @@ import uuid import os import pkg_resources from scp import SCPClient + +from snaps.config.flavor import FlavorConfig +from snaps.config.keypair import KeypairConfig +from snaps.config.network import PortConfig +from snaps.config.security_group import ( + Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig) +from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig + from snaps.openstack import create_flavor from snaps.openstack import create_image from snaps.openstack import create_instance from snaps.openstack import create_keypairs from snaps.openstack import create_network from snaps.openstack import create_router -from snaps.openstack.create_security_group import ( - SecurityGroupRuleSettings, Direction, Protocol, OpenStackSecurityGroup, - SecurityGroupSettings) +from snaps.openstack.create_security_group import OpenStackSecurityGroup from snaps.openstack.tests import openstack_tests from snaps.openstack.tests.create_instance_tests import check_dhcp_lease from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase @@ -100,14 +106,14 @@ class AnsibleProvisioningTests(OSIntegrationTestCase): # Create Flavor self.flavor_creator = create_flavor.OpenStackFlavor( self.admin_os_creds, - create_flavor.FlavorSettings(name=guid + '-flavor-name', - ram=2048, disk=10, vcpus=2, - metadata=self.flavor_metadata)) + FlavorConfig( + name=guid + '-flavor-name', ram=2048, disk=10, vcpus=2, + metadata=self.flavor_metadata)) self.flavor_creator.create() # Create Key/Pair self.keypair_creator = create_keypairs.OpenStackKeypair( - self.os_creds, create_keypairs.KeypairSettings( + self.os_creds, KeypairConfig( name=self.keypair_name, public_filepath=self.keypair_pub_filepath, private_filepath=self.keypair_priv_filepath)) @@ -115,32 +121,30 @@ class AnsibleProvisioningTests(OSIntegrationTestCase): # Create Security Group sec_grp_name = guid + '-sec-grp' - rule1 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.icmp) - rule2 = SecurityGroupRuleSettings(sec_grp_name=sec_grp_name, - direction=Direction.ingress, - protocol=Protocol.tcp, - port_range_min=22, - port_range_max=22) + rule1 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.icmp) + rule2 = SecurityGroupRuleConfig( + sec_grp_name=sec_grp_name, direction=Direction.ingress, + protocol=Protocol.tcp, port_range_min=22, port_range_max=22) self.sec_grp_creator = OpenStackSecurityGroup( self.os_creds, - SecurityGroupSettings(name=sec_grp_name, - rule_settings=[rule1, rule2])) + SecurityGroupConfig( + name=sec_grp_name, rule_settings=[rule1, rule2])) self.sec_grp_creator.create() # Create instance ports_settings = list() ports_settings.append( - create_network.PortSettings( + PortConfig( name=self.port_1_name, network_name=self.pub_net_config.network_settings.name)) - instance_settings = create_instance.VmInstanceSettings( + instance_settings = VmInstanceConfig( name=self.vm_inst_name, flavor=self.flavor_creator.flavor_settings.name, port_settings=ports_settings, - floating_ip_settings=[create_instance.FloatingIpSettings( + floating_ip_settings=[FloatingIpConfig( name=self.floating_ip_name, port_name=self.port_1_name, router_name=self.pub_net_config.router_settings.name)]) @@ -237,6 +241,9 @@ class AnsibleProvisioningTests(OSIntegrationTestCase): # Block until VM's ssh port has been opened self.assertTrue(self.inst_creator.vm_ssh_active(block=True)) + # Block until cloud-init has completed + self.assertTrue(self.inst_creator.cloud_init_complete(block=True)) + ssh_client = self.inst_creator.ssh_client() self.assertIsNotNone(ssh_client) @@ -262,12 +269,13 @@ class AnsibleProvisioningTests(OSIntegrationTestCase): ssh = ansible_utils.ssh_client(ip, user, priv_key, self.os_creds.proxy_settings) self.assertIsNotNone(ssh) - + scp = None try: scp = SCPClient(ssh.get_transport()) scp.get('~/hello.txt', self.test_file_local_path) finally: - scp.close() + if scp: + scp.close() ssh.close() self.assertTrue(os.path.isfile(self.test_file_local_path)) @@ -305,6 +313,9 @@ class AnsibleProvisioningTests(OSIntegrationTestCase): # Block until VM's ssh port has been opened self.assertTrue(self.inst_creator.vm_ssh_active(block=True)) + # Block until cloud-init has completed + self.assertTrue(self.inst_creator.cloud_init_complete(block=True)) + # Apply Security Group self.inst_creator.add_security_group( self.sec_grp_creator.get_security_group()) @@ -326,12 +337,14 @@ class AnsibleProvisioningTests(OSIntegrationTestCase): ssh = ansible_utils.ssh_client(ip, user, priv_key, self.os_creds.proxy_settings) self.assertIsNotNone(ssh) + scp = None try: scp = SCPClient(ssh.get_transport()) scp.get('/tmp/hello.txt', self.test_file_local_path) finally: - scp.close() + if scp: + scp.close() ssh.close() self.assertTrue(os.path.isfile(self.test_file_local_path)) diff --git a/snaps/test_suite_builder.py b/snaps/test_suite_builder.py index a1b72aa..7b3ece7 100644 --- a/snaps/test_suite_builder.py +++ b/snaps/test_suite_builder.py @@ -16,6 +16,26 @@ import logging import unittest +from snaps.config.tests.cluster_template_tests import ( + ClusterTemplateConfigUnitTests) +from snaps.config.tests.network_tests import ( + NetworkConfigUnitTests, SubnetConfigUnitTests, PortConfigUnitTests) +from snaps.config.tests.security_group_tests import ( + SecurityGroupConfigUnitTests, SecurityGroupRuleConfigUnitTests) +from snaps.config.tests.vm_inst_tests import ( + VmInstanceConfigUnitTests, FloatingIpConfigUnitTests) +from snaps.config.tests.volume_tests import VolumeConfigUnitTests +from snaps.config.tests.volume_type_tests import VolumeTypeConfigUnitTests +from snaps.config.tests.qos_tests import QoSConfigUnitTests +from snaps.config.tests.stack_tests import StackConfigUnitTests +from snaps.config.tests.router_tests import RouterConfigUnitTests +from snaps.config.tests.user_tests import UserConfigUnitTests +from snaps.config.tests.project_tests import ProjectConfigUnitTests +from snaps.config.tests.keypair_tests import KeypairConfigUnitTests +from snaps.config.tests.flavor_tests import FlavorConfigUnitTests +import snaps.config.tests.image_tests as image_tests +import snaps.openstack.tests.create_image_tests as creator_tests +from snaps.domain.test.cluster_template_tests import ClusterTemplateUnitTests from snaps.domain.test.flavor_tests import FlavorDomainObjectTests from snaps.domain.test.image_tests import ImageDomainObjectTests from snaps.domain.test.keypair_tests import KeypairDomainObjectTests @@ -34,26 +54,29 @@ from snaps.domain.test.vm_inst_tests import ( VmInstDomainObjectTests, FloatingIpDomainObjectTests) from snaps.domain.test.volume_tests import ( QoSSpecDomainObjectTests, VolumeTypeDomainObjectTests, - VolumeTypeEncryptionObjectTests) + VolumeTypeEncryptionObjectTests, VolumeDomainObjectTests) +from snaps.openstack.tests.cluster_template_tests import ( + CreateClusterTemplateTests) from snaps.openstack.tests.conf.os_credentials_tests import ( ProxySettingsUnitTests, OSCredsUnitTests) from snaps.openstack.tests.create_flavor_tests import ( CreateFlavorTests, FlavorSettingsUnitTests) from snaps.openstack.tests.create_image_tests import ( - CreateImageSuccessTests, CreateImageNegativeTests, ImageSettingsUnitTests, + CreateImageSuccessTests, CreateImageNegativeTests, CreateMultiPartImageTests) from snaps.openstack.tests.create_instance_tests import ( - CreateInstanceSingleNetworkTests, CreateInstancePubPrivNetTests, - CreateInstanceOnComputeHost, CreateInstanceSimpleTests, - FloatingIpSettingsUnitTests, InstanceSecurityGroupTests, - VmInstanceSettingsUnitTests, CreateInstancePortManipulationTests, - SimpleHealthCheck, CreateInstanceFromThreePartImage, - CreateInstanceMockOfflineTests, CreateInstanceTwoNetTests) + CreateInstanceSingleNetworkTests, CreateInstanceOnComputeHost, + CreateInstanceSimpleTests, FloatingIpSettingsUnitTests, + InstanceSecurityGroupTests, VmInstanceSettingsUnitTests, + CreateInstancePortManipulationTests, SimpleHealthCheck, + CreateInstanceFromThreePartImage, CreateInstanceMockOfflineTests, + CreateInstanceTwoNetTests, CreateInstanceVolumeTests, + CreateInstanceIPv6NetworkTests) from snaps.openstack.tests.create_keypairs_tests import ( CreateKeypairsTests, KeypairSettingsUnitTests, CreateKeypairsCleanupTests) from snaps.openstack.tests.create_network_tests import ( CreateNetworkSuccessTests, NetworkSettingsUnitTests, PortSettingsUnitTests, - SubnetSettingsUnitTests, CreateNetworkTypeTests) + SubnetSettingsUnitTests, CreateNetworkTypeTests, CreateNetworkIPv6Tests) from snaps.openstack.tests.create_project_tests import ( CreateProjectSuccessTests, ProjectSettingsUnitTests, CreateProjectUserTests) @@ -67,9 +90,15 @@ from snaps.openstack.tests.create_security_group_tests import ( SecurityGroupSettingsUnitTests) from snaps.openstack.tests.create_stack_tests import ( StackSettingsUnitTests, CreateStackSuccessTests, CreateStackNegativeTests, - CreateComplexStackTests) + CreateStackFlavorTests, CreateStackFloatingIpTests, + CreateStackNestedResourceTests, CreateStackKeypairTests, + CreateStackVolumeTests, CreateStackSecurityGroupTests) from snaps.openstack.tests.create_user_tests import ( UserSettingsUnitTests, CreateUserSuccessTests) +from snaps.openstack.tests.create_volume_tests import ( + VolumeSettingsUnitTests, CreateSimpleVolumeSuccessTests, + CreateVolumeWithTypeTests, CreateVolumeWithImageTests, + CreateSimpleVolumeFailureTests) from snaps.openstack.tests.create_volume_type_tests import ( VolumeTypeSettingsUnitTests, CreateSimpleVolumeTypeSuccessTests, CreateVolumeTypeComplexTests) @@ -77,21 +106,27 @@ from snaps.openstack.tests.os_source_file_test import ( OSComponentTestCase, OSIntegrationTestCase) from snaps.openstack.utils.tests.cinder_utils_tests import ( CinderSmokeTests, CinderUtilsQoSTests, CinderUtilsSimpleVolumeTypeTests, - CinderUtilsAddEncryptionTests, CinderUtilsVolumeTypeCompleteTests) + CinderUtilsAddEncryptionTests, CinderUtilsVolumeTypeCompleteTests, + CinderUtilsVolumeTests) from snaps.openstack.utils.tests.glance_utils_tests import ( GlanceSmokeTests, GlanceUtilsTests) from snaps.openstack.utils.tests.heat_utils_tests import ( HeatSmokeTests, HeatUtilsCreateSimpleStackTests, - HeatUtilsCreateComplexStackTests) + HeatUtilsCreateComplexStackTests, HeatUtilsFlavorTests, + HeatUtilsKeypairTests, HeatUtilsVolumeTests, HeatUtilsSecurityGroupTests) from snaps.openstack.utils.tests.keystone_utils_tests import ( KeystoneSmokeTests, KeystoneUtilsTests) from snaps.openstack.utils.tests.neutron_utils_tests import ( NeutronSmokeTests, NeutronUtilsNetworkTests, NeutronUtilsSubnetTests, NeutronUtilsRouterTests, NeutronUtilsSecurityGroupTests, - NeutronUtilsFloatingIpTests) + NeutronUtilsFloatingIpTests, NeutronUtilsIPv6Tests) from snaps.openstack.utils.tests.nova_utils_tests import ( NovaSmokeTests, NovaUtilsKeypairTests, NovaUtilsFlavorTests, - NovaUtilsInstanceTests) + NovaUtilsInstanceTests, NovaUtilsInstanceVolumeTests) +from snaps.openstack.utils.tests.settings_utils_tests import ( + SettingsUtilsUnitTests) +from snaps.openstack.utils.tests.magnum_utils_tests import ( + MagnumSmokeTests, MagnumUtilsClusterTypeTests) from snaps.provisioning.tests.ansible_utils_tests import ( AnsibleProvisioningTests) from snaps.tests.file_utils_tests import FileUtilsTests @@ -107,34 +142,48 @@ def add_unit_tests(suite): """ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(FileUtilsTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( - SecurityGroupRuleSettingsUnitTests)) - suite.addTest(unittest.TestLoader().loadTestsFromTestCase( ProxySettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( OSCredsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + SecurityGroupConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( SecurityGroupSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + SecurityGroupRuleConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + SecurityGroupRuleSettingsUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( SecurityGroupDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( SecurityGroupRuleDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( - ImageSettingsUnitTests)) + image_tests.ImageConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + creator_tests.ImageSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( ImageDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + FlavorConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( FlavorSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( FlavorDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + KeypairConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( KeypairSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( KeypairDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + UserConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( UserSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( UserDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + ProjectConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( ProjectSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( ProjectDomainObjectTests)) @@ -147,47 +196,77 @@ def add_unit_tests(suite): suite.addTest(unittest.TestLoader().loadTestsFromTestCase( RoleDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + NetworkConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( NetworkSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( NetworkObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + SubnetConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( SubnetSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( SubnetObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + PortConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( PortSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( PortDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + RouterConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( RouterSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( RouterDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( InterfaceRouterDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + FloatingIpConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( FloatingIpSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + VmInstanceConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( VmInstanceSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( StackDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( ResourceDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + StackConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( StackSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( VolumeTypeDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( VolumeTypeEncryptionObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + VolumeDomainObjectTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( QoSSpecDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( VmInstDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( FloatingIpDomainObjectTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + QoSConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( QoSSettingsUnitTests)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + VolumeTypeConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( VolumeTypeSettingsUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + VolumeConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + VolumeSettingsUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + ClusterTemplateConfigUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + ClusterTemplateUnitTests)) + suite.addTest(unittest.TestLoader().loadTestsFromTestCase( + SettingsUtilsUnitTests)) def add_openstack_client_tests(suite, os_creds, ext_net_name, @@ -279,6 +358,9 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True, NeutronUtilsSubnetTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level)) suite.addTest(OSComponentTestCase.parameterize( + NeutronUtilsIPv6Tests, os_creds=os_creds, ext_net_name=ext_net_name, + log_level=log_level)) + suite.addTest(OSComponentTestCase.parameterize( NeutronUtilsRouterTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level)) suite.addTest(OSComponentTestCase.parameterize( @@ -297,6 +379,10 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True, NovaUtilsInstanceTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level, image_metadata=image_metadata)) suite.addTest(OSComponentTestCase.parameterize( + NovaUtilsInstanceVolumeTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level, + image_metadata=image_metadata)) + suite.addTest(OSComponentTestCase.parameterize( CreateFlavorTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level)) suite.addTest(OSComponentTestCase.parameterize( @@ -308,10 +394,30 @@ def add_openstack_api_tests(suite, os_creds, ext_net_name, use_keystone=True, ext_net_name=ext_net_name, log_level=log_level, image_metadata=image_metadata)) suite.addTest(OSComponentTestCase.parameterize( + HeatUtilsFlavorTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level, + image_metadata=image_metadata)) + suite.addTest(OSComponentTestCase.parameterize( + HeatUtilsKeypairTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level, + image_metadata=image_metadata)) + suite.addTest(OSComponentTestCase.parameterize( + HeatUtilsSecurityGroupTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level, + image_metadata=image_metadata)) + suite.addTest(OSComponentTestCase.parameterize( + HeatUtilsVolumeTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level, + image_metadata=image_metadata)) + suite.addTest(OSComponentTestCase.parameterize( CinderUtilsQoSTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level, image_metadata=image_metadata)) suite.addTest(OSComponentTestCase.parameterize( + CinderUtilsVolumeTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level, + image_metadata=image_metadata)) + suite.addTest(OSComponentTestCase.parameterize( CinderUtilsSimpleVolumeTypeTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level, image_metadata=image_metadata)) @@ -393,6 +499,11 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name, flavor_metadata=flavor_metadata, image_metadata=image_metadata, log_level=log_level)) suite.addTest(OSIntegrationTestCase.parameterize( + CreateNetworkIPv6Tests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( CreateRouterSuccessTests, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone, flavor_metadata=flavor_metadata, image_metadata=image_metadata, @@ -417,6 +528,26 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name, ext_net_name=ext_net_name, use_keystone=use_keystone, flavor_metadata=flavor_metadata, image_metadata=image_metadata, log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateSimpleVolumeSuccessTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateSimpleVolumeFailureTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateVolumeWithTypeTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateVolumeWithImageTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) # VM Instances suite.addTest(OSIntegrationTestCase.parameterize( @@ -455,11 +586,41 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name, flavor_metadata=flavor_metadata, image_metadata=image_metadata, log_level=log_level)) suite.addTest(OSIntegrationTestCase.parameterize( + CreateInstanceVolumeTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateInstanceIPv6NetworkTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( CreateStackSuccessTests, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone, flavor_metadata=flavor_metadata, image_metadata=image_metadata, log_level=log_level)) suite.addTest(OSIntegrationTestCase.parameterize( + CreateStackVolumeTests, os_creds=os_creds, ext_net_name=ext_net_name, + use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateStackFlavorTests, os_creds=os_creds, ext_net_name=ext_net_name, + use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateStackKeypairTests, os_creds=os_creds, ext_net_name=ext_net_name, + use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateStackSecurityGroupTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( CreateStackNegativeTests, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone, flavor_metadata=flavor_metadata, image_metadata=image_metadata, @@ -472,7 +633,12 @@ def add_openstack_integration_tests(suite, os_creds, ext_net_name, flavor_metadata=flavor_metadata, image_metadata=image_metadata, log_level=log_level)) suite.addTest(OSIntegrationTestCase.parameterize( - CreateComplexStackTests, os_creds=os_creds, + CreateStackFloatingIpTests, os_creds=os_creds, + ext_net_name=ext_net_name, use_keystone=use_keystone, + flavor_metadata=flavor_metadata, image_metadata=image_metadata, + log_level=log_level)) + suite.addTest(OSIntegrationTestCase.parameterize( + CreateStackNestedResourceTests, os_creds=os_creds, ext_net_name=ext_net_name, use_keystone=use_keystone, flavor_metadata=flavor_metadata, image_metadata=image_metadata, log_level=log_level)) @@ -556,11 +722,17 @@ def add_openstack_staging_tests(suite, os_creds, ext_net_name, :return: None as the tests will be adding to the 'suite' parameter object """ suite.addTest(OSComponentTestCase.parameterize( - CreateNetworkTypeTests, os_creds=os_creds, ext_net_name=ext_net_name, - log_level=log_level)) + CreateNetworkTypeTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level)) suite.addTest(OSComponentTestCase.parameterize( CreateInstanceMockOfflineTests, os_creds=os_creds, ext_net_name=ext_net_name, log_level=log_level)) - suite.addTest(OSIntegrationTestCase.parameterize( - CreateInstancePubPrivNetTests, os_creds=os_creds, - ext_net_name=ext_net_name, log_level=log_level)) + suite.addTest(OSComponentTestCase.parameterize( + MagnumSmokeTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level)) + suite.addTest(OSComponentTestCase.parameterize( + MagnumUtilsClusterTypeTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level)) + suite.addTest(OSComponentTestCase.parameterize( + CreateClusterTemplateTests, os_creds=os_creds, + ext_net_name=ext_net_name, log_level=log_level)) |