Skip to content

Commit 61669a1

Browse files
authored
feat(dbaas): support for Managed Redis Database (#192)
1 parent c4f146c commit 61669a1

9 files changed

Lines changed: 523 additions & 42 deletions

CHANGELOG.md

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

66
## [Unreleased]
77

8+
### Added
9+
- experimental support for Managed Redis Database
10+
811
## [5.1.0]
912

1013
### Added

upcloud/managed_database.go

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const (
5353
ManagedDatabaseServiceTypePostgreSQL ManagedDatabaseServiceType = "pg"
5454
// ManagedDatabaseServiceTypeMySQL references a MySQL type of database instance
5555
ManagedDatabaseServiceTypeMySQL ManagedDatabaseServiceType = "mysql"
56+
// ManagedDatabaseServiceTypeRedis references a Redis type of database instance
57+
ManagedDatabaseServiceTypeRedis ManagedDatabaseServiceType = "redis"
5658
)
5759

5860
// ManagedDatabaseMetricPeriod represents the observation period of database metrics
@@ -131,30 +133,32 @@ const (
131133

132134
// ManagedDatabase represents an existing managed database instance
133135
type ManagedDatabase struct {
134-
Backups []ManagedDatabaseBackup `json:"backups"`
135-
Components []ManagedDatabaseComponent `json:"components"`
136-
CreateTime time.Time `json:"create_time"`
137-
Maintenance ManagedDatabaseMaintenanceTime `json:"maintenance"`
138-
Name string `json:"name"`
139-
NodeCount int `json:"node_count"`
140-
NodeStates []ManagedDatabaseNodeState `json:"node_states"`
141-
Plan string `json:"plan"`
142-
Powered bool `json:"powered"`
143-
Properties ManagedDatabaseProperties `json:"properties"`
144-
State ManagedDatabaseState `json:"state"`
145-
Title string `json:"title"`
146-
Type ManagedDatabaseServiceType `json:"type"`
147-
UpdateTime time.Time `json:"update_time"`
148-
ServiceURI string `json:"service_uri"`
149-
ServiceURIParams ManagedDatabaseServiceURIParams `json:"service_uri_params"`
150-
Users []ManagedDatabaseUser `json:"users"`
151-
UUID string `json:"uuid"`
152-
Zone string `json:"zone"`
136+
Backups []ManagedDatabaseBackup `json:"backups,omitempty"`
137+
Components []ManagedDatabaseComponent `json:"components,omitempty"`
138+
CreateTime time.Time `json:"create_time,omitempty"`
139+
Maintenance ManagedDatabaseMaintenanceTime `json:"maintenance,omitempty"`
140+
Name string `json:"name,omitempty"`
141+
NodeCount int `json:"node_count,omitempty"`
142+
NodeStates []ManagedDatabaseNodeState `json:"node_states,omitempty"`
143+
Plan string `json:"plan,omitempty"`
144+
Powered bool `json:"powered,omitempty"`
145+
Properties ManagedDatabaseProperties `json:"properties,omitempty"`
146+
State ManagedDatabaseState `json:"state,omitempty"`
147+
Title string `json:"title,omitempty"`
148+
Type ManagedDatabaseServiceType `json:"type,omitempty"`
149+
UpdateTime time.Time `json:"update_time,omitempty"`
150+
ServiceURI string `json:"service_uri,omitempty"`
151+
ServiceURIParams ManagedDatabaseServiceURIParams `json:"service_uri_params,omitempty"`
152+
Users []ManagedDatabaseUser `json:"users,omitempty"`
153+
UUID string `json:"uuid,omitempty"`
154+
Zone string `json:"zone,omitempty"`
155+
Metadata *ManagedDatabaseMetadata `json:"metadata,omitempty"`
153156
}
154157

155158
// ManagedDatabaseBackup represents a full backup taken at a point in time. It should be noted that both
156159
// MySQL and PostgreSQL support restoring to any point in time between full backups.
157160
type ManagedDatabaseBackup struct {
161+
BackupName string `json:"backup_name"`
158162
BackupTime time.Time `json:"backup_time"`
159163
DataSize int `json:"data_size"`
160164
}
@@ -535,11 +539,24 @@ type ManagedDatabaseUser struct {
535539
// upcloud.ManagedDatabaseUserAuthenticationCachingSHA2Password
536540
// upcloud.ManagedDatabaseUserAuthenticationMySQLNativePassword
537541
Authentication ManagedDatabaseUserAuthenticationType `json:"authentication,omitempty"`
538-
Type ManagedDatabaseUserType `json:"type"`
542+
Type ManagedDatabaseUserType `json:"type,omitempty"`
539543
// Password field is only visible when querying an individual user. It is omitted in main service view and in
540544
// get all users view.
541-
Password string `json:"password,omitempty"`
542-
Username string `json:"username"`
545+
Password string `json:"password,omitempty"`
546+
Username string `json:"username,omitempty"`
547+
PGAccessControl *ManagedDatabaseUserPGAccessControl `json:"pg_access_control,omitempty"`
548+
RedisAccessControl *ManagedDatabaseUserRedisAccessControl `json:"redis_access_control,omitempty"`
549+
}
550+
551+
type ManagedDatabaseUserPGAccessControl struct {
552+
AllowReplication bool `json:"allow_replication"`
553+
}
554+
555+
type ManagedDatabaseUserRedisAccessControl struct {
556+
Categories []string `json:"categories,omitempty"`
557+
Channels []string `json:"channels,omitempty"`
558+
Commands []string `json:"commands,omitempty"`
559+
Keys []string `json:"keys,omitempty"`
543560
}
544561

545562
// ManagedDatabaseQueryStatisticsMySQL represents statistics reported by a MySQL server.
@@ -673,3 +690,11 @@ type ManagedDatabaseServiceProperty struct {
673690
Enum interface{} `json:"enum,omitempty"`
674691
UserError string `json:"user_error,omitempty"`
675692
}
693+
694+
type ManagedDatabaseMetadata struct {
695+
MaxConnections int `json:"max_connections,omitempty"`
696+
PGVersion string `json:"pg_version,omitempty"`
697+
MySQLVersion string `json:"mysql_version,omitempty"`
698+
RedisVersion string `json:"redis_version,omitempty"`
699+
WriteBlockThresholdExceeded *bool `json:"write_block_threshold_exceeded,omitempty"`
700+
}

upcloud/managed_database_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
910
)
1011

1112
func TestManagedDatabaseMetricsChartFloat64_MarshalJSON(t *testing.T) {
@@ -567,3 +568,117 @@ func TestUnmarshalManagedDatabaseConnection(t *testing.T) {
567568
}
568569
assert.Equal(t, expect, c)
569570
}
571+
572+
func TestManagedDatabaseMetadata(t *testing.T) {
573+
got := ManagedDatabase{}
574+
require.NoError(t, json.Unmarshal([]byte(`
575+
{
576+
"metadata": {
577+
"redis_version": "7",
578+
"mysql_version": "8.0.30",
579+
"pg_version": "14.6",
580+
"write_block_threshold_exceeded": false,
581+
"max_connections": 100
582+
}
583+
}`), &got))
584+
want := ManagedDatabase{
585+
Metadata: &ManagedDatabaseMetadata{
586+
MaxConnections: 100,
587+
PGVersion: "14.6",
588+
MySQLVersion: "8.0.30",
589+
RedisVersion: "7",
590+
WriteBlockThresholdExceeded: BoolPtr(false),
591+
},
592+
}
593+
assert.Equal(t, want, got)
594+
595+
got = ManagedDatabase{}
596+
require.NoError(t, json.Unmarshal([]byte(`
597+
{
598+
"metadata": {
599+
"mysql_version": "8.0.30",
600+
"write_block_threshold_exceeded": null
601+
}
602+
}`), &got))
603+
want = ManagedDatabase{
604+
Metadata: &ManagedDatabaseMetadata{
605+
MySQLVersion: "8.0.30",
606+
WriteBlockThresholdExceeded: nil,
607+
},
608+
}
609+
assert.Equal(t, want, got)
610+
611+
got = ManagedDatabase{}
612+
require.NoError(t, json.Unmarshal([]byte(`
613+
{
614+
"metadata": {
615+
"write_block_threshold_exceeded": true
616+
}
617+
}`), &got))
618+
want = ManagedDatabase{
619+
Metadata: &ManagedDatabaseMetadata{
620+
WriteBlockThresholdExceeded: BoolPtr(true),
621+
},
622+
}
623+
assert.Equal(t, want, got)
624+
}
625+
626+
func TestManagedDatabaseBackups(t *testing.T) {
627+
got := ManagedDatabase{}
628+
require.NoError(t, json.Unmarshal([]byte(`
629+
{
630+
"backups": [
631+
{
632+
"backup_name": "backup-name",
633+
"backup_time": "2022-12-08T08:07:40.960849Z",
634+
"data_size": 864733663
635+
}
636+
]
637+
}`), &got))
638+
want := ManagedDatabase{
639+
Backups: []ManagedDatabaseBackup{
640+
{
641+
BackupName: "backup-name",
642+
BackupTime: timeParse("2022-12-08T08:07:40.960849Z"),
643+
DataSize: 864733663,
644+
},
645+
},
646+
}
647+
assert.Equal(t, want, got)
648+
}
649+
650+
func TestManagedDatabaseUser(t *testing.T) {
651+
got := ManagedDatabaseUser{}
652+
require.NoError(t, json.Unmarshal([]byte(`
653+
{
654+
"username": "api-doc-user",
655+
"password": "new-password",
656+
"authentication": "caching_sha2_password",
657+
"type": "regular",
658+
"redis_access_control": {
659+
"categories": ["+@set"],
660+
"channels": ["*"],
661+
"commands": ["+set"],
662+
"keys": ["key_*"]
663+
},
664+
"pg_access_control": {
665+
"allow_replication": false
666+
}
667+
}`), &got))
668+
want := ManagedDatabaseUser{
669+
Authentication: "caching_sha2_password",
670+
Type: "regular",
671+
Password: "new-password",
672+
Username: "api-doc-user",
673+
PGAccessControl: &ManagedDatabaseUserPGAccessControl{
674+
AllowReplication: false,
675+
},
676+
RedisAccessControl: &ManagedDatabaseUserRedisAccessControl{
677+
Categories: []string{"+@set"},
678+
Channels: []string{"*"},
679+
Commands: []string{"+set"},
680+
Keys: []string{"key_*"},
681+
},
682+
}
683+
assert.Equal(t, want, got)
684+
}

upcloud/request/managed_database.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ type CloneManagedDatabaseRequest struct {
3939
// CloneTime selects a point-in-time from where to clone the data. Zero value selects the most recent available.
4040
CloneTime time.Time `json:"clone_time"`
4141

42+
// Only for Redis. Create a clone of your database service data from the backups by name.
43+
BackupName string `json:"backup_name,omitempty"`
44+
4245
HostNamePrefix string `json:"hostname_prefix"`
4346
Maintenance ManagedDatabaseMaintenanceTimeRequest `json:"maintenance,omitempty"`
4447
Plan string `json:"plan"`
@@ -488,7 +491,9 @@ type CreateManagedDatabaseUserRequest struct {
488491
// Authentication selects authentication type for the user. See following constants for more information:
489492
// upcloud.ManagedDatabaseUserAuthenticationCachingSHA2Password
490493
// upcloud.ManagedDatabaseUserAuthenticationMySQLNativePassword
491-
Authentication upcloud.ManagedDatabaseUserAuthenticationType `json:"authentication,omitempty"`
494+
Authentication upcloud.ManagedDatabaseUserAuthenticationType `json:"authentication,omitempty"`
495+
PGAccessControl *upcloud.ManagedDatabaseUserPGAccessControl `json:"pg_access_control,omitempty"`
496+
RedisAccessControl *upcloud.ManagedDatabaseUserRedisAccessControl `json:"redis_access_control,omitempty"`
492497
}
493498

494499
// RequestURL implements the request.Request interface
@@ -553,6 +558,21 @@ func (m *ModifyManagedDatabaseUserRequest) RequestURL() string {
553558
return fmt.Sprintf("/database/%s/users/%s", m.ServiceUUID, m.Username)
554559
}
555560

561+
// ModifyManagedDatabaseUserRequest represents a request to modify an existing user of an existing managed database instance
562+
type ModifyManagedDatabaseUserAccessControlRequest struct {
563+
// ServiceUUID selects a managed database service to modify
564+
ServiceUUID string `json:"-"`
565+
// Username selects the username to modify. The username itself is immutable. To change it, recreate the user.
566+
Username string `json:"-"`
567+
PGAccessControl *upcloud.ManagedDatabaseUserPGAccessControl `json:"pg_access_control,omitempty"`
568+
RedisAccessControl *upcloud.ManagedDatabaseUserRedisAccessControl `json:"redis_access_control,omitempty"`
569+
}
570+
571+
// RequestURL implements the request.Request interface
572+
func (m *ModifyManagedDatabaseUserAccessControlRequest) RequestURL() string {
573+
return fmt.Sprintf("/database/%s/users/%s/access-control", m.ServiceUUID, m.Username)
574+
}
575+
556576
/* Logical Database Management */
557577

558578
// CreateManagedDatabaseLogicalDatabaseRequest represents a request to create a new logical database to an existing

upcloud/request/managed_database_test.go

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func TestCloneManagedDatabaseRequest_MarshalJSON(t *testing.T) {
3333
UUID: "fakeuuid",
3434
CloneTime: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
3535
HostNamePrefix: "fakename",
36+
BackupName: "backup name",
3637
Maintenance: ManagedDatabaseMaintenanceTimeRequest{
3738
DayOfWeek: "monday",
3839
Time: "12:00:00",
@@ -49,6 +50,7 @@ func TestCloneManagedDatabaseRequest_MarshalJSON(t *testing.T) {
4950
const expect = `{
5051
"hostname_prefix": "fakename",
5152
"plan": "fakeplan",
53+
"backup_name": "backup name",
5254
"properties": {
5355
"fakeprop": "fakevalue"
5456
},
@@ -61,7 +63,7 @@ func TestCloneManagedDatabaseRequest_MarshalJSON(t *testing.T) {
6163
"time": "12:00:00"
6264
}
6365
}`
64-
assert.Equal(t, expect, string(d))
66+
assert.JSONEq(t, expect, string(d))
6567
}
6668

6769
func TestCloneManagedDatabaseRequest_RequestURL(t *testing.T) {
@@ -375,9 +377,88 @@ func TestManagedDatabasePropertiesRequest_GetPublicAccess(t *testing.T) {
375377

376378
/* User Management */
377379

378-
func TestCreateManagedDatabaseUserRequest_RequestURL(t *testing.T) {
379-
req := CreateManagedDatabaseUserRequest{ServiceUUID: "fakeuuid"}
380-
assert.Equal(t, "/database/fakeuuid/users", req.RequestURL())
380+
func TestCreateManagedDatabaseUserRequest(t *testing.T) {
381+
want := `
382+
{
383+
"username": "api-doc-user",
384+
"password": "new-password",
385+
"authentication": "caching_sha2_password",
386+
"redis_access_control": {
387+
"categories": ["+@set"],
388+
"channels": ["*"],
389+
"commands": ["+set"],
390+
"keys": ["key_*"]
391+
},
392+
"pg_access_control": {
393+
"allow_replication": true
394+
}
395+
}
396+
`
397+
r := CreateManagedDatabaseUserRequest{
398+
ServiceUUID: "fakeuuid",
399+
Username: "api-doc-user",
400+
Password: "new-password",
401+
Authentication: upcloud.ManagedDatabaseUserAuthenticationCachingSHA2Password,
402+
PGAccessControl: &upcloud.ManagedDatabaseUserPGAccessControl{
403+
AllowReplication: true,
404+
},
405+
RedisAccessControl: &upcloud.ManagedDatabaseUserRedisAccessControl{
406+
Categories: []string{"+@set"},
407+
Channels: []string{"*"},
408+
Commands: []string{"+set"},
409+
Keys: []string{"key_*"},
410+
},
411+
}
412+
got, err := json.Marshal(&r)
413+
assert.NoError(t, err)
414+
assert.JSONEq(t, want, string(got))
415+
assert.Equal(t, "/database/fakeuuid/users", r.RequestURL())
416+
417+
want = `
418+
{
419+
"username": "api-doc-user"
420+
}
421+
`
422+
r = CreateManagedDatabaseUserRequest{
423+
ServiceUUID: "fakeuuid",
424+
Username: "api-doc-user",
425+
}
426+
got, err = json.Marshal(&r)
427+
assert.NoError(t, err)
428+
assert.JSONEq(t, want, string(got))
429+
}
430+
431+
func TestModifyManagedDatabaseUserAccessControlRequest(t *testing.T) {
432+
want := `
433+
{
434+
"redis_access_control": {
435+
"categories": ["+@set"],
436+
"channels": ["*"],
437+
"commands": ["+set"],
438+
"keys": ["key_*"]
439+
},
440+
"pg_access_control": {
441+
"allow_replication": true
442+
}
443+
}
444+
`
445+
r := ModifyManagedDatabaseUserAccessControlRequest{
446+
ServiceUUID: "fakeuuid",
447+
Username: "fakeuser",
448+
PGAccessControl: &upcloud.ManagedDatabaseUserPGAccessControl{
449+
AllowReplication: true,
450+
},
451+
RedisAccessControl: &upcloud.ManagedDatabaseUserRedisAccessControl{
452+
Categories: []string{"+@set"},
453+
Channels: []string{"*"},
454+
Commands: []string{"+set"},
455+
Keys: []string{"key_*"},
456+
},
457+
}
458+
got, err := json.Marshal(&r)
459+
assert.NoError(t, err)
460+
assert.JSONEq(t, want, string(got))
461+
assert.Equal(t, "/database/fakeuuid/users/fakeuser/access-control", r.RequestURL())
381462
}
382463

383464
func TestDeleteManagedDatabaseUserRequest_RequestURL(t *testing.T) {

0 commit comments

Comments
 (0)