blob: 25147c31b9746b718cd9e26471be8e1ed10300ea [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package versioning
import (
"context"
"errors"
"sort"
"strconv"
"strings"
"time"
"github.com/apache/dubbo-admin/pkg/common/bizerror"
coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
)
// Service provides rule history operations. RuleVersion entries are audit
// records and rollback material; live current state is still owned by
// ResourceManager and the backing registry.
type Service struct {
maxVersions int64
store *ResourceStoreAdapter
}
func NewService(maxVersions int64, store *ResourceStoreAdapter) *Service {
return &Service{
maxVersions: maxVersions,
store: store,
}
}
func (s *Service) ensureAvailable() error {
if s == nil || s.store == nil {
return ErrVersionStoreError
}
return nil
}
func (s *Service) List(kind coremodel.ResourceKind, mesh, ruleName string) (*ListResult, error) {
if err := s.ensureAvailable(); err != nil {
return nil, err
}
resourceKey := coremodel.BuildResourceKey(mesh, ruleName)
snapshot, err := s.store.HistorySnapshot(kind, resourceKey)
if err != nil {
return nil, err
}
result := &ListResult{Items: snapshot.Versions, Total: int64(len(snapshot.Versions)), LatestRecordedDeleted: snapshot.Deleted}
if snapshot.Head != nil {
result.LatestRecordedVersionNo = snapshot.Head.VersionNo
}
return result, nil
}
func (s *Service) Get(kind coremodel.ResourceKind, mesh, ruleName string, versionNo int64) (*Version, error) {
if err := s.ensureAvailable(); err != nil {
return nil, err
}
resourceKey := coremodel.BuildResourceKey(mesh, ruleName)
version, err := s.store.GetVersion(kind, resourceKey, versionNo)
if err != nil {
return nil, err
}
snapshot, err := s.store.HistorySnapshot(kind, resourceKey)
if err != nil {
return nil, err
}
if snapshot.Head != nil {
version.IsLatestRecorded = version.VersionNo == snapshot.Head.VersionNo
}
return version, nil
}
func (s *Service) DiffHistoryVersions(kind coremodel.ResourceKind, mesh, ruleName string, versionNo int64, against string) (*DiffResult, error) {
if err := s.ensureAvailable(); err != nil {
return nil, err
}
left, err := s.Get(kind, mesh, ruleName, versionNo)
if err != nil {
return nil, err
}
right, err := s.diffRight(kind, mesh, ruleName, versionNo, against)
if err != nil {
return nil, err
}
return &DiffResult{
Left: DiffSide{VersionNo: left.VersionNo, SpecJSON: left.SpecJSON},
Right: DiffSide{VersionNo: right.VersionNo, SpecJSON: right.SpecJSON},
}, nil
}
func (s *Service) diffRight(kind coremodel.ResourceKind, mesh, ruleName string, versionNo int64, against string) (*Version, error) {
switch against {
case "previous":
list, err := s.store.ListVersions(kind, coremodel.BuildResourceKey(mesh, ruleName))
if err != nil {
return nil, err
}
sort.Slice(list, func(i, j int) bool {
return list[i].VersionNo > list[j].VersionNo
})
for i := range list {
if list[i].VersionNo != versionNo {
continue
}
if i+1 >= len(list) {
return nil, ErrVersionNotFound
}
return &list[i+1], nil
}
return nil, ErrVersionNotFound
case "", "current":
return nil, bizerror.New(bizerror.InvalidArgument, "current diff requires live ResourceManager state and is implemented by the console service")
default:
againstVersionNo, err := strconv.ParseInt(against, 10, 64)
if err != nil || againstVersionNo <= 0 {
return nil, bizerror.New(bizerror.InvalidArgument, "against must be 'current', 'previous', or a version number")
}
return s.Get(kind, mesh, ruleName, againstVersionNo)
}
}
func (s *Service) Append(ctx context.Context, res coremodel.Resource, op Operation, source Source, author, reason string, rolledBackFromVersionNo *int64) (*Version, error) {
if err := s.ensureAvailable(); err != nil {
return nil, err
}
req, err := BuildInsertRequest(res, op, source, author, reason, rolledBackFromVersionNo, time.Now())
if err != nil {
return nil, err
}
return s.store.InsertVersion(ctx, req, s.maxVersions)
}
func (s *Service) LatestVersion(kind coremodel.ResourceKind, mesh, ruleName string) (*Version, error) {
if err := s.ensureAvailable(); err != nil {
return nil, err
}
return s.store.LatestVersion(kind, coremodel.BuildResourceKey(mesh, ruleName))
}
func (s *Service) HasHistory(kind coremodel.ResourceKind, mesh, ruleName string) (bool, error) {
if err := s.ensureAvailable(); err != nil {
return false, err
}
_, err := s.store.LatestVersion(kind, coremodel.BuildResourceKey(mesh, ruleName))
if err == nil {
return true, nil
}
if errors.Is(err, ErrVersionNotFound) {
return false, nil
}
return false, err
}
func BuildInsertRequest(res coremodel.Resource, op Operation, source Source, author, reason string, rolledBackFromVersionNo *int64, createdAt time.Time) (InsertRequest, error) {
if res == nil {
return InsertRequest{}, bizerror.New(bizerror.InvalidArgument, "rule resource is required")
}
hash, specJSON, err := NormalizeResource(res)
if err != nil {
return InsertRequest{}, err
}
if op == OperationDelete {
specJSON = DeleteSpecJSON
hash = HashSpecJSON(specJSON)
}
if strings.TrimSpace(author) == "" {
author = "system:unknown"
} else {
author = strings.TrimSpace(author)
}
if source == "" {
source = SourceAdmin
}
return InsertRequest{
RuleKind: res.ResourceKind(),
Mesh: res.ResourceMesh(),
ResourceKey: res.ResourceKey(),
RuleName: res.ResourceMeta().Name,
SpecJSON: specJSON,
ContentHash: hash,
Source: source,
Operation: op,
Author: author,
Reason: reason,
RolledBackFromVersionNo: rolledBackFromVersionNo,
CreatedAt: createdAt,
}, nil
}