Skip to content

Commit 4adbc1c

Browse files
authored
Refactor openstack migrations syncer (#311)
Refactor nova syncer for migrations to fetch complete history + minor adjustment to comments and general clean up
1 parent 2e9012f commit 4adbc1c

File tree

8 files changed

+77
-203
lines changed

8 files changed

+77
-203
lines changed

internal/extractor/plugins/shared/vm_host_residency.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ WITH durations AS (
2121
)) AS BIGINT)
2222
) AS duration
2323
FROM openstack_migrations AS migrations
24-
LEFT JOIN openstack_servers_v2 AS servers ON servers.id = migrations.instance_uuid
24+
LEFT JOIN openstack_servers AS servers ON servers.id = migrations.instance_uuid
2525
LEFT JOIN openstack_flavors_v2 AS flavors ON flavors.name = servers.flavor_name
2626
)
2727
SELECT

internal/extractor/plugins/vmware/vrops_hostsystem_resolver.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ SELECT DISTINCT
33
m.hostsystem AS vrops_hostsystem,
44
s.os_ext_srv_attr_host AS nova_compute_host
55
FROM vrops_vm_metrics m
6-
LEFT JOIN openstack_servers_v2 s ON m.instance_uuid = s.id
6+
LEFT JOIN openstack_servers s ON m.instance_uuid = s.id
77
WHERE s.os_ext_srv_attr_host IS NOT NULL;

