Skip to content

Commit 7e75c88

Browse files
feat(dbaas): session support, deprecates connection methods (#248)
1 parent a104d77 commit 7e75c88

9 files changed

Lines changed: 496 additions & 17 deletions

CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/)
55

66
## [Unreleased]
77

8+
### Added
9+
- Managed Database session support, including methods `service.GetManagedDatabaseSessions` & `service.CancelManagedDatabaseSession`.
10+
11+
### Deprecated
12+
- `service.GetManagedDatabaseConnections` and `service.CancelManagedDatabaseConnection` in favor of `service.GetManagedDatabaseSessions` and `service.CancelManagedDatabaseSession`
13+
814
## [6.4.0]
915

1016
### Added
1117
- client functions `NewDefaultHTTPClient` and `NewDefaultHTTPTransport` to provide HTTP client default properties
1218
- kubernetes: experimental support for deleting nodes from node groups
13-
- kubernetes: consts for `scaling-up` and `scaling-down` node group states
19+
- kubernetes: consts for `scaling-up` and `scaling-down` node-group states
1420
- kubernetes: `utility_network_access` field to node group for configuring utility network access on the given group
1521

1622
### Changed
@@ -320,8 +326,7 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/)
320326

321327
First stable release
322328

323-
[Unreleased]: https://github.com/UpCloudLtd/upcloud-go-api/compare/v6.4.0...HEAD
324-
[6.4.0]: https://github.com/UpCloudLtd/upcloud-go-api/compare/v6.3.2...v6.4.0
329+
[Unreleased]: https://github.com/UpCloudLtd/upcloud-go-api/compare/v6.3.2...HEAD
325330
[6.3.2]: https://github.com/UpCloudLtd/upcloud-go-api/compare/v6.3.1...v6.3.2
326331
[6.3.1]: https://github.com/UpCloudLtd/upcloud-go-api/compare/v6.3.0...v6.3.1
327332
[6.3.0]: https://github.com/UpCloudLtd/upcloud-go-api/compare/v6.2.0...v6.3.0

