From 8b59d08973d1a77ec543685696c3ab16d0aa937e Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Thu, 24 Nov 2022 11:45:16 +0800 Subject: [PATCH] Add VM priority feature package --- ...VM-high-low-priority-feature-support.patch | 459 ++++++++++++++++++ openstack-nova.spec | 37 +- 2 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 Add-VM-high-low-priority-feature-support.patch diff --git a/Add-VM-high-low-priority-feature-support.patch b/Add-VM-high-low-priority-feature-support.patch new file mode 100644 index 0000000..3910fba --- /dev/null +++ b/Add-VM-high-low-priority-feature-support.patch @@ -0,0 +1,459 @@ +From 26a8d5dc7c164f9f24cd86c0624da833a4d00afe Mon Sep 17 00:00:00 2001 +From: wangxiyuan +Date: Thu, 13 Oct 2022 11:08:33 +0800 +Subject: [PATCH] Add VM high/low priority feature support + +--- + api/openstack/compute/schemas/servers.py | 4 ++ + compute/api.py | 25 +++++++-- + conf/compute.py | 10 +++- + .../versions/403_add_priority_mix_feature.py | 21 ++++++++ + db/sqlalchemy/models.py | 4 ++ + exception.py | 5 ++ + objects/fields.py | 12 +++++ + objects/instance.py | 7 ++- + virt/hardware.py | 54 ++++++++++++++++++- + virt/libvirt/config.py | 6 +++ + virt/libvirt/driver.py | 34 +++++++++--- + 11 files changed, 169 insertions(+), 13 deletions(-) + create mode 100644 db/sqlalchemy/migrate_repo/versions/403_add_priority_mix_feature.py + +diff --git a/api/openstack/compute/schemas/servers.py b/api/openstack/compute/schemas/servers.py +index 6d0651c..3405c86 100644 +--- a/api/openstack/compute/schemas/servers.py ++++ b/api/openstack/compute/schemas/servers.py +@@ -138,6 +138,10 @@ _hints = { + 'type': 'string', + 'pattern': '^/[0-9a-f.:]+$' + }, ++ 'priority': { ++ 'type': 'string', ++ 'enum': ['high', 'low'], ++ }, + }, + # NOTE: As this Mail: + # http://lists.openstack.org/pipermail/openstack-dev/2015-June/067996.html +diff --git a/compute/api.py b/compute/api.py +index 4fa0cc2..d954e84 100644 +--- a/compute/api.py ++++ b/compute/api.py +@@ -916,7 +916,8 @@ class API(base.Base): + requested_networks, config_drive, + auto_disk_config, reservation_id, + max_count, +- supports_port_resource_request): ++ supports_port_resource_request, ++ priority): + """Verify all the input parameters regardless of the provisioning + strategy being performed. + """ +@@ -958,8 +959,9 @@ class API(base.Base): + boot_meta.get('properties', {}))) + + image_meta = _get_image_meta_obj(boot_meta) ++ final_priority = hardware.get_final_priority(instance_type, priority) + numa_topology = hardware.numa_get_constraints( +- instance_type, image_meta) ++ instance_type, image_meta, final_priority) + + system_metadata = {} + +@@ -1011,7 +1013,8 @@ class API(base.Base): + 'pci_requests': pci_request_info, + 'numa_topology': numa_topology, + 'system_metadata': system_metadata, +- 'port_resource_requests': port_resource_requests} ++ 'port_resource_requests': port_resource_requests, ++ 'priority': final_priority} + + options_from_image = self._inherit_properties_from_image( + boot_meta, auto_disk_config) +@@ -1322,6 +1325,17 @@ class API(base.Base): + + return objects.InstanceGroup.get_by_uuid(context, group_hint) + ++ def _get_requested_priority(self, filter_properties): ++ if (not filter_properties or ++ not filter_properties.get('scheduler_hints')): ++ return ++ ++ priority = filter_properties.get('scheduler_hints').get('priority') ++ if not priority: ++ return ++ ++ return priority ++ + def _create_instance(self, context, instance_type, + image_href, kernel_id, ramdisk_id, + min_count, max_count, +@@ -1368,6 +1382,8 @@ class API(base.Base): + self._check_auto_disk_config(image=boot_meta, + auto_disk_config=auto_disk_config) + ++ priority = self._get_requested_priority(filter_properties) ++ + base_options, max_net_count, key_pair, security_groups, \ + network_metadata = self._validate_and_build_base_options( + context, instance_type, boot_meta, image_href, image_id, +@@ -1375,7 +1391,8 @@ class API(base.Base): + key_name, key_data, security_groups, availability_zone, + user_data, metadata, access_ip_v4, access_ip_v6, + requested_networks, config_drive, auto_disk_config, +- reservation_id, max_count, supports_port_resource_request) ++ reservation_id, max_count, supports_port_resource_request, ++ priority) + + # max_net_count is the maximum number of instances requested by the + # user adjusted for any network quota constraints, including +diff --git a/conf/compute.py b/conf/compute.py +index 6713f61..fec2542 100644 +--- a/conf/compute.py ++++ b/conf/compute.py +@@ -719,7 +719,15 @@ for performance reasons, for example, with Ironic. + Possible values: + + * Any positive integer representing greenthreads count. +-""") ++"""), ++ cfg.BoolOpt('cpu_priority_mix_enable', ++ default=False, ++ help=""" ++Whether allow low priority VM use dedicated cpu set. ++ ++If enabled, The low priority VM can bind cpu both on dedicated and shared cpu ++set. ++"""), + ] + + compute_group_opts = [ +diff --git a/db/sqlalchemy/migrate_repo/versions/403_add_priority_mix_feature.py b/db/sqlalchemy/migrate_repo/versions/403_add_priority_mix_feature.py +new file mode 100644 +index 0000000..da37b6c +--- /dev/null ++++ b/db/sqlalchemy/migrate_repo/versions/403_add_priority_mix_feature.py +@@ -0,0 +1,21 @@ ++# Licensed under the Apache License, Version 2.0 (the "License"); you may ++# not use this file except in compliance with the License. You may obtain ++# a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++# License for the specific language governing permissions and limitations ++# under the License. ++ ++from sqlalchemy import MetaData, Column, Table, String ++ ++ ++def upgrade(migrate_engine): ++ meta = MetaData(bind=migrate_engine) ++ instances = Table('instances', meta, autoload=True) ++ if not hasattr(instances.c, 'priority'): ++ priority = Column('priority', String(255), nullable=True) ++ instances.create_column(priority) +diff --git a/db/sqlalchemy/models.py b/db/sqlalchemy/models.py +index 027a04f..1c49ec2 100644 +--- a/db/sqlalchemy/models.py ++++ b/db/sqlalchemy/models.py +@@ -354,6 +354,10 @@ class Instance(BASE, NovaBase, models.SoftDeleteMixin): + + hidden = Column(Boolean, default=False) + ++ # Identifies the high or low priority of the Instance. ++ # If this property is not set, the current instance is a common instance. ++ priority = Column(String(255)) ++ + + class InstanceInfoCache(BASE, NovaBase, models.SoftDeleteMixin): + """Represents a cache of information about an instance +diff --git a/exception.py b/exception.py +index d178034..98f9d81 100644 +--- a/exception.py ++++ b/exception.py +@@ -2310,6 +2310,11 @@ class InvalidCPUAllocationPolicy(Invalid): + "given: '%(requested)s', available: '%(available)s'.") + + ++class InvalidCPUAllocationPriority(Invalid): ++ msg_fmt = _("CPU priority requested from '%(source)s' is invalid, " ++ "given: '%(requested)s', available: '%(available)s'.") ++ ++ + class InvalidCPUThreadAllocationPolicy(Invalid): + msg_fmt = _("CPU thread policy requested from '%(source)s' is invalid, " + "given: '%(requested)s', available: '%(available)s'.") +diff --git a/objects/fields.py b/objects/fields.py +index 366a970..f45e1ec 100644 +--- a/objects/fields.py ++++ b/objects/fields.py +@@ -963,6 +963,18 @@ class InstanceTaskState(BaseNovaEnum): + SHELVING_OFFLOADING, UNSHELVING) + + ++class CPUAllocationPriority(BaseNovaEnum): ++ ++ HIGH = "high" ++ LOW = "low" ++ ++ ALL = (HIGH, LOW) ++ ++ ++class CPUAllocationPriorityField(BaseEnumField): ++ AUTO_TYPE = CPUAllocationPriority() ++ ++ + class InstancePowerState(Enum): + _UNUSED = '_unused' + NOSTATE = 'pending' +diff --git a/objects/instance.py b/objects/instance.py +index ba0ea15..c069afc 100644 +--- a/objects/instance.py ++++ b/objects/instance.py +@@ -116,7 +116,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject, + # Version 2.5: Added hard_delete kwarg in destroy + # Version 2.6: Added hidden + # Version 2.7: Added resources +- VERSION = '2.7' ++ # Version 2.8: Added priority ++ VERSION = '2.8' + + fields = { + 'id': fields.IntegerField(), +@@ -220,6 +221,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject, + 'trusted_certs': fields.ObjectField('TrustedCerts', nullable=True), + 'hidden': fields.BooleanField(default=False), + 'resources': fields.ObjectField('ResourceList', nullable=True), ++ ++ 'priority': fields.StringField(nullable=True), + } + + obj_extra_fields = ['name'] +@@ -227,6 +230,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject, + def obj_make_compatible(self, primitive, target_version): + super(Instance, self).obj_make_compatible(primitive, target_version) + target_version = versionutils.convert_version_to_tuple(target_version) ++ if target_version < (2, 8) and 'priority' in primitive: ++ del primitive['priority'] + if target_version < (2, 7) and 'resources' in primitive: + del primitive['resources'] + if target_version < (2, 6) and 'hidden' in primitive: +diff --git a/virt/hardware.py b/virt/hardware.py +index 1a22c6b..39ebf56 100644 +--- a/virt/hardware.py ++++ b/virt/hardware.py +@@ -1734,11 +1734,12 @@ def get_emulator_thread_policy_constraint(flavor): + + + # TODO(sahid): Move numa related to hardware/numa.py +-def numa_get_constraints(flavor, image_meta): ++def numa_get_constraints(flavor, image_meta, final_priority): + """Return topology related to input request. + + :param flavor: a flavor object to read extra specs from + :param image_meta: nova.objects.ImageMeta object instance ++ :param final_priority: a priority string + + :raises: exception.InvalidNUMANodesNumber if the number of NUMA + nodes is less than 1 or not an integer +@@ -1877,6 +1878,9 @@ def numa_get_constraints(flavor, image_meta): + + # sanity checks + ++ if final_priority: ++ _check_cpu_priority_constraint(final_priority, cpu_policy) ++ + if cpu_policy in (fields.CPUAllocationPolicy.SHARED, None): + if cpu_thread_policy: + raise exception.CPUThreadPolicyConfigurationInvalid() +@@ -2253,3 +2257,51 @@ def get_vpmems(flavor): + if formed_label: + formed_labels.append(formed_label) + return formed_labels ++ ++ ++def _get_flavor_priority(key, flavor, default=None, prefix='hw'): ++ """Extract both flavor- and image-based variants of metadata.""" ++ flavor_key = ':'.join([prefix, key]) ++ ++ flavor_value = flavor.get('extra_specs', {}).get(flavor_key, default) ++ ++ return flavor_value ++ ++ ++def get_final_priority(flavor, priority): ++ flavor_priority = _get_flavor_priority( ++ 'cpu_priority', flavor) ++ ++ if flavor_priority and (flavor_priority not in fields.CPUAllocationPriority.ALL): ++ raise exception.InvalidCPUAllocationPriority( ++ source='flavor extra specs', ++ requested=flavor_priority, ++ available=str(fields.CPUAllocationPriority.ALL)) ++ if priority and (priority not in fields.CPUAllocationPriority.ALL): ++ raise exception.InvalidCPUAllocationPriority( ++ source='scheduler hints', ++ requested=priority, ++ available=str(fields.CPUAllocationPriority.ALL)) ++ final_priority = priority if priority else flavor_priority ++ return final_priority ++ ++ ++def _check_cpu_priority_constraint(final_priority, cpu_policy): ++ """Validate and return the requested CPU priority. ++ ++ :param flavor: ``nova.objects.Flavor`` instance ++ :param priority: priority string ++ :param cpu_policy: cpu_policy string ++ :raises: exception.HintsPriorityForbidden if priority is defined on both ++ api and flavor and these priorities conflict. ++ :raises: exception.InvalidCPUAllocationPriority if priority is defined with ++ invalid value in api or flavor. ++ """ ++ if final_priority == fields.CPUAllocationPriority.HIGH: ++ if not cpu_policy or cpu_policy != fields.CPUAllocationPolicy.DEDICATED: ++ msg = _('cpu policy must exist and be dedicated if priority is high') ++ raise exception.InvalidRequest(msg) ++ elif final_priority == fields.CPUAllocationPriority.LOW: ++ if cpu_policy and cpu_policy != fields.CPUAllocationPolicy.SHARED: ++ msg = _('cpu policy must be empty or shared if priority is low') ++ raise exception.InvalidRequest(msg) +diff --git a/virt/libvirt/config.py b/virt/libvirt/config.py +index 39c4da8..e9db836 100644 +--- a/virt/libvirt/config.py ++++ b/virt/libvirt/config.py +@@ -2605,6 +2605,7 @@ class LibvirtConfigGuest(LibvirtConfigObject): + self.idmaps = [] + self.perf_events = [] + self.launch_security = None ++ self.partition = None + + def _format_basic_props(self, root): + root.append(self._text_node("uuid", self.uuid)) +@@ -2633,6 +2634,11 @@ class LibvirtConfigGuest(LibvirtConfigObject): + metadata.append(m.format_dom()) + root.append(metadata) + ++ if self.partition is not None: ++ resource = etree.Element("resource") ++ resource.append(self._text_node("partition", self.partition)) ++ root.append(resource) ++ + def _format_os(self, root): + os = etree.Element("os") + type_node = self._text_node("type", self.os_type) +diff --git a/virt/libvirt/driver.py b/virt/libvirt/driver.py +index 94d9982..dae25e3 100644 +--- a/virt/libvirt/driver.py ++++ b/virt/libvirt/driver.py +@@ -4845,7 +4845,7 @@ class LibvirtDriver(driver.ComputeDriver): + cell_pairs.append((guest_config_cell, host_cell)) + return cell_pairs + +- def _get_pin_cpuset(self, vcpu, object_numa_cell, host_cell): ++ def _get_pin_cpuset(self, vcpu, object_numa_cell, host_cell, priority): + """Returns the config object of LibvirtConfigGuestCPUTuneVCPUPin. + Prepares vcpupin config for the guest with the following caveats: + +@@ -4860,12 +4860,14 @@ class LibvirtDriver(driver.ComputeDriver): + pin_cpuset.cpuset = set([object_numa_cell.cpu_pinning[vcpu]]) + else: + pin_cpuset.cpuset = host_cell.cpuset ++ if CONF.compute.cpu_priority_mix_enable and priority == 'low': ++ pin_cpuset.cpuset |= host_cell.pcpuset + + return pin_cpuset + + def _get_emulatorpin_cpuset(self, vcpu, object_numa_cell, vcpus_rt, + emulator_threads_policy, wants_realtime, +- pin_cpuset): ++ pin_cpuset, priority): + """Returns a set of cpu_ids to add to the cpuset for emulator threads + with the following caveats: + +@@ -4885,6 +4887,7 @@ class LibvirtDriver(driver.ComputeDriver): + """ + emulatorpin_cpuset = set([]) + shared_ids = hardware.get_cpu_shared_set() ++ dedicated_ids = hardware.get_cpu_dedicated_set() + + if emulator_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE: + if object_numa_cell.cpuset_reserved: +@@ -4901,6 +4904,9 @@ class LibvirtDriver(driver.ComputeDriver): + {'online': sorted(online_pcpus), + 'req': sorted(shared_ids)}) + raise exception.Invalid(msg) ++ if (CONF.compute.cpu_priority_mix_enable and priority == 'low' and ++ dedicated_ids): ++ cpuset |= dedicated_ids & online_pcpus + emulatorpin_cpuset = cpuset + elif not wants_realtime or vcpu not in vcpus_rt: + emulatorpin_cpuset = pin_cpuset.cpuset +@@ -4908,7 +4914,7 @@ class LibvirtDriver(driver.ComputeDriver): + return emulatorpin_cpuset + + def _get_guest_numa_config(self, instance_numa_topology, flavor, +- image_meta): ++ image_meta, priority=None): + """Returns the config objects for the guest NUMA specs. + + Determines the CPUs that the guest can be pinned to if the guest +@@ -4950,6 +4956,11 @@ class LibvirtDriver(driver.ComputeDriver): + if CONF.vcpu_pin_set or CONF.compute.cpu_shared_set: + shared_cpus = self._get_vcpu_available() + ++ if CONF.compute.cpu_priority_mix_enable: ++ if shared_cpus and priority == 'low' and CONF.compute.cpu_dedicated_set: ++ dedicated_cpus = self._get_pcpu_available() ++ shared_cpus |= dedicated_cpus ++ + topology = self._get_host_numa_topology() + + # We have instance NUMA so translate it to the config class +@@ -5011,12 +5022,13 @@ class LibvirtDriver(driver.ComputeDriver): + object_numa_cell = instance_numa_topology.cells[guest_node_id] + for cpu in guest_config_cell.cpus: + pin_cpuset = self._get_pin_cpuset(cpu, object_numa_cell, +- host_cell) ++ host_cell, priority) + guest_cpu_tune.vcpupin.append(pin_cpuset) + + emu_pin_cpuset = self._get_emulatorpin_cpuset( + cpu, object_numa_cell, vcpus_rt, +- emulator_threads_policy, wants_realtime, pin_cpuset) ++ emulator_threads_policy, wants_realtime, pin_cpuset, ++ priority) + guest_cpu_tune.emulatorpin.cpuset.update(emu_pin_cpuset) + + # TODO(berrange) When the guest has >1 NUMA node, it will +@@ -5782,7 +5794,7 @@ class LibvirtDriver(driver.ComputeDriver): + guest.vcpus = flavor.vcpus + + guest_numa_config = self._get_guest_numa_config( +- instance.numa_topology, flavor, image_meta) ++ instance.numa_topology, flavor, image_meta, instance.priority) + + guest.cpuset = guest_numa_config.cpuset + guest.cputune = guest_numa_config.cputune +@@ -5887,8 +5899,18 @@ class LibvirtDriver(driver.ComputeDriver): + if vpmems: + self._guest_add_vpmems(guest, vpmems) + ++ self._guest_add_partition(guest, instance) ++ + return guest + ++ def _guest_add_partition(self, guest, instance): ++ if instance.priority == 'high': ++ guest.partition = '/high_prio_machine' ++ elif instance.priority == 'low': ++ guest.partition = '/low_prio_machine' ++ else: ++ guest.partition = None ++ + def _get_ordered_vpmems(self, instance, flavor): + ordered_vpmems = [] + vpmems = self._get_vpmems(instance) +-- +2.17.1 diff --git a/openstack-nova.spec b/openstack-nova.spec index e07cef5..1273481 100644 --- a/openstack-nova.spec +++ b/openstack-nova.spec @@ -1,3 +1,15 @@ +%define gitPatch() \ +cd %1; \ +git init && git config user.name "openstack-plugin" && git config user.email "openstack-plugin"; \ +git add . && git commit -m "openstack-plugin init"; \ +git apply --check %2 || exit 1 && git apply %2; \ +git add . && git commit -m "openstack-plugin patch" + +%define gitUnPatch() \ +cd %1;\ +git reset --hard HEAD~;\ +rm -rf %1/.git + %{!?upstream_version: %global upstream_version %{version}%{?milestone}} %global with_doc 0 @@ -15,7 +27,7 @@ Name: openstack-nova # Liberty semver reset # https://review.openstack.org/#/q/I6a35fa0dda798fad93b804d00a46af80f08d475c,n,z Version: 20.6.1 -Release: 7 +Release: 8 Summary: OpenStack Compute (nova) License: ASL 2.0 @@ -48,6 +60,8 @@ Source39: nova_migration_authorized_keys Source40: nova_migration-rootwrap.conf Source41: nova_migration-rootwrap_cold_migration +Source50: Add-VM-high-low-priority-feature-support.patch + Patch1: Fixes-aarch64-incorrect-cpu-model.patch Patch2: 0001-Remove-monotonic-usage.patch BuildArch: noarch @@ -353,6 +367,14 @@ Requires: openstack-nova = %{version}-%{release} This package contains the nova Python library. +%package priority-feature +Summary: The plug-in package of openstack-nova for VM priority feature +Requires: git +Requires: openstack-nova + +%description priority-feature +The plug-in package of openstack-nova for VM priority feature + %if 0%{?with_doc} %package doc Summary: Documentation for OpenStack Compute @@ -545,6 +567,9 @@ rm -f %{buildroot}/usr/share/doc/nova/README* # Remove duplicated configuration files deployed at /usr/etc rm -rf %{buildroot}%{_prefix}/etc/nova +# Install priority patch +install -D -p -m 644 %{SOURCE50} %{buildroot}%{python3_sitelib}/openstack-plugin/%{SOURCE50} + # FIXME(jpena): unit tests are taking too long in the current DLRN infra # Until we have a better architecture, let's not run them when under DLRN %if 0%{!?dlrn} @@ -594,6 +619,8 @@ exit 0 %systemd_post %{name}-spicehtml5proxy.service %post serialproxy %systemd_post %{name}-serialproxy.service +%post priority-feature +%gitPatch %{python3_sitelib}/nova %{python3_sitelib}/openstack-plugin/%{SOURCE50} %preun compute %systemd_preun %{name}-compute.service @@ -624,6 +651,8 @@ exit 0 %systemd_postun_with_restart %{name}-spicehtml5proxy.service %postun serialproxy %systemd_postun_with_restart %{name}-serialproxy.service +%preun priority-feature +%gitUnPatch %{python3_sitelib}/nova %files @@ -722,6 +751,9 @@ exit 0 %license LICENSE %{python3_sitelib}/nova/tests +%files priority-feature +%{python3_sitelib}/openstack-plugin/%{SOURCE50} + %if 0%{?with_doc} %files doc %license LICENSE @@ -729,6 +761,9 @@ exit 0 %endif %changelog +* Thu Nov 24 2022 wangxiyuan -20.6.1-8 +- Add VM priority feature pacakge. + * Sat Oct 8 2022 binshuozu - 20.6.1-7 - Migrate to openstack-macros to create user & group -- Gitee