diff --git a/app/controllers/api/connect/v3/systems/systems_controller.rb b/app/controllers/api/connect/v3/systems/systems_controller.rb index 08c60f105..f8f3b8c92 100644 --- a/app/controllers/api/connect/v3/systems/systems_controller.rb +++ b/app/controllers/api/connect/v3/systems/systems_controller.rb @@ -3,6 +3,22 @@ class Api::Connect::V3::Systems::SystemsController < Api::Connect::BaseControlle before_action :authenticate_system def update + if params[:online_at].present? + params[:online_at].each do |online_at| + dthours = online_at.split(':') + if dthours.count == 2 + begin + @system_uptime = SystemUptime.create!(system_id: @system.id, online_at_day: dthours[0], online_at_hours: dthours[1]) + logger.debug(N_("Added uptime information for system '%s'") % @system.id) + rescue ActiveRecord::RecordNotUnique + logger.debug(N_("Uptime information existing for system '%s'") % @system.id) + end + else + logger.error(N_("Uptime data is malformed '%s'") % online_at) + end + end + end + @system.hostname = params[:hostname] # Since the payload is handled by rails all values are converted to string diff --git a/app/models/system.rb b/app/models/system.rb index 1e10bbc4c..0883f7de0 100644 --- a/app/models/system.rb +++ b/app/models/system.rb @@ -6,6 +6,7 @@ class System < ApplicationRecord has_many :services, through: :activations has_many :repositories, -> { distinct }, through: :services has_many :products, -> { distinct }, through: :services + has_many :system_uptimes, dependent: :destroy validates :system_token, uniqueness: { scope: %i[login password], case_sensitive: false } diff --git a/app/models/system_uptime.rb b/app/models/system_uptime.rb new file mode 100644 index 000000000..f6bf1087b --- /dev/null +++ b/app/models/system_uptime.rb @@ -0,0 +1,7 @@ +class SystemUptime < ApplicationRecord + belongs_to :system + + validates :system_id, presence: true + validates :online_at_day, presence: true + validates :online_at_hours, presence: true +end diff --git a/db/migrate/20240111200053_create_system_uptimes.rb b/db/migrate/20240111200053_create_system_uptimes.rb new file mode 100644 index 000000000..ade4778c3 --- /dev/null +++ b/db/migrate/20240111200053_create_system_uptimes.rb @@ -0,0 +1,18 @@ +class CreateSystemUptimes < ActiveRecord::Migration[6.1] + def change + safety_assured do + create_table :system_uptimes, id: :serial, force: :cascade do |t| + t.bigint :system_id, null: false + t.date :online_at_day, null: false + t.column :online_at_hours, 'binary(24)', null: false + t.timestamps + end + + commit_db_transaction + + add_index :system_uptimes, %i[system_id online_at_day], unique: true, name: 'id_online_day' + + add_foreign_key :system_uptimes, :systems, column: :system_id, validate: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 1f3795dbe..ae69ebced 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,174 +10,183 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_08_14_105634) do +ActiveRecord::Schema.define(version: 20_230_814_105_634) do + create_table 'activations', charset: 'utf8', force: :cascade do |t| + t.bigint 'service_id', null: false + t.bigint 'system_id', null: false + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.bigint 'subscription_id' + t.index ['service_id'], name: 'fk_rails_5ad14bc754' + t.index ['subscription_id'], name: 'fk_rails_296466bd8d' + t.index ['system_id', 'service_id'], name: 'index_activations_on_system_id_and_service_id', unique: true + t.index ['system_id'], name: 'index_activations_on_system_id' + end - create_table "activations", charset: "utf8", force: :cascade do |t| - t.bigint "service_id", null: false - t.bigint "system_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.bigint "subscription_id" - t.index ["service_id"], name: "fk_rails_5ad14bc754" - t.index ["subscription_id"], name: "fk_rails_296466bd8d" - t.index ["system_id", "service_id"], name: "index_activations_on_system_id_and_service_id", unique: true - t.index ["system_id"], name: "index_activations_on_system_id" + create_table 'deregistered_systems', charset: 'utf8', force: :cascade do |t| + t.bigint 'scc_system_id', null: false, comment: 'SCC IDs of deregistered systems; used for forwarding to SCC' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['scc_system_id'], name: 'index_deregistered_systems_on_scc_system_id', unique: true end - create_table "deregistered_systems", charset: "utf8", force: :cascade do |t| - t.bigint "scc_system_id", null: false, comment: "SCC IDs of deregistered systems; used for forwarding to SCC" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["scc_system_id"], name: "index_deregistered_systems_on_scc_system_id", unique: true + create_table 'downloaded_files', charset: 'utf8', force: :cascade do |t| + t.string 'checksum_type' + t.string 'checksum' + t.string 'local_path' + t.bigint 'file_size', unsigned: true + t.index ['checksum_type', 'checksum'], name: 'index_downloaded_files_on_checksum_type_and_checksum' + t.index ['local_path'], name: 'index_downloaded_files_on_local_path', unique: true end - create_table "downloaded_files", charset: "utf8", force: :cascade do |t| - t.string "checksum_type" - t.string "checksum" - t.string "local_path" - t.bigint "file_size", unsigned: true - t.index ["checksum_type", "checksum"], name: "index_downloaded_files_on_checksum_type_and_checksum" - t.index ["local_path"], name: "index_downloaded_files_on_local_path", unique: true + create_table 'hw_infos', charset: 'utf8', force: :cascade do |t| + t.integer 'cpus' + t.integer 'sockets' + t.string 'hypervisor' + t.string 'arch' + t.integer 'system_id' + t.string 'uuid' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.text 'instance_data', comment: 'Additional client information, e.g. instance identity document' + t.string 'cloud_provider' + t.index ['hypervisor'], name: 'index_hw_infos_on_hypervisor' + t.index ['system_id'], name: 'index_hw_infos_on_system_id', unique: true end - create_table "hw_infos", charset: "utf8", force: :cascade do |t| - t.integer "cpus" - t.integer "sockets" - t.string "hypervisor" - t.string "arch" - t.integer "system_id" - t.string "uuid" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "instance_data", comment: "Additional client information, e.g. instance identity document" - t.string "cloud_provider" - t.index ["hypervisor"], name: "index_hw_infos_on_hypervisor" - t.index ["system_id"], name: "index_hw_infos_on_system_id", unique: true + create_table 'product_predecessors', charset: 'utf8', force: :cascade do |t| + t.bigint 'product_id', null: false + t.bigint 'predecessor_id' + t.integer 'kind', default: 0, null: false + t.index ['predecessor_id'], name: 'fk_rails_ae2fd616af' + t.index ['product_id', 'predecessor_id'], name: 'index_product_predecessors_on_product_id_and_predecessor_id', unique: true + t.index ['product_id'], name: 'index_product_predecessors_on_product_id' end - create_table "product_predecessors", charset: "utf8", force: :cascade do |t| - t.bigint "product_id", null: false - t.bigint "predecessor_id" - t.integer "kind", default: 0, null: false - t.index ["predecessor_id"], name: "fk_rails_ae2fd616af" - t.index ["product_id", "predecessor_id"], name: "index_product_predecessors_on_product_id_and_predecessor_id", unique: true - t.index ["product_id"], name: "index_product_predecessors_on_product_id" + create_table 'products', charset: 'utf8', force: :cascade do |t| + t.string 'name' + t.text 'description' + t.string 'shortname' + t.string 'former_identifier' + t.string 'product_type' + t.string 'product_class' + t.string 'release_type' + t.string 'release_stage' + t.string 'identifier' + t.string 'version' + t.string 'arch' + t.string 'eula_url' + t.boolean 'free' + t.string 'cpe' + t.string 'friendly_version' end - create_table "products", charset: "utf8", force: :cascade do |t| - t.string "name" - t.text "description" - t.string "shortname" - t.string "former_identifier" - t.string "product_type" - t.string "product_class" - t.string "release_type" - t.string "release_stage" - t.string "identifier" - t.string "version" - t.string "arch" - t.string "eula_url" - t.boolean "free" - t.string "cpe" - t.string "friendly_version" + create_table 'products_extensions', charset: 'utf8', force: :cascade do |t| + t.bigint 'product_id', null: false + t.bigint 'extension_id', null: false + t.boolean 'recommended' + t.bigint 'root_product_id', null: false + t.boolean 'migration_extra', default: false + t.index ['extension_id'], name: 'index_products_extensions_on_extension_id' + t.index ['product_id', 'extension_id', 'root_product_id'], name: 'index_products_extensions_on_product_extension_root', unique: true + t.index ['product_id'], name: 'index_products_extensions_on_product_id' + t.index ['root_product_id'], name: 'fk_rails_7d0e68d364' end - create_table "products_extensions", charset: "utf8", force: :cascade do |t| - t.bigint "product_id", null: false - t.bigint "extension_id", null: false - t.boolean "recommended" - t.bigint "root_product_id", null: false - t.boolean "migration_extra", default: false - t.index ["extension_id"], name: "index_products_extensions_on_extension_id" - t.index ["product_id", "extension_id", "root_product_id"], name: "index_products_extensions_on_product_extension_root", unique: true - t.index ["product_id"], name: "index_products_extensions_on_product_id" - t.index ["root_product_id"], name: "fk_rails_7d0e68d364" + create_table 'repositories', charset: 'utf8', force: :cascade do |t| + t.bigint 'scc_id', unsigned: true + t.string 'name', null: false + t.string 'description' + t.boolean 'enabled', default: false, null: false + t.boolean 'autorefresh', default: true, null: false + t.string 'external_url', null: false + t.string 'auth_token' + t.boolean 'installer_updates', default: false, null: false + t.boolean 'mirroring_enabled', default: false, null: false + t.string 'local_path', null: false + t.datetime 'last_mirrored_at' + t.string 'friendly_id' + t.index ['external_url'], name: 'index_repositories_on_external_url', unique: true + t.index ['friendly_id'], name: 'index_repositories_on_friendly_id', unique: true + t.index ['scc_id'], name: 'index_repositories_on_scc_id', unique: true end - create_table "repositories", charset: "utf8", force: :cascade do |t| - t.bigint "scc_id", unsigned: true - t.string "name", null: false - t.string "description" - t.boolean "enabled", default: false, null: false - t.boolean "autorefresh", default: true, null: false - t.string "external_url", null: false - t.string "auth_token" - t.boolean "installer_updates", default: false, null: false - t.boolean "mirroring_enabled", default: false, null: false - t.string "local_path", null: false - t.datetime "last_mirrored_at" - t.string "friendly_id" - t.index ["external_url"], name: "index_repositories_on_external_url", unique: true - t.index ["friendly_id"], name: "index_repositories_on_friendly_id", unique: true - t.index ["scc_id"], name: "index_repositories_on_scc_id", unique: true + create_table 'repositories_services', charset: 'utf8', force: :cascade do |t| + t.bigint 'repository_id', null: false + t.bigint 'service_id', null: false + t.index ['repository_id'], name: 'index_repositories_services_on_repository_id' + t.index ['service_id', 'repository_id'], name: 'index_repositories_services_on_service_id_and_repository_id', unique: true + t.index ['service_id'], name: 'index_repositories_services_on_service_id' end - create_table "repositories_services", charset: "utf8", force: :cascade do |t| - t.bigint "repository_id", null: false - t.bigint "service_id", null: false - t.index ["repository_id"], name: "index_repositories_services_on_repository_id" - t.index ["service_id", "repository_id"], name: "index_repositories_services_on_service_id_and_repository_id", unique: true - t.index ["service_id"], name: "index_repositories_services_on_service_id" + create_table 'services', charset: 'utf8', force: :cascade do |t| + t.bigint 'product_id', null: false + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['product_id'], name: 'index_services_on_product_id', unique: true end - create_table "services", charset: "utf8", force: :cascade do |t| - t.bigint "product_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["product_id"], name: "index_services_on_product_id", unique: true + create_table 'subscription_product_classes', charset: 'utf8', force: :cascade do |t| + t.bigint 'subscription_id', null: false + t.string 'product_class', null: false + t.index ['subscription_id', 'product_class'], name: 'index_product_class_unique', unique: true + t.index ['subscription_id'], name: 'index_subscription_product_classes_on_subscription_id' end - create_table "subscription_product_classes", charset: "utf8", force: :cascade do |t| - t.bigint "subscription_id", null: false - t.string "product_class", null: false - t.index ["subscription_id", "product_class"], name: "index_product_class_unique", unique: true - t.index ["subscription_id"], name: "index_subscription_product_classes_on_subscription_id" + create_table 'subscriptions', charset: 'utf8', force: :cascade do |t| + t.string 'regcode', null: false + t.string 'name', null: false + t.string 'kind', null: false + t.string 'status', null: false + t.datetime 'starts_at' + t.datetime 'expires_at' + t.integer 'system_limit', null: false + t.integer 'systems_count', null: false + t.integer 'virtual_count' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['regcode'], name: 'index_subscriptions_on_regcode' end - create_table "subscriptions", charset: "utf8", force: :cascade do |t| - t.string "regcode", null: false - t.string "name", null: false - t.string "kind", null: false - t.string "status", null: false - t.datetime "starts_at" - t.datetime "expires_at" - t.integer "system_limit", null: false - t.integer "systems_count", null: false - t.integer "virtual_count" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["regcode"], name: "index_subscriptions_on_regcode" + create_table 'system_uptimes', id: { type: :bigint, unsigned: true }, charset: 'utf8mb3', collation: 'utf8mb3_general_ci', force: :cascade do |t| + t.bigint 'system_id', null: false + t.date 'online_at_day', null: false + t.binary 'online_at_hours', limit: 24, null: false + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + t.index ['system_id', 'online_at_day'], name: 'id_online_day', unique: true end - create_table "systems", charset: "utf8", force: :cascade do |t| - t.string "login" - t.string "password" - t.string "hostname" - t.datetime "registered_at" - t.datetime "last_seen_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.datetime "scc_registered_at" - t.bigint "scc_system_id", comment: "System ID in SCC (if the system registration was forwarded; needed for forwarding de-registrations)" - t.boolean "proxy_byos", default: false - t.string "system_token" - t.text "system_information", size: :long - t.text "instance_data" - t.index ["login", "password", "system_token"], name: "index_systems_on_login_and_password_and_system_token", unique: true - t.index ["login", "password"], name: "index_systems_on_login_and_password" - t.check_constraint "json_valid(`system_information`)", name: "system_information" + create_table 'systems', charset: 'utf8', force: :cascade do |t| + t.string 'login' + t.string 'password' + t.string 'hostname' + t.datetime 'registered_at' + t.datetime 'last_seen_at' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.datetime 'scc_registered_at' + t.bigint 'scc_system_id', comment: 'System ID in SCC (if the system registration was forwarded; needed for forwarding de-registrations)' + t.boolean 'proxy_byos', default: false + t.string 'system_token' + t.text 'system_information', size: :long + t.text 'instance_data' + t.index ['login', 'password', 'system_token'], name: 'index_systems_on_login_and_password_and_system_token', unique: true + t.index ['login', 'password'], name: 'index_systems_on_login_and_password' + t.check_constraint 'json_valid(`system_information`)', name: 'system_information' end - add_foreign_key "activations", "services", on_delete: :cascade - add_foreign_key "activations", "subscriptions" - add_foreign_key "activations", "systems", on_delete: :cascade - add_foreign_key "product_predecessors", "products", column: "predecessor_id" - add_foreign_key "product_predecessors", "products", on_delete: :cascade - add_foreign_key "products_extensions", "products", column: "extension_id", on_delete: :cascade - add_foreign_key "products_extensions", "products", column: "root_product_id" - add_foreign_key "products_extensions", "products", on_delete: :cascade - add_foreign_key "repositories_services", "repositories", on_delete: :cascade - add_foreign_key "repositories_services", "services", on_delete: :cascade - add_foreign_key "services", "products" - add_foreign_key "subscription_product_classes", "subscriptions", on_delete: :cascade + add_foreign_key 'activations', 'services', on_delete: :cascade + add_foreign_key 'activations', 'subscriptions' + add_foreign_key 'activations', 'systems', on_delete: :cascade + add_foreign_key 'product_predecessors', 'products', column: 'predecessor_id' + add_foreign_key 'product_predecessors', 'products', on_delete: :cascade + add_foreign_key 'products_extensions', 'products', column: 'extension_id', on_delete: :cascade + add_foreign_key 'products_extensions', 'products', column: 'root_product_id' + add_foreign_key 'products_extensions', 'products', on_delete: :cascade + add_foreign_key 'repositories_services', 'repositories', on_delete: :cascade + add_foreign_key 'repositories_services', 'services', on_delete: :cascade + add_foreign_key 'services', 'products' + add_foreign_key 'subscription_product_classes', 'subscriptions', on_delete: :cascade + add_foreign_key 'system_uptimes', 'systems' end diff --git a/lib/rmt.rb b/lib/rmt.rb index c763386a7..ec7b4497d 100644 --- a/lib/rmt.rb +++ b/lib/rmt.rb @@ -1,5 +1,5 @@ module RMT - VERSION ||= '2.15'.freeze + VERSION ||= '2.16'.freeze DEFAULT_USER = '_rmt'.freeze DEFAULT_GROUP = 'nginx'.freeze diff --git a/lib/suse/connect/system_serializer.rb b/lib/suse/connect/system_serializer.rb index 950139c96..638670708 100644 --- a/lib/suse/connect/system_serializer.rb +++ b/lib/suse/connect/system_serializer.rb @@ -18,6 +18,7 @@ class SUSE::Connect::SystemSerializer < ActiveModel::Serializer attribute :hostname, if: :needs_full_update? attribute :hwinfo, if: :has_hwinfo_and_needs_full_update? attribute :products, if: :needs_full_update? + attribute :systemuptimes, if: :has_systemuptime? # We send the internal system id as system_token if the system (in RMT) is # duplicated (therefore using the system_token mechanism). @@ -47,6 +48,17 @@ def products end end + def systemuptimes + object.system_uptimes.map do |systemuptime| + payload = { + system_id: systemuptime.system_id, + online_at_day: systemuptime.online_at_day, + online_at_hours: systemuptime.online_at_hours + } + payload + end + end + def hwinfo JSON.parse(object.system_information).symbolize_keys end @@ -59,6 +71,10 @@ def has_system_token? object.system_token.present? end + def has_systemuptime? + systemuptimes.any? + end + def needs_full_update? !object.scc_synced_at end diff --git a/lib/tasks/system_uptimes.rake b/lib/tasks/system_uptimes.rake new file mode 100644 index 000000000..3a7231130 --- /dev/null +++ b/lib/tasks/system_uptimes.rake @@ -0,0 +1,8 @@ +namespace :db do + namespace :maintenance do + desc 'Delete all uptime tracking data which are older than 90 days' + task cleanup_uptime_tracking: :environment do + SystemUptime.where("online_at_day < '#{2.days.ago}'").delete_all + end + end +end diff --git a/package/files/systemd/rmt-uptime-cleanup.service b/package/files/systemd/rmt-uptime-cleanup.service new file mode 100644 index 000000000..cce384e23 --- /dev/null +++ b/package/files/systemd/rmt-uptime-cleanup.service @@ -0,0 +1,12 @@ +[Unit] +Description=RMT uptime cleanup service +Requires=mysql.service +Wants=rmt-uptime-cleanup.timer + +[Service] +Type=oneshot +WorkingDirectory=/usr/share/rmt/bin +ExecStart=bundle exec rake db:maintenance:cleanup_uptime_tracking RAILS_ENV=production + +[Install] +WantedBy=multi-user.target diff --git a/package/files/systemd/rmt-uptime-cleanup.timer b/package/files/systemd/rmt-uptime-cleanup.timer new file mode 100644 index 000000000..6dc8c930d --- /dev/null +++ b/package/files/systemd/rmt-uptime-cleanup.timer @@ -0,0 +1,10 @@ +[Unit] +Description=RMT Uptime Data cleanup timer + +[Timer] +# Run this timer every day at a randomized 24h delay. +OnCalendar=daily +RandomizedDelaySec=24h + +[Install] +WantedBy=timers.target diff --git a/package/obs/rmt-server.spec b/package/obs/rmt-server.spec index 577087ba5..e14faa66c 100644 --- a/package/obs/rmt-server.spec +++ b/package/obs/rmt-server.spec @@ -1,7 +1,7 @@ # # spec file for package rmt-server # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -30,7 +30,7 @@ %define ruby_version %{rb_default_ruby_suffix} Name: rmt-server -Version: 2.15 +Version: 2.16 Release: 0 Summary: Repository mirroring tool and registration proxy for SCC License: GPL-2.0-or-later @@ -144,6 +144,7 @@ mkdir -p %{buildroot}%{_unitdir} install -m 444 package/files/systemd/rmt-server-mirror.timer %{buildroot}%{_unitdir} install -m 444 package/files/systemd/rmt-server-sync.timer %{buildroot}%{_unitdir} install -m 444 package/files/systemd/rmt-server-systems-scc-sync.timer %{buildroot}%{_unitdir} +install -m 444 package/files/systemd/rmt-uptime-cleanup.timer %{buildroot}%{_unitdir} install -m 444 package/files/systemd/rmt-server-mirror.service %{buildroot}%{_unitdir} install -m 444 package/files/systemd/rmt-server-sync.service %{buildroot}%{_unitdir} @@ -151,6 +152,7 @@ install -m 444 package/files/systemd/rmt-server-systems-scc-sync.service %{build install -m 444 package/files/systemd/rmt-server.service %{buildroot}%{_unitdir} install -m 444 package/files/systemd/rmt-server.target %{buildroot}%{_unitdir} install -m 444 package/files/systemd/rmt-server-migration.service %{buildroot}%{_unitdir} +install -m 444 package/files/systemd/rmt-uptime-cleanup.service %{buildroot}%{_unitdir} install -m 444 engines/registration_sharing/package/rmt-server-regsharing.service %{buildroot}%{_unitdir} install -m 444 engines/registration_sharing/package/rmt-server-regsharing.timer %{buildroot}%{_unitdir} @@ -163,6 +165,7 @@ ln -fs %{_sbindir}/service %{buildroot}%{_sbindir}/rcrmt-server-migration ln -fs %{_sbindir}/service %{buildroot}%{_sbindir}/rcrmt-server-mirror ln -fs %{_sbindir}/service %{buildroot}%{_sbindir}/rcrmt-server-sync ln -fs %{_sbindir}/service %{buildroot}%{_sbindir}/rcrmt-server-systems-scc-sync +ln -fs %{_sbindir}/service %{buildroot}%{_sbindir}/rcrmt-uptime-cleanup ln -fs %{_sbindir}/service %{buildroot}%{_sbindir}/rcrmt-server-regsharing ln -fs %{_sbindir}/service %{buildroot}%{_sbindir}/rcrmt-server-trim-cache @@ -269,6 +272,7 @@ chrpath -d %{buildroot}%{lib_dir}/vendor/bundle/ruby/*/extensions/*/*/mysql2-*/m %{_sbindir}/rcrmt-server-sync %{_sbindir}/rcrmt-server-mirror %{_sbindir}/rcrmt-server-systems-scc-sync +%{_sbindir}/rcrmt-uptime-cleanup %{_unitdir}/rmt-server.target %{_unitdir}/rmt-server.service %{_unitdir}/rmt-server-migration.service @@ -278,6 +282,8 @@ chrpath -d %{buildroot}%{lib_dir}/vendor/bundle/ruby/*/extensions/*/*/mysql2-*/m %{_unitdir}/rmt-server-sync.timer %{_unitdir}/rmt-server-systems-scc-sync.service %{_unitdir}/rmt-server-systems-scc-sync.timer +%{_unitdir}/rmt-uptime-cleanup.service +%{_unitdir}/rmt-uptime-cleanup.timer %dir %{_datadir}/bash-completion/ %dir %{_datadir}/bash-completion/completions/ %{_datadir}/bash-completion/completions/rmt-cli @@ -318,10 +324,10 @@ getent group %{rmt_group} >/dev/null || %{_sbindir}/groupadd -r %{rmt_group} getent passwd %{rmt_user} >/dev/null || \ %{_sbindir}/useradd -g %{rmt_group} -s /bin/false -r \ -c "user for RMT" %{rmt_user} -%service_add_pre rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service +%service_add_pre rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service rmt-uptime-cleanup.service %post -%service_add_post rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service +%service_add_post rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service rmt-uptime-cleanup.service # Run only on install if [ $1 -eq 1 ]; then @@ -354,10 +360,10 @@ if [ ! -e %{_datadir}/rmt/public/suma ]; then fi %preun -%service_del_preun rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service +%service_del_preun rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service rmt-uptime-cleanup.service %postun -%service_del_postun rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service +%service_del_postun rmt-server.target rmt-server.service rmt-server-migration.service rmt-server-mirror.service rmt-server-sync.service rmt-server-systems-scc-sync.service rmt-uptime-cleanup.service %posttrans config # Don't fail if either systemd or nginx are not running diff --git a/spec/factories/system_uptimes.rb b/spec/factories/system_uptimes.rb new file mode 100644 index 000000000..fc7c976c2 --- /dev/null +++ b/spec/factories/system_uptimes.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :system_uptime do + association :system + sequence(:online_at_day) { Time.zone.now } + online_at_hours { '111111111111111111111111' } + end +end diff --git a/spec/factories/systems.rb b/spec/factories/systems.rb index 53cf16ff0..d826bb0f3 100644 --- a/spec/factories/systems.rb +++ b/spec/factories/systems.rb @@ -65,5 +65,11 @@ trait :with_system_token do sequence(:system_token) { |n| "00000000-0000-4000-9000-#{n.to_s.rjust(12, '0')}" } end + + trait :with_system_uptimes do + after :create do |system, _| + create(:system_uptime, system: system) + end + end end end diff --git a/spec/lib/suse/connect/system_serializer_spec.rb b/spec/lib/suse/connect/system_serializer_spec.rb index a77bd7fab..7187df834 100644 --- a/spec/lib/suse/connect/system_serializer_spec.rb +++ b/spec/lib/suse/connect/system_serializer_spec.rb @@ -86,4 +86,13 @@ expect(serializer.key? :system_token).to eq(false) end end + + context 'system with systemuptime' do + let(:system) { create :system, :with_system_uptimes } + + it 'match systemuptime data' do + expect((serializer[:systemuptimes][0][:online_at_day]).to_date).to eq(Time.zone.now.to_date) + expect((serializer[:systemuptimes][0][:online_at_hours]).to_s).to eq('111111111111111111111111') + end + end end diff --git a/spec/models/system_uptime_spec.rb b/spec/models/system_uptime_spec.rb new file mode 100644 index 000000000..84d2c2f68 --- /dev/null +++ b/spec/models/system_uptime_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +RSpec.describe SystemUptime, type: :model do + it { is_expected.to validate_presence_of(:system_id) } + it { is_expected.to validate_presence_of(:online_at_day) } + it { is_expected.to validate_presence_of(:online_at_hours) } +end diff --git a/spec/requests/api/connect/v3/systems/systems_controller_spec.rb b/spec/requests/api/connect/v3/systems/systems_controller_spec.rb index f029c5dff..cac33c73b 100644 --- a/spec/requests/api/connect/v3/systems/systems_controller_spec.rb +++ b/spec/requests/api/connect/v3/systems/systems_controller_spec.rb @@ -18,6 +18,8 @@ } end let(:payload) { { hostname: 'test', hwinfo: hwinfo } } + let(:systemuptime) { system.system_uptimes.first } + let(:online_hours) { ':111111111111111111111111' } describe '#update' do subject(:update_action) { put url, params: payload, headers: headers } @@ -45,6 +47,41 @@ expect(information[:cpus]).to eq('16') end end + + context 'when uptime data provided' do + let(:payload) { { hostname: 'test', hwinfo: hwinfo, online_at: [1.day.ago.to_date.to_s << online_hours] } } + + it 'inserts the uptime data in system_uptimes table' do + update_action + + expect(systemuptime.system_id).to eq(system.reload.id) + expect(systemuptime.online_at_day.to_date).to eq(1.day.ago.to_date) + expect(systemuptime.online_at_hours.to_s).to eq('111111111111111111111111') + end + end + + context 'when same uptime data added twice' do + let(:payload) { { hostname: 'test', hwinfo: hwinfo, online_at: [1.day.ago.to_date.to_s << online_hours, 1.day.ago.to_date.to_s << online_hours] } } + + it 'avoids duplication if multiple records have same data' do + update_action + + expect(system.system_uptimes.count).to eq(1) + expect(systemuptime.system_id).to eq(system.reload.id) + expect(systemuptime.online_at_day.to_date).to eq(1.day.ago.to_date) + expect(systemuptime.online_at_hours.to_s).to eq('111111111111111111111111') + end + end + + context 'when uptime data is malformed' do + let(:payload) { { hostname: 'test', hwinfo: hwinfo, online_at: [1.day.ago.to_date.to_s] } } + + it 'record is not inserted' do + update_action + + expect(system.system_uptimes.count).to eq(0) + end + end end context 'when hostname is not provided' do