Add names to service and router. Add more store tests. Fixes GH-101

This commit is contained in:
Paul Lorenz
2020-06-18 14:36:58 -04:00
parent 68fd06d4e6
commit ffbb4e4cc3
17 changed files with 613 additions and 142 deletions
+4
View File
@@ -20,6 +20,10 @@ import (
"github.com/openziti/foundation/storage/boltz"
)
const (
FieldName = "name"
)
type store interface {
boltz.CrudStore
}
+22 -1
View File
@@ -9,7 +9,7 @@ import (
"time"
)
const CurrentDbVersion = 2
const CurrentDbVersion = 3
func (stores *stores) migrate(step *boltz.MigrationStep) int {
if step.CurrentVersion > CurrentDbVersion {
@@ -25,6 +25,11 @@ func (stores *stores) migrate(step *boltz.MigrationStep) int {
stores.extractTerminators(step)
}
if step.CurrentVersion < 3 {
stores.setNames(step, stores.service)
stores.setNames(step, stores.router)
}
if step.CurrentVersion <= CurrentDbVersion {
return CurrentDbVersion
}
@@ -56,6 +61,22 @@ func (stores *stores) initCreatedAtUpdatedAt(step *boltz.MigrationStep, now time
}
}
func (stores *stores) setNames(step *boltz.MigrationStep, store boltz.CrudStore) {
ids, _, err := store.QueryIds(step.Ctx.Tx(), "true")
step.SetError(err)
for _, id := range ids {
entityBucket := store.GetEntityBucket(step.Ctx.Tx(), []byte(id))
if entityBucket == nil {
step.SetError(errors.Errorf("could not get entity bucket for %v with id %v", store.GetSingularEntityType(), id))
return
}
if name := entityBucket.GetString(FieldName); name == nil || len(*name) == 0 {
entityBucket.SetString(FieldName, string(id), nil)
step.SetError(entityBucket.GetError())
}
}
}
const (
FieldServiceEgress = "egress"
FieldServiceBinding = "binding"
+22
View File
@@ -32,16 +32,19 @@ const (
type Router struct {
boltz.BaseExtEntity
Name string
Fingerprint *string
}
func (entity *Router) LoadValues(_ boltz.CrudStore, bucket *boltz.TypedBucket) {
entity.LoadBaseValues(bucket)
entity.Name = bucket.GetStringOrError(FieldName)
entity.Fingerprint = bucket.GetString(FieldRouterFingerprint)
}
func (entity *Router) SetValues(ctx *boltz.PersistContext) {
entity.SetBaseValues(ctx)
ctx.SetString(FieldName, entity.Name)
ctx.SetStringP(FieldRouterFingerprint, entity.Fingerprint)
}
@@ -51,7 +54,9 @@ func (entity *Router) GetEntityType() string {
type RouterStore interface {
boltz.CrudStore
GetNameIndex() boltz.ReadIndex
LoadOneById(tx *bbolt.Tx, id string) (*Router, error)
LoadOneByName(tx *bbolt.Tx, id string) (*Router, error)
}
func newRouterStore(stores *stores) *routerStoreImpl {
@@ -71,11 +76,16 @@ func newRouterStore(stores *stores) *routerStoreImpl {
type routerStoreImpl struct {
baseStore
indexName boltz.ReadIndex
terminatorsSymbol boltz.EntitySetSymbol
}
func (store *routerStoreImpl) initializeLocal() {
store.AddExtEntitySymbols()
symbolName := store.AddSymbol(FieldName, ast.NodeTypeString)
store.indexName = store.AddUniqueIndex(symbolName)
store.AddSymbol(FieldRouterFingerprint, ast.NodeTypeString)
store.terminatorsSymbol = store.AddFkSetSymbol(EntityTypeTerminators, store.stores.terminator)
}
@@ -83,6 +93,10 @@ func (store *routerStoreImpl) initializeLocal() {
func (store *routerStoreImpl) initializeLinked() {
}
func (store *routerStoreImpl) GetNameIndex() boltz.ReadIndex {
return store.indexName
}
func (store *routerStoreImpl) NewStoreEntity() boltz.Entity {
return &Router{}
}
@@ -95,6 +109,14 @@ func (store *routerStoreImpl) LoadOneById(tx *bbolt.Tx, id string) (*Router, err
return entity, nil
}
func (store *routerStoreImpl) LoadOneByName(tx *bbolt.Tx, name string) (*Router, error) {
id := store.indexName.Read(tx, []byte(name))
if id != nil {
return store.LoadOneById(tx, string(id))
}
return nil, nil
}
func (store *routerStoreImpl) DeleteById(ctx boltz.MutateContext, id string) error {
terminatorIds := store.GetRelatedEntitiesIdList(ctx.Tx(), id, EntityTypeTerminators)
for _, terminatorId := range terminatorIds {
+177
View File
@@ -0,0 +1,177 @@
/*
Copyright NetFoundry, Inc.
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
https://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 db
import (
"fmt"
"github.com/google/uuid"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/openziti/foundation/storage/boltz"
"go.etcd.io/bbolt"
)
func Test_RouterStore(t *testing.T) {
ctx := NewTestContext(t)
defer ctx.Cleanup()
ctx.Init()
t.Run("test create invalid api routers", ctx.testCreateInvalidRouters)
t.Run("test create router", ctx.testCreateRouters)
t.Run("test load/query routers", ctx.testLoadQueryRouters)
t.Run("test update routers", ctx.testUpdateRouters)
t.Run("test delete routers", ctx.testDeleteRouters)
}
func (ctx *TestContext) testCreateInvalidRouters(t *testing.T) {
ctx.Impl.NextTest(t)
defer ctx.cleanupAll()
router := &Router{
BaseExtEntity: boltz.BaseExtEntity{Id: uuid.New().String()},
Name: uuid.New().String(),
}
ctx.RequireCreate(router)
err := ctx.Create(router)
ctx.EqualError(err, fmt.Sprintf("an entity of type router already exists with id %v", router.Id))
router.Id = uuid.New().String()
err = ctx.Create(router)
ctx.EqualError(err, fmt.Sprintf("duplicate value '%v' in unique index on routers store", router.Name))
}
func (ctx *TestContext) testCreateRouters(t *testing.T) {
ctx.Impl.NextTest(t)
defer ctx.cleanupAll()
router := &Router{
BaseExtEntity: boltz.BaseExtEntity{Id: uuid.New().String()},
Name: uuid.New().String(),
}
ctx.RequireCreate(router)
ctx.ValidateBaseline(router)
}
type routerTestEntities struct {
router1 *Router
router2 *Router
terminator *Terminator
service *Service
}
func (ctx *TestContext) createRouterTestEntities() *routerTestEntities {
router1 := ctx.requireNewRouter()
router2 := ctx.requireNewRouter()
service := ctx.requireNewService()
terminator := &Terminator{
BaseExtEntity: boltz.BaseExtEntity{
Id: uuid.New().String(),
},
Service: service.Id,
Router: router1.Id,
Binding: "transport",
Address: "tcp:localhost:22",
}
ctx.RequireCreate(terminator)
return &routerTestEntities{
router1: router1,
router2: router2,
service: service,
terminator: terminator,
}
}
func (ctx *TestContext) testLoadQueryRouters(t *testing.T) {
ctx.Impl.NextTest(t)
ctx.cleanupAll()
entities := ctx.createRouterTestEntities()
err := ctx.GetDb().View(func(tx *bbolt.Tx) error {
router, err := ctx.stores.Router.LoadOneById(tx, entities.router1.Id)
ctx.NoError(err)
ctx.NotNil(router)
ctx.EqualValues(entities.router1.Id, router.Id)
router, err = ctx.stores.Router.LoadOneByName(tx, entities.router1.Name)
ctx.NoError(err)
ctx.NotNil(router)
ctx.EqualValues(entities.router1.Id, router.Id)
query := fmt.Sprintf(`anyOf(terminators) = "%v"`, entities.terminator.Id)
ids, _, err := ctx.stores.Router.QueryIds(tx, query)
ctx.NoError(err)
ctx.EqualValues(1, len(ids))
ctx.Equal(entities.router1.Id, ids[0])
return nil
})
ctx.NoError(err)
}
func (ctx *TestContext) testUpdateRouters(t *testing.T) {
ctx.Impl.NextTest(t)
ctx.cleanupAll()
entities := ctx.createRouterTestEntities()
earlier := time.Now()
time.Sleep(time.Millisecond * 50)
err := ctx.GetDb().Update(func(tx *bbolt.Tx) error {
original, err := ctx.stores.Router.LoadOneById(tx, entities.router1.Id)
ctx.NoError(err)
ctx.NotNil(original)
router, err := ctx.stores.Router.LoadOneById(tx, entities.router1.Id)
ctx.NoError(err)
ctx.NotNil(router)
tags := ctx.CreateTags()
now := time.Now()
router.Name = uuid.New().String()
router.UpdatedAt = earlier
router.CreatedAt = now
router.Tags = tags
err = ctx.stores.Router.Update(boltz.NewMutateContext(tx), router, nil)
ctx.NoError(err)
loaded, err := ctx.stores.Router.LoadOneById(tx, entities.router1.Id)
ctx.NoError(err)
ctx.NotNil(loaded)
ctx.EqualValues(original.CreatedAt, loaded.CreatedAt)
ctx.True(loaded.UpdatedAt.Equal(now) || loaded.UpdatedAt.After(now))
router.CreatedAt = loaded.CreatedAt
router.UpdatedAt = loaded.UpdatedAt
ctx.True(cmp.Equal(router, loaded), cmp.Diff(router, loaded))
return nil
})
ctx.NoError(err)
}
func (ctx *TestContext) testDeleteRouters(t *testing.T) {
ctx.Impl.NextTest(t)
ctx.cleanupAll()
entities := ctx.createRouterTestEntities()
ctx.RequireDelete(entities.router1)
ctx.RequireDelete(entities.router2)
}
+22
View File
@@ -31,16 +31,19 @@ const (
type Service struct {
boltz.BaseExtEntity
Name string
TerminatorStrategy string
}
func (entity *Service) LoadValues(_ boltz.CrudStore, bucket *boltz.TypedBucket) {
entity.LoadBaseValues(bucket)
entity.Name = bucket.GetStringOrError(FieldName)
entity.TerminatorStrategy = bucket.GetStringWithDefault(FieldServiceTerminatorStrategy, "")
}
func (entity *Service) SetValues(ctx *boltz.PersistContext) {
entity.SetBaseValues(ctx)
ctx.SetString(FieldName, entity.Name)
if entity.TerminatorStrategy == "" {
entity.TerminatorStrategy = xt_smartrouting.Name
}
@@ -69,7 +72,9 @@ func (entity *Service) GetEntityType() string {
type ServiceStore interface {
store
GetNameIndex() boltz.ReadIndex
LoadOneById(tx *bbolt.Tx, id string) (*Service, error)
LoadOneByName(tx *bbolt.Tx, name string) (*Service, error)
}
func newServiceStore(stores *stores) *serviceStoreImpl {
@@ -89,11 +94,16 @@ func newServiceStore(stores *stores) *serviceStoreImpl {
type serviceStoreImpl struct {
baseStore
indexName boltz.ReadIndex
terminatorsSymbol boltz.EntitySetSymbol
}
func (store *serviceStoreImpl) initializeLocal() {
store.AddExtEntitySymbols()
symbolName := store.AddSymbol(FieldName, ast.NodeTypeString)
store.indexName = store.AddUniqueIndex(symbolName)
store.AddSymbol(FieldServiceTerminatorStrategy, ast.NodeTypeString)
store.terminatorsSymbol = store.AddFkSetSymbol(EntityTypeTerminators, store.stores.terminator)
}
@@ -101,6 +111,10 @@ func (store *serviceStoreImpl) initializeLocal() {
func (store *serviceStoreImpl) initializeLinked() {
}
func (store *serviceStoreImpl) GetNameIndex() boltz.ReadIndex {
return store.indexName
}
func (store *serviceStoreImpl) NewStoreEntity() boltz.Entity {
return &Service{}
}
@@ -113,6 +127,14 @@ func (store *serviceStoreImpl) LoadOneById(tx *bbolt.Tx, id string) (*Service, e
return entity, nil
}
func (store *serviceStoreImpl) LoadOneByName(tx *bbolt.Tx, name string) (*Service, error) {
id := store.indexName.Read(tx, []byte(name))
if id != nil {
return store.LoadOneById(tx, string(id))
}
return nil, nil
}
func (store *serviceStoreImpl) DeleteById(ctx boltz.MutateContext, id string) error {
terminatorIds := store.GetRelatedEntitiesIdList(ctx.Tx(), id, EntityTypeTerminators)
for _, terminatorId := range terminatorIds {
+183
View File
@@ -0,0 +1,183 @@
/*
Copyright NetFoundry, Inc.
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
https://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 db
import (
"fmt"
"github.com/google/uuid"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/openziti/foundation/storage/boltz"
"go.etcd.io/bbolt"
)
func Test_ServiceStore(t *testing.T) {
ctx := NewTestContext(t)
defer ctx.Cleanup()
ctx.Init()
t.Run("test create invalid api services", ctx.testCreateInvalidServices)
t.Run("test create service", ctx.testCreateServices)
t.Run("test load/query services", ctx.testLoadQueryServices)
t.Run("test update services", ctx.testUpdateServices)
t.Run("test delete services", ctx.testDeleteServices)
}
func (ctx *TestContext) testCreateInvalidServices(t *testing.T) {
ctx.Impl.NextTest(t)
defer ctx.cleanupAll()
service := &Service{
BaseExtEntity: boltz.BaseExtEntity{Id: uuid.New().String()},
Name: uuid.New().String(),
}
ctx.RequireCreate(service)
err := ctx.Create(service)
ctx.EqualError(err, fmt.Sprintf("an entity of type service already exists with id %v", service.Id))
service.Id = uuid.New().String()
err = ctx.Create(service)
ctx.EqualError(err, fmt.Sprintf("duplicate value '%v' in unique index on services store", service.Name))
service.Id = uuid.New().String()
service.Name = uuid.New().String()
service.TerminatorStrategy = uuid.New().String()
err = ctx.Create(service)
ctx.EqualError(err, fmt.Sprintf("terminatorStrategy with name %v not found", service.TerminatorStrategy))
}
func (ctx *TestContext) testCreateServices(t *testing.T) {
ctx.Impl.NextTest(t)
defer ctx.cleanupAll()
service := &Service{
BaseExtEntity: boltz.BaseExtEntity{Id: uuid.New().String()},
Name: uuid.New().String(),
}
ctx.RequireCreate(service)
ctx.ValidateBaseline(service)
}
type serviceTestEntities struct {
service1 *Service
service2 *Service
terminator *Terminator
router *Router
}
func (ctx *TestContext) createServiceTestEntities() *serviceTestEntities {
service1 := ctx.requireNewService()
service2 := ctx.requireNewService()
router := ctx.requireNewRouter()
terminator := &Terminator{
BaseExtEntity: boltz.BaseExtEntity{
Id: uuid.New().String(),
},
Service: service1.Id,
Router: router.Id,
Binding: "transport",
Address: "tcp:localhost:22",
}
ctx.RequireCreate(terminator)
return &serviceTestEntities{
service1: service1,
service2: service2,
router: router,
terminator: terminator,
}
}
func (ctx *TestContext) testLoadQueryServices(t *testing.T) {
ctx.Impl.NextTest(t)
ctx.cleanupAll()
entities := ctx.createServiceTestEntities()
err := ctx.GetDb().View(func(tx *bbolt.Tx) error {
service, err := ctx.stores.Service.LoadOneById(tx, entities.service1.Id)
ctx.NoError(err)
ctx.NotNil(service)
ctx.EqualValues(entities.service1.Id, service.Id)
service, err = ctx.stores.Service.LoadOneByName(tx, entities.service1.Name)
ctx.NoError(err)
ctx.NotNil(service)
ctx.EqualValues(entities.service1.Id, service.Id)
query := fmt.Sprintf(`anyOf(terminators) = "%v"`, entities.terminator.Id)
ids, _, err := ctx.stores.Service.QueryIds(tx, query)
ctx.NoError(err)
ctx.EqualValues(1, len(ids))
ctx.Equal(entities.service1.Id, ids[0])
return nil
})
ctx.NoError(err)
}
func (ctx *TestContext) testUpdateServices(t *testing.T) {
ctx.Impl.NextTest(t)
ctx.cleanupAll()
entities := ctx.createServiceTestEntities()
earlier := time.Now()
time.Sleep(time.Millisecond * 50)
err := ctx.GetDb().Update(func(tx *bbolt.Tx) error {
original, err := ctx.stores.Service.LoadOneById(tx, entities.service1.Id)
ctx.NoError(err)
ctx.NotNil(original)
service, err := ctx.stores.Service.LoadOneById(tx, entities.service1.Id)
ctx.NoError(err)
ctx.NotNil(service)
tags := ctx.CreateTags()
now := time.Now()
service.Name = uuid.New().String()
service.UpdatedAt = earlier
service.CreatedAt = now
service.Tags = tags
err = ctx.stores.Service.Update(boltz.NewMutateContext(tx), service, nil)
ctx.NoError(err)
loaded, err := ctx.stores.Service.LoadOneById(tx, entities.service1.Id)
ctx.NoError(err)
ctx.NotNil(loaded)
ctx.EqualValues(original.CreatedAt, loaded.CreatedAt)
ctx.True(loaded.UpdatedAt.Equal(now) || loaded.UpdatedAt.After(now))
service.CreatedAt = loaded.CreatedAt
service.UpdatedAt = loaded.UpdatedAt
ctx.True(cmp.Equal(service, loaded), cmp.Diff(service, loaded))
return nil
})
ctx.NoError(err)
}
func (ctx *TestContext) testDeleteServices(t *testing.T) {
ctx.Impl.NextTest(t)
ctx.cleanupAll()
entities := ctx.createServiceTestEntities()
ctx.RequireDelete(entities.service1)
ctx.RequireDelete(entities.service2)
}
+6
View File
@@ -3,12 +3,16 @@ package db
import (
"github.com/google/uuid"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/fabric/controller/xt"
"github.com/openziti/fabric/controller/xt_smartrouting"
"github.com/openziti/foundation/storage/boltz"
"go.etcd.io/bbolt"
"testing"
)
func NewTestContext(t *testing.T) *TestContext {
xt.GlobalRegistry().RegisterFactory(xt_smartrouting.NewFactory())
context := &TestContext{
BaseTestContext: *boltz.NewTestContext(t),
}
@@ -45,6 +49,7 @@ func (ctx *TestContext) Init() {
func (ctx *TestContext) requireNewService() *Service {
entity := &Service{
BaseExtEntity: boltz.BaseExtEntity{Id: uuid.New().String()},
Name: uuid.New().String(),
}
ctx.RequireCreate(entity)
return entity
@@ -53,6 +58,7 @@ func (ctx *TestContext) requireNewService() *Service {
func (ctx *TestContext) requireNewRouter() *Router {
entity := &Router{
BaseExtEntity: boltz.BaseExtEntity{Id: uuid.New().String()},
Name: uuid.New().String(),
}
ctx.RequireCreate(entity)
return entity
+1 -1
View File
@@ -42,7 +42,7 @@ func (h *createRouterHandler) HandleReceive(msg *channel2.Message, ch channel2.C
create := &mgmt_pb.CreateRouterRequest{}
if err := proto.Unmarshal(msg.Body, create); err == nil {
r := network.NewRouter(create.Router.Id, create.Router.Fingerprint)
r := network.NewRouter(create.Router.Id, create.Router.Name, create.Router.Fingerprint)
if err := h.network.CreateRouter(r); err == nil {
log.Infof("created router [r/%s] with fingerprint [%s]", r.Id, create.Router.Fingerprint)
handler_common.SendSuccess(msg, ch, "")
@@ -41,8 +41,12 @@ func (h *createServiceHandler) HandleReceive(msg *channel2.Message, ch channel2.
cs := &mgmt_pb.CreateServiceRequest{}
err := proto.Unmarshal(msg.Body, cs)
if err == nil {
if cs.Service.Name == "" {
cs.Service.Name = cs.Service.Id
}
service := &network.Service{
BaseEntity: models.BaseEntity{Id: cs.Service.Id},
Name: cs.Service.Name,
TerminatorStrategy: cs.Service.TerminatorStrategy,
}
for _, terminator := range cs.Service.Terminators {
+1
View File
@@ -72,6 +72,7 @@ func toApiService(s *network.Service) *mgmt_pb.Service {
return &mgmt_pb.Service{
Id: s.Id,
Name: s.Name,
TerminatorStrategy: s.TerminatorStrategy,
Terminators: terminators,
}
+3 -2
View File
@@ -56,8 +56,9 @@ func (h *getTerminatorHandler) HandleReceive(msg *channel2.Message, ch channel2.
if err == nil {
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_GetTerminatorResponseType), body)
responseMsg.ReplyTo(msg)
ch.Send(responseMsg)
if err = ch.Send(responseMsg); err != nil {
pfxlog.ContextLogger(ch.Label()).Errorf("unexpected error (%s)", err)
}
} else {
pfxlog.ContextLogger(ch.Label()).Errorf("unexpected error (%s)", err)
}
+1
View File
@@ -58,6 +58,7 @@ func (h *listRoutersHandler) HandleReceive(msg *channel2.Message, ch channel2.Ch
responseR := &mgmt_pb.Router{
Id: router.Id,
Name: router.Name,
Fingerprint: stringz.OrEmpty(router.Fingerprint),
}
+2 -2
View File
@@ -25,8 +25,8 @@ import (
func TestLifecycle(t *testing.T) {
linkController := newLinkController()
r0 := NewRouter("r0", "")
r1 := NewRouter("r1", "")
r0 := NewRouter("r0", "", "")
r1 := NewRouter("r1", "", "")
l0 := &Link{
Id: &identity.TokenId{Token: "l0"},
Src: r0,
+8 -1
View File
@@ -30,6 +30,7 @@ import (
type Router struct {
models.BaseEntity
Name string
Fingerprint *string
AdvertisedListener string
Control channel2.Channel
@@ -42,6 +43,7 @@ func (entity *Router) fillFrom(_ Controller, _ *bbolt.Tx, boltEntity boltz.Entit
if !ok {
return errors.Errorf("unexpected type %v when filling model router", reflect.TypeOf(boltEntity))
}
entity.Name = boltRouter.Name
entity.Fingerprint = boltRouter.Fingerprint
entity.FillCommon(boltRouter)
return nil
@@ -50,13 +52,18 @@ func (entity *Router) fillFrom(_ Controller, _ *bbolt.Tx, boltEntity boltz.Entit
func (entity *Router) toBolt() *db.Router {
return &db.Router{
BaseExtEntity: *boltz.NewExtEntity(entity.Id, entity.Tags),
Name: entity.Name,
Fingerprint: entity.Fingerprint,
}
}
func NewRouter(id, fingerprint string) *Router {
func NewRouter(id, name, fingerprint string) *Router {
if name == "" {
name = id
}
return &Router{
BaseEntity: models.BaseEntity{Id: id},
Name: name,
Fingerprint: &fingerprint,
}
}
+3
View File
@@ -29,6 +29,7 @@ import (
type Service struct {
models.BaseEntity
Name string
TerminatorStrategy string
Terminators []*Terminator
}
@@ -38,6 +39,7 @@ func (entity *Service) fillFrom(ctrl Controller, tx *bbolt.Tx, boltEntity boltz.
if !ok {
return errors.Errorf("unexpected type %v when filling model service", reflect.TypeOf(boltEntity))
}
entity.Name = boltService.Name
entity.TerminatorStrategy = boltService.TerminatorStrategy
entity.FillCommon(boltService)
@@ -54,6 +56,7 @@ func (entity *Service) fillFrom(ctrl Controller, tx *bbolt.Tx, boltEntity boltz.
func (entity *Service) toBolt() *db.Service {
return &db.Service{
BaseExtEntity: *boltz.NewExtEntity(entity.Id, entity.Tags),
Name: entity.Name,
TerminatorStrategy: entity.TerminatorStrategy,
}
}