diff --git a/README.md b/README.md index f5e6b722..ff4d207f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If both 'vip' and 'dns-ha' are set as they are mutually exclusive If 'dns-ha' is set and none of the os-{admin,internal,public}-hostname(s) are set -# Network Space support +## Network Space support This charm supports the use of Juju Network Spaces, allowing the charm to be bound to network space configurations managed directly by Juju. This is only supported with Juju 2.0 and above. @@ -66,3 +66,24 @@ alternatively these can also be provided as part of a juju native bundle configu NOTE: Spaces must be configured in the underlying provider prior to attempting to use them. NOTE: Existing deployments using os-*-network configuration options will continue to function; these options are preferred over any network space binding provided if set. + +## Default Quota Configuration + +This charm supports default quota settings for projects. +This feature is only available from Openstack Icehouse and later releases. + +The default quota settings do not overwrite post-deployment CLI quotas set by operators. +Existing projects whose quotas were not modified will adopt the new defaults when a config-changed hook occurs. +Newly created projects will also adopt the defaults set in the charm's config. + +By default, the charm's quota configs are not set and openstack projects have the values below as default: +quota-instances - 10 +quota-cores - 20 +quota-ram - 51200 +quota-metadata_items - 128 +quota-injected_files - 5 +quota-injected_file_content_bytes - 10240 +quota-injected_file_path_length - 255 +quota-key_pairs - 100 +quota-server_groups - 10 (only available after Icehouse) +quota-server_group_members - 10 (only available after Icehouse) diff --git a/config.yaml b/config.yaml index 954674b8..127c88b0 100644 --- a/config.yaml +++ b/config.yaml @@ -449,3 +449,89 @@ options: . Only supported in OpenStack Newton and higher. For deployments prior to Queens this value should be set in the neutron-gateway charm. + quota-instances: + type: int + default: + description: | + The number of instances allowed per project. + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-cores: + type: int + default: + description: | + The number of instance cores or vCPUs allowed per project. + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-ram: + type: int + default: + description: | + The number of megabytes of instance RAM allowed per project. + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-metadata-items: + type: int + default: + description: | + The number of metadata items allowed per instance. + . + Users can associate metadata with an instance during instance creation. + This metadata takes the form of key-value pairs. + . + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-injected-files: + type: int + default: + description: | + The number of injected files allowed. + . + File injection allows users to customize the personality of an instance + by injecting data into it upon boot. + Only text file injection is permitted: binary or ZIP files are not accepted. + During file injection, any existing files that match specified files are + renamed to include .bak extension appended with a timestamp. + . + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-injected-file-size: + type: int + default: + description: | + The number of bytes allowed per injected file. + . + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-injected-path-size: + type: int + default: + description: | + The maximum allowed injected file path length. + . + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-key-pairs: + type: int + default: + description: | + The maximum number of key pairs allowed per user. + . + Users can create at least one key pair for each project and use the key + pair for multiple instances that belong to that project. + . + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-server-groups: + type: int + default: + description: | + The maxiumum number of server groups per project. Not supported in Icehouse + and before + . + Server groups are used to control the affinity and anti-affinity + scheduling policy for a group of servers or instances. Reducing the + quota will not affect any existing group, but new servers will not be + allowed into groups that have become over quota. + . + Possible Values are positive integers or 0 and -1 to disable the quota. + quota-server-group-members: + type: int + default: + description: | + The maximum number of servers per server group. Not supported in Icehouse + and before + . + Possible Values are positive integers or 0 and -1 to disable the quota. diff --git a/hooks/nova_cc_context.py b/hooks/nova_cc_context.py index 36cec4d5..e2bb2e3a 100644 --- a/hooks/nova_cc_context.py +++ b/hooks/nova_cc_context.py @@ -289,6 +289,19 @@ class NovaConfigContext(ch_context.WorkerConfigContext): ctxt['ram_allocation_ratio'] = hookenv.config('ram-allocation-ratio') addr = ch_ip.resolve_address(ch_ip.INTERNAL) ctxt['host_ip'] = ch_network_ip.format_ipv6_addr(addr) or addr + ctxt['quota_instances'] = hookenv.config('quota-instances') + ctxt['quota_cores'] = hookenv.config('quota-cores') + ctxt['quota_ram'] = hookenv.config('quota-ram') + ctxt['quota_metadata_items'] = hookenv.config('quota-metadata-items') + ctxt['quota_injected_files'] = hookenv.config('quota-injected-files') + ctxt['quota_injected_file_content_bytes'] = hookenv.config( + 'quota-injected-file-size') + ctxt['quota_injected_file_path_length'] = hookenv.config( + 'quota-injected-path-size') + ctxt['quota_key_pairs'] = hookenv.config('quota-key-pairs') + ctxt['quota_server_groups'] = hookenv.config('quota-server-groups') + ctxt['quota_server_group_members'] = hookenv.config( + 'quota-server-group-members') return ctxt diff --git a/templates/icehouse/nova.conf b/templates/icehouse/nova.conf index ef8ab9e1..66a78cea 100644 --- a/templates/icehouse/nova.conf +++ b/templates/icehouse/nova.conf @@ -39,6 +39,31 @@ disk_allocation_ratio = {{ disk_allocation_ratio }} use_syslog={{ use_syslog }} my_ip = {{ host_ip }} +{% if quota_instances is not none -%} +quota_instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +quota_cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +quota_ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +quota_metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +quota_injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +quota_injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +quota_injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +quota_key_pairs = {{ quota_key_pairs }} +{% endif -%} + {% if memcached_servers %} memcached_servers = {{ memcached_servers }} {% endif %} diff --git a/templates/juno/nova.conf b/templates/juno/nova.conf index 774181da..66ec1a0d 100644 --- a/templates/juno/nova.conf +++ b/templates/juno/nova.conf @@ -39,6 +39,37 @@ disk_allocation_ratio = {{ disk_allocation_ratio }} use_syslog={{ use_syslog }} my_ip = {{ host_ip }} +{% if quota_instances is not none -%} +quota_instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +quota_cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +quota_ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +quota_metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +quota_injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +quota_injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +quota_injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +quota_key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +quota_server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +quota_server_group_members = {{ quota_server_group_members }} +{% endif -%} + {% if memcached_servers %} memcached_servers = {{ memcached_servers }} {% endif %} diff --git a/templates/kilo/nova.conf b/templates/kilo/nova.conf index 2c60096e..fa7f7058 100644 --- a/templates/kilo/nova.conf +++ b/templates/kilo/nova.conf @@ -48,6 +48,37 @@ disk_allocation_ratio = {{ disk_allocation_ratio }} use_syslog={{ use_syslog }} my_ip = {{ host_ip }} +{% if quota_instances is not none -%} +quota_instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +quota_cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +quota_ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +quota_metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +quota_injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +quota_injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +quota_injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +quota_key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +quota_server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +quota_server_group_members = {{ quota_server_group_members }} +{% endif -%} + {% if memcached_servers %} memcached_servers = {{ memcached_servers }} {% endif %} diff --git a/templates/liberty/nova.conf b/templates/liberty/nova.conf index d93bce35..63de0bf2 100644 --- a/templates/liberty/nova.conf +++ b/templates/liberty/nova.conf @@ -48,6 +48,37 @@ disk_allocation_ratio = {{ disk_allocation_ratio }} use_syslog={{ use_syslog }} my_ip = {{ host_ip }} +{% if quota_instances is not none -%} +quota_instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +quota_cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +quota_ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +quota_metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +quota_injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +quota_injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +quota_injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +quota_key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +quota_server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +quota_server_group_members = {{ quota_server_group_members }} +{% endif -%} + {% if memcached_servers %} memcached_servers = {{ memcached_servers }} {% endif %} diff --git a/templates/mitaka/nova.conf b/templates/mitaka/nova.conf index 1fd5f9ac..72ef2922 100644 --- a/templates/mitaka/nova.conf +++ b/templates/mitaka/nova.conf @@ -45,6 +45,37 @@ disk_allocation_ratio = {{ disk_allocation_ratio }} use_syslog={{ use_syslog }} my_ip = {{ host_ip }} +{% if quota_instances is not none -%} +quota_instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +quota_cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +quota_ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +quota_metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +quota_injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +quota_injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +quota_injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +quota_key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +quota_server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +quota_server_group_members = {{ quota_server_group_members }} +{% endif -%} + {% include "parts/novnc" %} {% if rbd_pool -%} diff --git a/templates/newton/nova.conf b/templates/newton/nova.conf index 66b18e67..72f6d745 100644 --- a/templates/newton/nova.conf +++ b/templates/newton/nova.conf @@ -47,6 +47,37 @@ disk_allocation_ratio = {{ disk_allocation_ratio }} use_syslog={{ use_syslog }} my_ip = {{ host_ip }} +{% if quota_instances is not none -%} +quota_instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +quota_cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +quota_ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +quota_metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +quota_injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +quota_injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +quota_injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +quota_key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +quota_server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +quota_server_group_members = {{ quota_server_group_members }} +{% endif -%} + {% include "parts/novnc" %} {% if rbd_pool -%} @@ -178,4 +209,3 @@ memcache_servers = {{ memcached_servers }} [wsgi] api_paste_config=/etc/nova/api-paste.ini - diff --git a/templates/ocata/nova.conf b/templates/ocata/nova.conf index 97c757b2..c00918ad 100644 --- a/templates/ocata/nova.conf +++ b/templates/ocata/nova.conf @@ -201,3 +201,35 @@ alias = {{ alias }} {% endfor -%} {% include "section-oslo-middleware" %} + +[quota] +{% if quota_instances is not none -%} +instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +server_group_members = {{ quota_server_group_members }} +{% endif -%} \ No newline at end of file diff --git a/templates/pike/nova.conf b/templates/pike/nova.conf index 99f527ab..5fbbb5b8 100644 --- a/templates/pike/nova.conf +++ b/templates/pike/nova.conf @@ -208,3 +208,35 @@ alias = {{ alias }} {% endfor -%} {% include "section-oslo-middleware" %} + +[quota] +{% if quota_instances is not none -%} +instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +server_group_members = {{ quota_server_group_members }} +{% endif -%} \ No newline at end of file diff --git a/templates/rocky/nova.conf b/templates/rocky/nova.conf index 20702c86..594225e3 100644 --- a/templates/rocky/nova.conf +++ b/templates/rocky/nova.conf @@ -214,3 +214,35 @@ alias = {{ alias }} {% endfor -%} {% include "section-oslo-middleware" %} + +[quota] +{% if quota_instances is not none -%} +instances = {{ quota_instances }} +{% endif -%} +{% if quota_cores is not none -%} +cores = {{ quota_cores }} +{% endif -%} +{% if quota_ram is not none -%} +ram = {{ quota_ram }} +{% endif -%} +{% if quota_metadata_items is not none -%} +metadata_items = {{ quota_metadata_items }} +{% endif -%} +{% if quota_injected_files is not none -%} +injected_files = {{ quota_injected_files }} +{% endif -%} +{% if quota_injected_file_content_bytes is not none -%} +injected_file_content_bytes = {{ quota_injected_file_content_bytes }} +{% endif -%} +{% if quota_injected_file_path_length is not none -%} +injected_file_path_length = {{ quota_injected_file_path_length }} +{% endif -%} +{% if quota_key_pairs is not none -%} +key_pairs = {{ quota_key_pairs }} +{% endif -%} +{% if quota_server_groups is not none -%} +server_groups = {{ quota_server_groups }} +{% endif -%} +{% if quota_server_group_members is not none -%} +server_group_members = {{ quota_server_group_members }} +{% endif -%} diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 40f0d9ee..fd4f4faa 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -15,6 +15,7 @@ import amulet import json import tempfile +import os from charmhelpers.contrib.openstack.amulet.deployment import ( OpenStackAmuletDeployment @@ -823,8 +824,8 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment): services['apache2'] = conf_file # Expected default and alternate values - flags_default = 'quota_cores=20,quota_instances=40,quota_ram=102400' - flags_alt = 'quota_cores=10,quota_instances=20,quota_ram=51200' + flags_default = 'cpu-allocation-ratio=16.0,ram-allocation-ratio=1.5' + flags_alt = 'cpu-allocation-ratio=32.0,ram-allocation-ratio=3.0' set_default = {'config-flags': flags_default} set_alternate = {'config-flags': flags_alt} @@ -857,3 +858,64 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment): action_id = u.run_action(self.nova_cc_sentry, "resume") assert u.wait_on_action(action_id), "Resume action failed" self._assert_services(should_run=True) + + def test_902_default_quota_settings(self): + """Test default quota settings.""" + config_file = '/etc/nova/nova.conf' + quotas = { + 'quota-instances': 20, + 'quota-cores': 40, + 'quota-ram': 102400, + 'quota-metadata-items': 256, + 'quota-injected-files': 10, + 'quota-injected-file-size': 20480, + 'quota-injected-path-size': 512, + 'quota-key-pairs': 200, + 'quota-server-groups': 20, + 'quota-server-group-members': 20, + } + cmp_os_release = CompareOpenStackReleases( + self._get_openstack_release_string() + ) + if cmp_os_release > 'newton': + section = 'quota' + else: + section = 'DEFAULT' + u.log.debug('Changing quotas in charm config') + self.d.configure('nova-cloud-controller', quotas) + self._auto_wait_for_status(exclude_services=self.exclude_services) + self.d.sentry.wait() + + if not u.validate_config_data(self.nova_cc_sentry, config_file, + section, quotas): + amulet.raise_status(amulet.FAIL, msg='update failed') + + u.log.debug('New default quotas found in correct section in nova.conf') + u.log.debug('test_902_default_quota_settings PASSED - (OK)') + + # Amulet test framework currently does not support setting charm-config + # values to None when an integer is expected by the configuration. + # By default, the quota settings are not written to nova.conf unless + # explicitly set. In order to keep tests idempotent, the following juju + # CLI commands are run to reset the quota values to None. + os.system("juju config nova-cloud-controller --reset" + " quota-instances") + os.system("juju config nova-cloud-controller --reset" + " quota-cores") + os.system("juju config nova-cloud-controller --reset" + " quota-ram") + os.system("juju config nova-cloud-controller --reset" + " quota-metadata-items") + os.system("juju config nova-cloud-controller --reset" + " quota-injected-files") + os.system("juju config nova-cloud-controller --reset" + " quota-injected-file-size") + os.system("juju config nova-cloud-controller --reset" + " quota-injected-path-size") + os.system("juju config nova-cloud-controller --reset" + " quota-key-pairs") + os.system("juju config nova-cloud-controller --reset" + " quota-server-groups") + os.system("juju config nova-cloud-controller --reset" + " quota-server-group-members") + self._auto_wait_for_status(exclude_services=self.exclude_services) diff --git a/unit_tests/test_nova_cc_contexts.py b/unit_tests/test_nova_cc_contexts.py index b2be4c16..7e1ae1df 100644 --- a/unit_tests/test_nova_cc_contexts.py +++ b/unit_tests/test_nova_cc_contexts.py @@ -323,6 +323,36 @@ class NovaComputeContextTests(CharmTestCase): self.config('ram-allocation-ratio')) self.assertEqual(ctxt['disk_allocation_ratio'], self.config('disk-allocation-ratio')) + self.assertEqual(ctxt['quota_instances'], + self.config('quota-instances')) + self.assertEqual(ctxt['quota_instances'], None) + self.assertEqual(ctxt['quota_cores'], + self.config('quota-cores')) + self.assertEqual(ctxt['quota_cores'], None) + self.assertEqual(ctxt['quota_ram'], + self.config('quota-ram')) + self.assertEqual(ctxt['quota_ram'], None) + self.assertEqual(ctxt['quota_metadata_items'], + self.config('quota-metadata-items')) + self.assertEqual(ctxt['quota_metadata_items'], None) + self.assertEqual(ctxt['quota_injected_files'], + self.config('quota-injected-files')) + self.assertEqual(ctxt['quota_injected_files'], None) + self.assertEqual(ctxt['quota_injected_file_content_bytes'], + self.config('quota-injected-file-size')) + self.assertEqual(ctxt['quota_injected_file_content_bytes'], None) + self.assertEqual(ctxt['quota_injected_file_path_length'], + self.config('quota-injected-path-size')) + self.assertEqual(ctxt['quota_injected_file_path_length'], None) + self.assertEqual(ctxt['quota_key_pairs'], + self.config('quota-key-pairs')) + self.assertEqual(ctxt['quota_key_pairs'], None) + self.assertEqual(ctxt['quota_server_groups'], + self.config('quota-server-groups')) + self.assertEqual(ctxt['quota_server_groups'], None) + self.assertEqual(ctxt['quota_server_group_members'], + self.config('quota-server-group-members')) + self.assertEqual(ctxt['quota_server_group_members'], None) _pci_alias1 = { "name": "IntelNIC",