Files
distcloud/distributedcloud/dcorch/db/sqlalchemy/models.py
Raphael Lima ded7177edd Add subsequent_sync flag to dcorch
This commit introduces the subsequent_sync flag to dcorch in order to
skip the initial_sync execution when the subcloud is already in a
managed state. Previously, an online event would trigger the whole sync
sequence to be executed again. With this change, the costly initial sync
step of the sync sequence is skipped for online event.

Test plan:
1. PASS: Unmanage a subcloud and verify that the initial_sync_state is
   updated to none and subsequent_sync is set to false.
2. PASS: Manage a subcloud and verify that the initial_sync_state is
   requested and, after completed, the subsequent_sync is set to
   True.
3. PASS: Turn off a managed subcloud and verify that initial_sync_state
   is updated to none and subsequent_sync remains the same.
4. PASS: Turn on a managed subcloud and verify that initial_sync_state
   is updated to requested and it skips the initial_sync in the
   execution.
5. PASS: Turn off an umanaged subcloud and verify that
   initial_sync_state is set to none and subsequent_sync is set to
   false.
6. PASS: Turn on an unmanaged subcloud and verify that
   initial_sync_state is set to none and subsequent_sync is set to
   false.
7. PASS: Apply the database migration in an existing system with
   sudo dcorch-manage db_sync 010 and verify that the subsequent_sync
   column is filled with false or true according to the subcloud's
   management state and initial_sync_state.
8. PASS: Manage a subcloud and verify that the sysinv client is
   correctly created for the subcloud.

Story: 2011106
Task: 50790

Change-Id: I3aecdf1c172a08e5c38f80b84940e377f6290d3a
Signed-off-by: Raphael Lima <Raphael.Lima@windriver.com>
2024-08-14 10:14:45 -03:00

326 lines
11 KiB
Python