internal/extractor/plugins/vmware/vrops_project_noisiness.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ host_cpu_usage AS (
1919
s.tenant_id,
2020
h.service_host,
2121
AVG(p.avg_cpu) AS avg_cpu_of_project
22-
FROM openstack_servers_v2 s
22+
FROM openstack_servers s
2323
JOIN vrops_vm_metrics m ON s.id = m.instance_uuid
2424
JOIN projects_avg_cpu p ON s.tenant_id = p.tenant_id
2525
JOIN openstack_hypervisors h ON s.os_ext_srv_attr_hypervisor_hostname = h.hostname

internal/sync/openstack/nova/nova_api.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ type NovaAPI interface {
3232
GetAllHypervisors(ctx context.Context) ([]Hypervisor, error)
3333
// Get all nova flavors.
3434
GetAllFlavors(ctx context.Context) ([]Flavor, error)
35-
// Get all changed nova migrations since the timestamp.
36-
GetChangedMigrations(ctx context.Context, changedSince *time.Time) ([]Migration, error)
35+
// Get all nova migrations.
36+
GetAllMigrations(ctx context.Context) ([]Migration, error)
3737
// Get all aggregates.
3838
GetAllAggregates(ctx context.Context) ([]Aggregate, error)
3939
}
@@ -110,6 +110,11 @@ func (api *novaAPI) GetAllServers(ctx context.Context) ([]Server, error) {
110110
}
111111

112112
// Get all deleted Nova servers.
113+
// Note on Nova terminology: Nova uses "instance" internally in its database and code,
114+
// but exposes these as "server" objects through the public API.
115+
// Server lifecycle and cleanup:
116+
// - In SAP Cloud Infrastructure's Nova fork, orphaned servers are purged after 3 weeks
117+
// - This means historical server data is limited to 3 weeks
113118
func (api *novaAPI) GetDeletedServers(ctx context.Context, since time.Time) ([]DeletedServer, error) {
114119
label := DeletedServer{}.TableName()
115120

@@ -226,10 +231,19 @@ func (api *novaAPI) GetAllFlavors(ctx context.Context) ([]Flavor, error) {
226231
return data.Flavors, nil
227232
}
228233

229-
// Get all changed Nova migrations.
230-
func (api *novaAPI) GetChangedMigrations(ctx context.Context, changedSince *time.Time) ([]Migration, error) {
234+
// Get all Nova migrations from the OpenStack API.
235+
//
236+
// Note on Nova terminology: Nova uses "instance" internally in its database and code,
237+
// but exposes these as "server" objects through the public API.
238+
//
239+
// Migration lifecycle and cleanup:
240+
// - Migrations are automatically deleted when their associated server is deleted
241+
// (see Nova source: https://github.com/openstack/nova/blob/1508cb39a2b12ef2d4f706b9c303a744ce40e707/nova/db/main/api.py#L1337-L1358)
242+
// - In SAP Cloud Infrastructure's Nova fork, orphaned migrations are purged after 3 weeks
243+
// - This means historical migration data has limited retention
244+
func (api *novaAPI) GetAllMigrations(ctx context.Context) ([]Migration, error) {
231245
label := Migration{}.TableName()
232-
slog.Info("fetching nova data", "label", label, "changedSince", changedSince)
246+
slog.Info("fetching nova data", "label", label)
233247
// Note: currently we need to fetch this without gophercloud.
234248
// See: https://github.com/gophercloud/gophercloud/pull/3244
235249
if api.mon.PipelineRequestTimer != nil {
@@ -238,11 +252,6 @@ func (api *novaAPI) GetChangedMigrations(ctx context.Context, changedSince *time
238252
defer timer.ObserveDuration()
239253
}
240254
initialURL := api.sc.Endpoint + "os-migrations"
241-
// It is important to omit the changes-since parameter if it is nil.
242-
// Otherwise Nova may return huge amounts of data since the beginning of time.
243-
if changedSince != nil {
244-
initialURL += "?changes-since=" + changedSince.Format(time.RFC3339)
245-
}
246255
var nextURL = &initialURL
247256
var migrations []Migration
248257
for nextURL != nil {

internal/sync/openstack/nova/nova_api_test.go

Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -191,60 +191,45 @@ func TestNovaAPI_GetAllFlavors(t *testing.T) {
191191
}
192192
}
193193

194-
func TestNovaAPI_GetChangedMigrations(t *testing.T) {
195-
tests := []struct {
196-
name string
197-
time *time.Time
198-
}{
199-
{"nil", nil},
200-
{"time", &time.Time{}},
201-
}
202-
for _, tt := range tests {
203-
handler := func(w http.ResponseWriter, r *http.Request) {
204-
if tt.time == nil {
205-
// Check that the changes-since query parameter is not set.
206-
if r.URL.Query().Get("changes-since") != "" {
207-
t.Fatalf("expected no changes-since query parameter, got %s", r.URL.Query().Get("changes-since"))
208-
}
209-
} else {
210-
if r.URL.Query().Get("changes-since") != tt.time.Format(time.RFC3339) {
211-
t.Fatalf("expected changes-since query parameter to be %s, got %s", tt.time.Format(time.RFC3339), r.URL.Query().Get("changes-since"))
212-
}
213-
}
214-
w.Header().Add("Content-Type", "application/json")
215-
w.WriteHeader(http.StatusOK)
216-
resp := struct {
217-
Migrations []Migration `json:"migrations"`
218-
Links []struct {
219-
Rel string `json:"rel"`
220-
Href string `json:"href"`
221-
} `json:"migrations_links"`
222-
}{
223-
Migrations: []Migration{{ID: 1, SourceCompute: "host1", DestCompute: "host2", Status: "completed"}},
224-
}
225-
if err := json.NewEncoder(w).Encode(resp); err != nil {
226-
t.Fatalf("failed to write response: %v", err)
227-
}
194+
func TestNovaAPI_GetAllMigrations(t *testing.T) {
195+
handler := func(w http.ResponseWriter, r *http.Request) {
196+
if r.URL.Query().Get("changes-since") != "" {
197+
t.Fatalf("expected no changes-since query parameter, got %s", r.URL.Query().Get("changes-since"))
228198
}
229-
server, k := setupNovaMockServer(handler)
230-
defer server.Close()
199+
w.Header().Add("Content-Type", "application/json")
200+
w.WriteHeader(http.StatusOK)
201+
resp := struct {
202+
Migrations []Migration `json:"migrations"`
203+
Links []struct {
204+
Rel string `json:"rel"`
205+
Href string `json:"href"`
206+
} `json:"migrations_links"`
207+
}{
208+
Migrations: []Migration{{ID: 1, SourceCompute: "host1", DestCompute: "host2", Status: "completed"}},
209+
}
210+
if err := json.NewEncoder(w).Encode(resp); err != nil {
211+
t.Fatalf("failed to write response: %v", err)
212+
}
213+
}
231214

232-
mon := sync.Monitor{}
233-
conf := NovaConf{Availability: "public"}
215+
server, k := setupNovaMockServer(handler)
216+
defer server.Close()
234217

235-
api := NewNovaAPI(mon, k, conf).(*novaAPI)
236-
api.Init(t.Context())
218+
mon := sync.Monitor{}
219+
conf := NovaConf{Availability: "public"}
237220

238-
ctx := t.Context()
239-
migrations, err := api.GetChangedMigrations(ctx, tt.time)
240-
if err != nil {
241-
t.Fatalf("expected no error, got %v", err)
242-
}
243-
if len(migrations) != 1 {
244-
t.Fatalf("expected 1 migration, got %d", len(migrations))
245-
}
246-
if migrations[0].ID != 1 || migrations[0].SourceCompute != "host1" || migrations[0].DestCompute != "host2" || migrations[0].Status != "completed" {
247-
t.Errorf("unexpected migration data: %+v", migrations[0])
248-
}
221+
api := NewNovaAPI(mon, k, conf).(*novaAPI)
222+
api.Init(t.Context())
223+
224+
ctx := t.Context()
225+
migrations, err := api.GetAllMigrations(ctx)
226+
if err != nil {
227+
t.Fatalf("expected no error, got %v", err)
228+
}
229+
if len(migrations) != 1 {
230+
t.Fatalf("expected 1 migration, got %d", len(migrations))
231+
}
232+
if migrations[0].ID != 1 || migrations[0].SourceCompute != "host1" || migrations[0].DestCompute != "host2" || migrations[0].Status != "completed" {
233+
t.Errorf("unexpected migration data: %+v", migrations[0])
249234
}
250235
}

0 commit comments

Comments
 (0)