Skip to content

Commit 1f579db

Browse files
authored
Merge pull request #147 from thin-edge/feat-delete-orphaned-cloud-services
feat: delete orphaned services from previously created containers
2 parents 3b1684d + 910e1d3 commit 1f579db

File tree

5 files changed

+88
-2
lines changed

5 files changed

+88
-2
lines changed

cli/run/cmd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func NewRunCommand(cliContext cli.Cli) *cobra.Command {
4848
ServiceName: cliContext.GetServiceName(),
4949
EnableMetrics: cliContext.MetricsEnabled(),
5050
DeleteFromCloud: cliContext.DeleteFromCloud(),
51+
DeleteOrphans: cliContext.DeleteOrphans(),
5152
EnableEngineEvents: cliContext.EngineEventsEnabled(),
5253

5354
HTTPHost: cliContext.GetHTTPHost(),
@@ -158,6 +159,7 @@ func NewRunCommand(cliContext cli.Cli) *cobra.Command {
158159
// Feature flags
159160
viper.SetDefault("events.enabled", true)
160161
viper.SetDefault("delete_from_cloud.enabled", true)
162+
viper.SetDefault("delete_from_cloud.orphans", true)
161163

162164
// thin-edge.io services
163165
viper.SetDefault("client.http.host", "127.0.0.1")

packaging/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ enabled = true
7272
[delete_from_cloud]
7373
# Enable/Disable the deletion of services from the cloud (using REST API) when a container is removed
7474
enabled = true
75+
# delete orphaned services (related to containers) from the cloud
76+
orphans = true
7577

7678
[registry]
7779
# Path to the file containing container registry credentials

pkg/app/app.go

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/docker/docker/api/types/events"
1515
mqtt "github.com/eclipse/paho.mqtt.golang"
16+
"github.com/reubenmiller/go-c8y/pkg/c8y"
1617
"github.com/thin-edge/tedge-container-plugin/pkg/container"
1718
"github.com/thin-edge/tedge-container-plugin/pkg/tedge"
1819
)
@@ -68,6 +69,7 @@ type Config struct {
6869
EnableMetrics bool
6970
EnableEngineEvents bool
7071
DeleteFromCloud bool
72+
DeleteOrphans bool
7173

7274
HTTPHost string
7375
HTTPPort uint16
@@ -167,6 +169,42 @@ func (a *App) DeleteLegacyService(deleteFromCloud bool) {
167169
}
168170
}
169171

172+
// Delete any unclaimed/orphaned cloud services which haven't been registered with
173+
func (a *App) DeleteOrphanedCloudServices(tedgeEntities map[string]tedge.Entity) error {
174+
extID, _, err := a.client.CumulocityClient.Identity.GetExternalID(context.Background(), "c8y_Serial", a.Device.ExternalID())
175+
if err != nil {
176+
slog.Warn("Could not lookup device's managed object by its external id.", "err", err, "externalId", a.Device.ExternalID())
177+
}
178+
179+
mos, _, err := a.client.CumulocityClient.Inventory.GetChildAdditions(context.Background(), extID.ManagedObject.ID, &c8y.ManagedObjectOptions{
180+
Query: fmt.Sprintf("type eq 'c8y_Service' and (serviceType eq '%s' or serviceType eq '%s')", container.ContainerType, container.ContainerGroupType),
181+
PaginationOptions: *c8y.NewPaginationOptions(100),
182+
})
183+
if err != nil {
184+
return err
185+
}
186+
187+
slog.Info("Found cloud services.", "count", len(mos.References))
188+
189+
for _, ref := range mos.References {
190+
target := a.client.Target.Service(ref.ManagedObject.Name)
191+
slog.Info("Check if service is registered locally.", "topic-id", target.TopicID)
192+
193+
if _, found := tedgeEntities[target.TopicID]; !found {
194+
slog.Info("Found orphaned cloud service.", "service", ref.ManagedObject.Name, "type", ref.ManagedObject.Type, "moID", ref.ManagedObject.ID)
195+
196+
if _, respErr := a.client.CumulocityClient.Inventory.Delete(context.Background(), ref.ManagedObject.ID); respErr != nil {
197+
slog.Warn("Could not delete orphaned cloud service.", "err", respErr)
198+
} else {
199+
slog.Info("Successfully deleted orphaned cloud service.", "moID", ref.ManagedObject.ID)
200+
}
201+
} else {
202+
slog.Info("Service is registered locally.", "topic-id", target.TopicID)
203+
}
204+
}
205+
return nil
206+
}
207+
170208
func (a *App) Subscribe() error {
171209
topic := tedge.GetTopic(*a.Device.Service("+"), "cmd", "health", "check")
172210
slog.Info("Listening to commands on topic.", "topic", topic)
@@ -465,19 +503,23 @@ func (a *App) doUpdate(filterOptions container.FilterOptions) error {
465503
delete(existingServices, target.TopicID)
466504

467505
// Register using HTTP API
468-
resp, err := tedgeClient.TedgeAPI.CreateEntity(context.Background(), tedge.Entity{
506+
entity := tedge.Entity{
469507
TedgeType: tedge.EntityTypeService,
470508
TedgeTopicID: target.TopicID,
471509
Name: item.Name,
472510
Type: item.ServiceType,
473511
TedgeParentID: tedgeClient.Parent.TopicID,
474-
})
512+
}
513+
resp, err := tedgeClient.TedgeAPI.CreateEntity(context.Background(), entity)
475514

476515
if err == nil {
477516
slog.Info("Registered container.", "topic", target.Topic(), "url", resp.RawResponse.Request.URL.String(), "status_code", resp.RawResponse.Status)
478517
} else {
479518
slog.Error("Failed to register container.", "topic", target.Topic(), "err", err)
480519
}
520+
521+
// Manually add to entities store for re-use later without having to fetch a new list of entities
522+
entities[entity.TedgeTopicID] = entity
481523
}
482524

483525
// Publish health messages
@@ -530,6 +572,9 @@ func (a *App) doUpdate(filterOptions container.FilterOptions) error {
530572
slog.Warn("Failed to deregister entity.", "err", err)
531573
}
532574

575+
// Update entities map
576+
delete(entities, staleTopicID)
577+
533578
// mark targets for deletion from the cloud, but don't delete them yet to give time
534579
// for thin-edge.io to process the status updates
535580
markedForDeletion = append(markedForDeletion, *target)
@@ -556,5 +601,12 @@ func (a *App) doUpdate(filterOptions container.FilterOptions) error {
556601
}
557602
}
558603

604+
// Delete orphaned cloud services
605+
if a.config.DeleteFromCloud && a.config.DeleteOrphans {
606+
if err := a.DeleteOrphanedCloudServices(entities); err != nil {
607+
slog.Warn("Could not delete orphaned cloud services.", "err", err)
608+
}
609+
}
610+
559611
return nil
560612
}