# Copyright (c) 2015 Ericsson AB
# Copyright (c) 2017-2022, 2024 Wind River Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
SQLAlchemy models for dcorch data.
"""
import json
from oslo_db.sqlalchemy import models
from sqlalchemy.orm import session as orm_session
from sqlalchemy import Column, Integer, String, Boolean, Index, schema, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import ForeignKey, UniqueConstraint
from sqlalchemy.types import TypeDecorator, VARCHAR
from sqlalchemy.orm import relationship
from dccommon import consts as dccommon_consts
from dcorch.common import consts
BASE = declarative_base()
def get_session():
from dcorch.db.sqlalchemy import api as db_api
return db_api.get_session()
class JSONEncodedDict(TypeDecorator):
"""Represents an immutable structure as a json-encoded string."""
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
class OrchestratorBase(models.ModelBase, models.SoftDeleteMixin, models.TimestampMixin):
"""Base class for Orchestrator Models."""
__table_args__ = {"mysql_engine": "InnoDB"}
def expire(self, session=None, attrs=None):
if not session:
session = orm_session.Session.object_session(self)
if not session:
session = get_session()
session.expire(self, attrs)
def refresh(self, session=None, attrs=None):
"""Refresh this object."""
if not session:
session = orm_session.Session.object_session(self)
if not session:
session = get_session()
session.refresh(self, attrs)
def delete(self, session=None):
"""Delete this object."""
if not session:
session = orm_session.Session.object_session(self)
if not session:
session = get_session()
session.begin()
session.delete(self)
session.commit()
class Quota(BASE, OrchestratorBase):
"""Represents a single quota override for a project.
If there is no row for a given project id and resource, then the
default for the quota class is used. If there is no row for a
given quota class and resource, then the default for the
deployment is used. If the row is present but the hard limit is
Null, then the resource is unlimited.
"""
__tablename__ = "quotas"
__table_args__ = (
schema.UniqueConstraint(
"project_id",
"resource",
"deleted",
name="uniq_quotas0project_id0resource0deleted",
),
)
id = Column(Integer, primary_key=True)
project_id = Column(String(255), index=True)
resource = Column(String(255), nullable=False)
hard_limit = Column(Integer, nullable=True)
capabilities = Column(JSONEncodedDict)
class QuotaClass(BASE, OrchestratorBase):
"""Represents a single quota override for a quota class.
If there is no row for a given quota class and resource, then the
default for the deployment is used. If the row is present but the
hard limit is Null, then the resource is unlimited.
"""
__tablename__ = "quota_classes"
id = Column(Integer, primary_key=True)
class_name = Column(String(255), index=True)
resource = Column(String(255))
hard_limit = Column(Integer, nullable=True)
capabilities = Column(JSONEncodedDict)
class Service(BASE, OrchestratorBase):
"""Orchestrator service engine registry"""
__tablename__ = "service"
id = Column("id", String(36), primary_key=True, nullable=False)
host = Column(String(255))
binary = Column(String(255))
topic = Column(String(255))
disabled = Column(Boolean, default=False)
disabled_reason = Column(String(255))
capabilities = Column(JSONEncodedDict)
# Distributed Cloud Orchestrator Data Base Models
class Subcloud(BASE, OrchestratorBase):
"""Represents a Distributed Cloud subcloud"""
__tablename__ = "subcloud"
__table_args__ = (Index("subcloud_region_name_idx", "region_name"),)
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
uuid = Column(String(36), unique=True)
region_name = Column("region_name", String(255), unique=True) # keystone
software_version = Column("software_version", String(255))
# dc manager updates the management and availability
# default management_state is None; could be set to 'deleting'
management_state = Column("management_state", String(64))
availability_status = Column(
"availability_status", String(64), default=dccommon_consts.AVAILABILITY_OFFLINE
)
capabilities = Column(JSONEncodedDict)
initial_sync_state = Column(
"initial_sync_state", String(64), default=consts.INITIAL_SYNC_STATE_NONE
)
subsequent_sync = Column("subsequent_sync", Boolean, default=False)
management_ip = Column("management_ip", String(64))
class Resource(BASE, OrchestratorBase):
"""Represents a Distributed Cloud Orchestrator Resource"""
__tablename__ = "resource"
__table_args__ = (
Index("resource_resource_type_idx", "resource_type"),
Index("resource_master_id_idx", "master_id"),
UniqueConstraint(
"resource_type",
"master_id",
"deleted",
name="uniq_resource0resource_type0master_id0deleted",
),
)
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
uuid = Column(String(36), unique=True)
capabilities = Column(JSONEncodedDict)
resource_type = Column(String(128)) # e.g. quota_x,flavor_extra_spec,dns..
master_id = Column(String(255)) # id/uuid of resource in central region
class SubcloudResource(BASE, OrchestratorBase):
"""Represents a Distributed Cloud Orchestrator Subcloud Resource"""
__tablename__ = "subcloud_resource"
__table_args__ = (
Index("subcloud_resource_resource_id_idx", "resource_id"),
UniqueConstraint(
"resource_id",
"subcloud_id",
name="uniq_subcloud_resource0resource_id0subcloud_id",
),
)
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
uuid = Column(String(36), unique=True)
# Could get subcloud_name (or target_region) from subcloud.region_name
# subcloud_name = Column('subcloud_name', String(255))
# Is this resource managed or unmanaged by orchestrator for some subcloud
shared_config_state = Column("shared_config_state", String(255), default="managed")
capabilities = Column(JSONEncodedDict)
subcloud_resource_id = Column(String(255)) # usually uuid, sometimes id
# if either resource_id or subcloud_id is set as primary key, id does not
# autoincrement
resource_id = Column(
"resource_id", Integer, ForeignKey("resource.id", ondelete="CASCADE")
)
subcloud_id = Column(
"subcloud_id", Integer, ForeignKey("subcloud.id", ondelete="CASCADE")
)
# todo: we shouldn't allow more than one row to have the same
# resource_id/subcloud_id tuple
class OrchJob(BASE, OrchestratorBase):
"""Orchestrator Job registry"""
__tablename__ = "orch_job"
__table_args__ = (Index("orch_job_endpoint_type_idx", "endpoint_type"),)
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
uuid = Column(String(36), unique=True)
user_id = Column("user_id", String(128))
project_id = Column("project_id", String(128))
# Filled in by x_orch_api e.g. platform, volumev2, compute, network
endpoint_type = Column(String(255), nullable=False)
# e.g. quota_x, flavors, flavor_extra_spec, idns
# resource_type = Column(String(255), nullable=False)
source_resource_id = Column(String(255)) # for debugging
operation_type = Column(String(255)) # http type: post/put/patch/delete
capabilities = Column(JSONEncodedDict)
resource_id = Column(
"resource_id", Integer, ForeignKey("resource.id")
) # nullable=False?
# resource_info cannot be derived from resource.master_values
# Represents resource info for a specific API call. In case of update, it
# may only be adding a specific k/v pair to an existing resource. Also, we
# need to ensure order of operations in the subcloud matches order of
# operations in the master cloud. This is a string representing a JSON-
# formatted dict. The exact contents will vary depending on resource.
resource_info = Column(String())
orchrequests = relationship("OrchRequest", backref="orch_job")
# orch_status can be derived from the underlying OrchRequests state
class OrchRequest(BASE, OrchestratorBase):
__tablename__ = "orch_request"
__table_args__ = (
Index("orch_request_state_idx", "state"),
UniqueConstraint(
"target_region_name",
"orch_job_id",
"deleted",
name="uniq_orchreq0target_region_name0orch_job_id0deleted",
),
)
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
uuid = Column(String(36), unique=True)
# state updated by engine one of: "in-progress", "completed", "failed",
# "timed-out", "aborted"
state = Column(String(128), default=consts.ORCH_REQUEST_NONE)
try_count = Column(Integer, default=0)
api_version = Column(String(128))
target_region_name = Column(String(255))
capabilities = Column(JSONEncodedDict)
orch_job_id = Column(
"orch_job_id", Integer, ForeignKey("orch_job.id"), primary_key=True
)
class SubcloudSync(BASE, OrchestratorBase):
"""Store subcloud sync information to allow coordination of dcorch workload"""
__tablename__ = "subcloud_sync"
id = Column(Integer, primary_key=True)
subcloud_id = Column(
"subcloud_id", Integer, ForeignKey("subcloud.id", ondelete="CASCADE")
)
subcloud_name = Column(String(255), nullable=False)
endpoint_type = Column(String(255), default="none")
sync_request = Column(String(64), default=consts.SYNC_STATUS_NONE)
sync_status_reported = Column(String(64), default=consts.SYNC_STATUS_NONE)
sync_status_report_time = Column(DateTime(timezone=False))
audit_status = Column(String(64), default=consts.AUDIT_STATUS_NONE)
last_audit_time = Column(DateTime(timezone=False))