upcloud/managed_database.go

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ const (
116116
// ManagedDatabasePropertyPublicAccess enables public access via internet to the service. A separate public
117117
// endpoint DNS name will be available under Components after enabling.
118118
ManagedDatabasePropertyPublicAccess ManagedDatabasePropertyKey = "public_access"
119-
// Deprecated: ManagedDatabasePropertyMaxIndexCount allows adjusting the maximum number of indices of an OpenSearch
120-
// Managed Database service. Use ManagedDatabaseUserOpenSearchAccessControlRule instead.
119+
// Deprecated: ManagedDatabasePropertyMaxIndexCount in favor of ManagedDatabaseUserOpenSearchAccessControlRule.
120+
// Allows adjusting the maximum number of indices of an OpenSearch Managed Database service. Use ManagedDatabaseUserOpenSearchAccessControlRule instead.
121121
ManagedDatabasePropertyMaxIndexCount ManagedDatabasePropertyKey = "max_index_count"
122122

123123
// ManagedDatabaseAllIPv4 property value can be used together with ManagedDatabasePropertyIPFilter to allow access from all
@@ -226,6 +226,71 @@ type ManagedDatabaseConnection struct {
226226
XactStart time.Time `json:"xact_start"`
227227
}
228228

229+
// ManagedDatabaseSessions represents sessions in the managed database instance by database instance type.
230+
type ManagedDatabaseSessions struct {
231+
MySQL []ManagedDatabaseSessionMySQL `json:"mysql,omitempty"`
232+
PostgreSQL []ManagedDatabaseSessionPostgreSQL `json:"pg,omitempty"`
233+
Redis []ManagedDatabaseSessionRedis `json:"redis,omitempty"`
234+
}
235+
236+
// ManagedDatabaseSessionMySQL represents a session in a managed MySQL database instance.
237+
type ManagedDatabaseSessionMySQL struct {
238+
ApplicationName string `json:"application_name"`
239+
ClientAddr string `json:"client_addr"`
240+
Datname string `json:"datname"`
241+
Id string `json:"id"`
242+
Query string `json:"query"`
243+
QueryDuration time.Duration `json:"query_duration"`
244+
State string `json:"state"`
245+
Usename string `json:"usename"`
246+
}
247+
248+
// ManagedDatabaseSessionPostgreSQL represents a session in a managed PostgreSQL database instance.
249+
type ManagedDatabaseSessionPostgreSQL struct {
250+
ApplicationName string `json:"application_name"`
251+
BackendStart time.Time `json:"backend_start"`
252+
BackendType string `json:"backend_type"`
253+
BackendXid *int `json:"backend_xid"`
254+
BackendXmin *int `json:"backend_xmin"`
255+
ClientAddr string `json:"client_addr"`
256+
ClientHostname *string `json:"client_hostname"`
257+
ClientPort int `json:"client_port"`
258+
Datid int `json:"datid"`
259+
Datname string `json:"datname"`
260+
Id string `json:"id"`
261+
Query string `json:"query"`
262+
QueryDuration time.Duration `json:"query_duration"`
263+
QueryStart time.Time `json:"query_start"`
264+
State string `json:"state"`
265+
StateChange time.Time `json:"state_change"`
266+
Usename string `json:"usename"`
267+
Usesysid int `json:"usesysid"`
268+
WaitEvent string `json:"wait_event"`
269+
WaitEventType string `json:"wait_event_type"`
270+
XactStart *time.Time `json:"xact_start"`
271+
}
272+
273+
// ManagedDatabaseSessionRedis represents a session in a managed Redis database instance.
274+
type ManagedDatabaseSessionRedis struct {
275+
ActiveChannelSubscriptions int `json:"active_channel_subscriptions"`
276+
ActiveDatabase string `json:"active_database"`
277+
ActivePatternMatchingChannelSubscriptions int `json:"active_pattern_matching_channel_subscriptions"`
278+
ApplicationName string `json:"application_name"`
279+
ClientAddr string `json:"client_addr"`
280+
ConnectionAge time.Duration `json:"connection_age"`
281+
ConnectionIdle time.Duration `json:"connection_idle"`
282+
Flags []string `json:"flags"`
283+
FlagsRaw string `json:"flags_raw"`
284+
Id string `json:"id"`
285+
MultiExecCommands int `json:"multi_exec_commands"`
286+
OutputBuffer int `json:"output_buffer"`
287+
OutputBufferMemory int `json:"output_buffer_memory"`
288+
OutputListLength int `json:"output_list_length"`
289+
Query string `json:"query"`
290+
QueryBuffer int `json:"query_buffer"`
291+
QueryBufferFree int `json:"query_buffer_free"`
292+
}
293+
229294
// ManagedDatabaseMaintenanceTime represents the set time of week when automatic maintenance operations are allowed
230295
type ManagedDatabaseMaintenanceTime struct {
231296
DayOfWeek string `json:"dow"`
@@ -523,8 +588,8 @@ func (m *ManagedDatabaseProperties) GetPublicAccess() bool {
523588
return v
524589
}
525590

526-
// Deprecated: GetMaxIndexCount returns the maximum index count of the service.
527-
// See upcloud.ManagedDatabasePropertyMaxIndexCount for more information.
591+
// Deprecated: GetMaxIndexCount is deprecated in favor of field upcloud.ManagedDatabasePropertyMaxIndexCount.
592+
// Returns the maximum index count of the service.
528593
func (m *ManagedDatabaseProperties) GetMaxIndexCount() int {
529594
v, _ := m.GetInt(ManagedDatabasePropertyMaxIndexCount)
530595
return v
@@ -689,7 +754,8 @@ type ManagedDatabaseServicePlan struct {
689754
Zones ManagedDatabaseServicePlanZones `json:"zones"`
690755
}
691756

692-
// Deprecated: ManagedDatabaseBackupConfig represents backup configuration of a database service plan.
757+
// Deprecated: ManagedDatabaseBackupConfig is deprecated in favor of service specific ManagedDatabaseBackupConfig<ServiceType> types.
758+
// Represents backup configuration of a database service plan.
693759
type ManagedDatabaseBackupConfig struct {
694760
Interval int `json:"interval"`
695761
MaxCount int `json:"max_count"`

upcloud/managed_database_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,128 @@ func TestUnmarshalManagedDatabaseConnection(t *testing.T) {
569569
assert.Equal(t, expect, c)
570570
}
571571

572+
func TestUnmarshalManagedDatabaseSessions(t *testing.T) {
573+
const d = `{
574+
"mysql": [
575+
{
576+
"application_name": "",
577+
"client_addr": "111.111.111.111:63244",
578+
"datname": "defaultdb",
579+
"id": "pid_23325",
580+
"query": "select\n ordinal_position,\n column_name,\n column_type,\n column_default,\n generation_expression,\n table_name,\n column_comment,\n is_nullable,\n extra,\n collation_name\n from information_schema.columns\n where table_schema = 'performance_schema'\n order by table_name, ordinal_position",
581+
"query_duration": 0,
582+
"state": "active",
583+
"usename": "upadmin"
584+
}
585+
],
586+
"pg": [
587+
{
588+
"application_name": "client 1.5.14",
589+
"backend_start": "2022-01-21T13:26:26.682858Z",
590+
"backend_type": "client backend",
591+
"backend_xid": 2,
592+
"backend_xmin": 1,
593+
"client_addr": "111.111.111.111",
594+
"client_hostname": "client.host.com",
595+
"client_port": 61264,
596+
"datid": 16401,
597+
"datname": "defaultdb",
598+
"id": "pid_2065031",
599+
"query": "SELECT \trel.relname, \trel.relkind, \trel.reltuples, \tcoalesce(rel.relpages,0) + coalesce(toast.relpages,0) AS num_total_pages, \tSUM(ind.relpages) AS index_pages, \tpg_roles.rolname AS owner FROM pg_class rel \tleft join pg_class toast on (toast.oid = rel.reltoastrelid) \tleft join pg_index on (indrelid=rel.oid) \tleft join pg_class ind on (ind.oid = indexrelid) \tjoin pg_namespace on (rel.relnamespace =pg_namespace.oid ) \tleft join pg_roles on ( rel.relowner = pg_roles.oid ) WHERE rel.relkind IN ('r','v','m','f','p') AND nspname = 'public'GROUP BY rel.relname, rel.relkind, rel.reltuples, coalesce(rel.relpages,0) + coalesce(toast.relpages,0), pg_roles.rolname;\n",
600+
"query_duration": 12225858000,
601+
"query_start": "2022-01-21T13:26:28.63132Z",
602+
"state": "idle",
603+
"state_change": "2022-01-21T13:26:28.63388Z",
604+
"usename": "upadmin",
605+
"usesysid": 16400,
606+
"wait_event": "ClientRead",
607+
"wait_event_type": "Client",
608+
"xact_start": "2022-01-21T13:26:28.63383Z"
609+
}
610+
],
611+
"redis": [
612+
{
613+
"active_channel_subscriptions": 0,
614+
"active_database": "",
615+
"active_pattern_matching_channel_subscriptions": 0,
616+
"client_addr": "[fff0:fff0:fff0:fff0:0:fff0:fff0:fff0]:39956",
617+
"connection_age": 2079483000000000,
618+
"connection_idle": 3000000000,
619+
"flags": [],
620+
"flags_raw": "N",
621+
"id": "15",
622+
"multi_exec_commands": -1,
623+
"application_name": "",
624+
"output_buffer": 0,
625+
"output_buffer_memory": 0,
626+
"output_list_length": 0,
627+
"query": "info",
628+
"query_buffer": 0,
629+
"query_buffer_free": 0
630+
}
631+
]
632+
}`
633+
634+
var c ManagedDatabaseSessions
635+
assert.NoError(t, json.Unmarshal([]byte(d), &c))
636+
637+
expect := ManagedDatabaseSessions{
638+
MySQL: []ManagedDatabaseSessionMySQL{{
639+
ApplicationName: "",
640+
ClientAddr: "111.111.111.111:63244",
641+
Datname: "defaultdb",
642+
Id: "pid_23325",
643+
Query: "select\n ordinal_position,\n column_name,\n column_type,\n column_default,\n generation_expression,\n table_name,\n column_comment,\n is_nullable,\n extra,\n collation_name\n from information_schema.columns\n where table_schema = 'performance_schema'\n order by table_name, ordinal_position",
644+
QueryDuration: 0,
645+
State: "active",
646+
Usename: "upadmin",
647+
}},
648+
PostgreSQL: []ManagedDatabaseSessionPostgreSQL{{
649+
ApplicationName: "client 1.5.14",
650+
BackendStart: time.Date(2022, 0o1, 21, 13, 26, 26, 682858000, time.UTC),
651+
BackendType: "client backend",
652+
BackendXid: IntPtr(2),
653+
BackendXmin: IntPtr(1),
654+
ClientAddr: "111.111.111.111",
655+
ClientHostname: StringPtr("client.host.com"),
656+
ClientPort: 61264,
657+
Datid: 16401,
658+
Datname: "defaultdb",
659+
Id: "pid_2065031",
660+
Query: "SELECT \trel.relname, \trel.relkind, \trel.reltuples, \tcoalesce(rel.relpages,0) + coalesce(toast.relpages,0) AS num_total_pages, \tSUM(ind.relpages) AS index_pages, \tpg_roles.rolname AS owner FROM pg_class rel \tleft join pg_class toast on (toast.oid = rel.reltoastrelid) \tleft join pg_index on (indrelid=rel.oid) \tleft join pg_class ind on (ind.oid = indexrelid) \tjoin pg_namespace on (rel.relnamespace =pg_namespace.oid ) \tleft join pg_roles on ( rel.relowner = pg_roles.oid ) WHERE rel.relkind IN ('r','v','m','f','p') AND nspname = 'public'GROUP BY rel.relname, rel.relkind, rel.reltuples, coalesce(rel.relpages,0) + coalesce(toast.relpages,0), pg_roles.rolname;\n",
661+
QueryDuration: 12225858000,
662+
QueryStart: time.Date(2022, 0o1, 21, 13, 26, 28, 631320000, time.UTC),
663+
State: "idle",
664+
StateChange: time.Date(2022, 0o1, 21, 13, 26, 28, 633880000, time.UTC),
665+
Usename: "upadmin",
666+
Usesysid: 16400,
667+
WaitEvent: "ClientRead",
668+
WaitEventType: "Client",
669+
XactStart: TimePtr(time.Date(2022, 0o1, 21, 13, 26, 28, 633830000, time.UTC)),
670+
}},
671+
Redis: []ManagedDatabaseSessionRedis{{
672+
ActiveChannelSubscriptions: 0,
673+
ActiveDatabase: "",
674+
ActivePatternMatchingChannelSubscriptions: 0,
675+
ApplicationName: "",
676+
ClientAddr: "[fff0:fff0:fff0:fff0:0:fff0:fff0:fff0]:39956",
677+
ConnectionAge: 2079483000000000,
678+
ConnectionIdle: 3000000000,
679+
Flags: []string{},
680+
FlagsRaw: "N",
681+
Id: "15",
682+
MultiExecCommands: -1,
683+
OutputBuffer: 0,
684+
OutputBufferMemory: 0,
685+
OutputListLength: 0,
686+
Query: "info",
687+
QueryBuffer: 0,
688+
QueryBufferFree: 0,
689+
}},
690+
}
691+
assert.Equal(t, expect, c)
692+
}
693+
572694
func TestManagedDatabaseMetadata(t *testing.T) {
573695
got := ManagedDatabase{}
574696
require.NoError(t, json.Unmarshal([]byte(`

upcloud/request/managed_database.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,26 @@ func (c *CancelManagedDatabaseConnection) RequestURL() string {
3232
return fmt.Sprintf("/database/%s/connections/%d?%s", c.UUID, c.Pid, qv.Encode())
3333
}
3434

35+
// CancelManagedDatabaseSession represents a request to cancel the current query of a connection or terminate
36+
// the entire connection.
37+
type CancelManagedDatabaseSession struct {
38+
// UUID selects a managed database instance to manage queries of
39+
UUID string
40+
// Pid selects a connection pid to cancel or terminate
41+
Pid int
42+
// Terminate selects whether the connection will be forcefully terminated
43+
Terminate bool
44+
}
45+
46+
// RequestURL implements the request.Request interface
47+
func (c *CancelManagedDatabaseSession) RequestURL() string {
48+
qv := url.Values{}
49+
if c.Terminate {
50+
qv.Set("terminate", "true")
51+
}
52+
return fmt.Sprintf("/database/%s/sessions/%d?%s", c.UUID, c.Pid, qv.Encode())
53+
}
54+
3555
// CloneManagedDatabaseRequest represents a request to cancel
3656
type CloneManagedDatabaseRequest struct {
3757
// UUID selects an existing managed database instance to clone
@@ -257,6 +277,33 @@ func (g *GetManagedDatabaseServiceTypesRequest) RequestURL() string {
257277
return "/database/service-types"
258278
}
259279

280+
// GetManagedDatabaseSessionsRequest represents a request to get managed database instance's current connections
281+
type GetManagedDatabaseSessionsRequest struct {
282+
// UUID selects a managed database instance to query connections from
283+
UUID string
284+
// Limit sets the upper bound how many connections to fetch
285+
Limit int
286+
// Offset skips n connections before returning them. It can be used to iteratively fetch connections.
287+
Offset int
288+
// Order by Session content variable and sort retrieved results. Limited variables can be used for ordering.
289+
Order string
290+
}
291+
292+
// RequestURL implements the request.Request interface
293+
func (g *GetManagedDatabaseSessionsRequest) RequestURL() string {
294+
qv := url.Values{}
295+
if g.Limit != 0 {
296+
qv.Set("limit", strconv.Itoa(g.Limit))
297+
}
298+
if g.Offset != 0 {
299+
qv.Set("offset", strconv.Itoa(g.Offset))
300+
}
301+
if len(g.Order) > 0 {
302+
qv.Set("order", g.Order)
303+
}
304+
return fmt.Sprintf("/database/%s/sessions?%s", g.UUID, qv.Encode())
305+
}
306+
260307
// ManagedDatabaseMaintenanceTimeRequest represents the set time of week when automatic maintenance operations are allowed
261308
type ManagedDatabaseMaintenanceTimeRequest struct {
262309
DayOfWeek string `json:"dow,omitempty"`

upcloud/request/managed_database_test.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import (
55
"testing"
66
"time"
77

8-
"github.com/stretchr/testify/assert"
9-
108
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud"
9+
"github.com/stretchr/testify/assert"
1110
)
1211

1312
/* Service Management */
@@ -21,6 +20,15 @@ func TestCancelManagedDatabaseConnection_RequestURL(t *testing.T) {
2120
assert.Equal(t, "/database/fake/connections/42?terminate=true", req.RequestURL())
2221
}
2322

23+
func TestCancelManagedDatabaseSession_RequestURL(t *testing.T) {
24+
req := &CancelManagedDatabaseSession{
25+
UUID: "fake",
26+
Pid: 42,
27+
Terminate: true,
28+
}
29+
assert.Equal(t, "/database/fake/sessions/42?terminate=true", req.RequestURL())
30+
}
31+
2432
func TestCloneManagedDatabaseRequest_MarshalJSON(t *testing.T) {
2533
t.Run("TestEmpty", func(t *testing.T) {
2634
req := CloneManagedDatabaseRequest{}
@@ -148,6 +156,11 @@ func TestGetManagedDatabasesRequest_RequestURL(t *testing.T) {
148156
assert.Equal(t, "/database", (&GetManagedDatabasesRequest{}).RequestURL())
149157
}
150158

159+
func TestGetManagedDatabaseAccessControlRequest_RequestURL(t *testing.T) {
160+
req := GetManagedDatabaseAccessControlRequest{ServiceUUID: "fake"}
161+
assert.Equal(t, "/database/fake/access-control", req.RequestURL())
162+
}
163+
151164
func TestGetManagedDatabaseConnectionsRequest_RequestURL(t *testing.T) {
152165
req := &GetManagedDatabaseConnectionsRequest{
153166
UUID: "fake",
@@ -174,9 +187,14 @@ func TestGetManagedDatabaseQueryStatisticsRequest_RequestURL(t *testing.T) {
174187
assert.Equal(t, "/database/fake/query-statistics?limit=1000&offset=42", req.RequestURL())
175188
}
176189

177-
func TestGetManagedDatabaseAccessControlRequest_RequestURL(t *testing.T) {
178-
req := GetManagedDatabaseAccessControlRequest{ServiceUUID: "fake"}
179-
assert.Equal(t, "/database/fake/access-control", req.RequestURL())
190+
func TestGetManagedDatabaseSessionsRequest_RequestURL(t *testing.T) {
191+
req := &GetManagedDatabaseSessionsRequest{
192+
UUID: "fake",
193+
Limit: 1000,
194+
Offset: 42,
195+
Order: "pid:desc",
196+
}
197+
assert.Equal(t, "/database/fake/sessions?limit=1000&offset=42&order=pid%3Adesc", req.RequestURL())
180198
}
181199

182200
func TestModifyManagedDatabaseAccessControlRequest(t *testing.T) {

0 commit comments

Comments
 (0)