pkg/cli/cli.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ func (c *Cli) DeleteFromCloud() bool {
155155
return viper.GetBool("delete_from_cloud.enabled")
156156
}
157157

158+
func (c *Cli) DeleteOrphans() bool {
159+
return viper.GetBool("delete_from_cloud.orphans")
160+
}
161+
158162
func (c *Cli) GetHTTPHost() string {
159163
return viper.GetString("client.http.host")
160164
}

tests/operations.robot

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,32 @@ Manual container created and then killed
129129
Cumulocity.Should Have Services name=manualapp3 service_type=container min_count=0 max_count=0 timeout=10
130130

131131

132+
Remove Orphaned Cloud Services
133+
[Documentation] Orphaned cloud services can occur if entities are deregistered manually when the tedge-container-plugin
134+
... service is not running.
135+
${operation}= Cumulocity.Execute Shell Command sudo tedge-container engine docker run -d --name manualapp4 busybox sh -c 'exec sleep infinity'
136+
Operation Should Be SUCCESSFUL ${operation} timeout=60
137+
Cumulocity.Should Have Services name=manualapp4 service_type=container status=up
138+
139+
Stop Service tedge-container-plugin
140+
141+
# Uninstall
142+
${operation}= Cumulocity.Execute Shell Command sudo tedge-container engine docker rm manualapp4 --force
143+
Operation Should Be SUCCESSFUL ${operation}
144+
145+
# Clear container service locally
146+
${operation}= Cumulocity.Execute Shell Command sudo tedge mqtt pub -r 'te/device/main/service/manualapp4' ''; sleep 1; sudo tedge http delete '/te/v1/entities/device/main/service/manualapp4'
147+
Operation Should Be SUCCESSFUL ${operation}
148+
149+
# Confirm that the cloud's service status has not changed
150+
# Note: This could change once thin-edge.io supports deleting the cloud entities
151+
Sleep 1s
152+
Cumulocity.Should Have Services name=manualapp4 service_type=container status=up
153+
154+
# Start the service, and check that the service has been removed (without the explicit service type defined)
155+
Start Service tedge-container-plugin
156+
Cumulocity.Should Have Services name=manualapp4 min_count=0 max_count=0 timeout=10
157+
132158
*** Keywords ***
133159

134160
Suite Setup

0 commit comments

Comments
 (0)