From 61a6121c77b15922be6e82859f09f9f3375eed7c Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Wed, 3 Apr 2019 11:40:41 -0400 Subject: [PATCH 1/8] Bump godo to 1.11.0 --- Gopkg.lock | 6 +- Gopkg.toml | 2 +- .../github.com/digitalocean/godo/databases.go | 614 ++++++++++++++++++ vendor/github.com/digitalocean/godo/doc.go | 9 + .../github.com/digitalocean/godo/droplets.go | 3 + vendor/github.com/digitalocean/godo/godo.go | 6 +- .../digitalocean/godo/kubernetes.go | 4 +- .../digitalocean/godo/load_balancers.go | 3 + vendor/github.com/digitalocean/godo/vpcs.go | 183 ++++++ 9 files changed, 824 insertions(+), 6 deletions(-) create mode 100644 vendor/github.com/digitalocean/godo/databases.go create mode 100644 vendor/github.com/digitalocean/godo/vpcs.go diff --git a/Gopkg.lock b/Gopkg.lock index ef08b739a..e217d952e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -87,12 +87,12 @@ version = "v1.1.0" [[projects]] - digest = "1:a710ae2a66582fe9c9cad8f04cdae91bcd594592efc88f331c50b6cf3cafc727" + digest = "1:dc983274a47d5ad125e5d4db6884ff66f38c923b5c863735243908e5ce299b44" name = "github.com/digitalocean/godo" packages = ["."] pruneopts = "NUT" - revision = "cd71616aba666a6c51fd79af3ee9210960c5ccaf" - version = "v1.9.0" + revision = "1438b2a2dd0cf69423232772a860c25bc7b17ea0" + version = "v1.11.0" [[projects]] digest = "1:4189ee6a3844f555124d9d2656fe7af02fca961c2a9bad9074789df13a0c62e0" diff --git a/Gopkg.toml b/Gopkg.toml index a93224ec0..69072fb21 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -44,7 +44,7 @@ [[constraint]] name = "github.com/digitalocean/godo" - version = "1.9.0" + version = "1.11.0" [[constraint]] branch = "master" diff --git a/vendor/github.com/digitalocean/godo/databases.go b/vendor/github.com/digitalocean/godo/databases.go new file mode 100644 index 000000000..c06cca3ac --- /dev/null +++ b/vendor/github.com/digitalocean/godo/databases.go @@ -0,0 +1,614 @@ +package godo + +import ( + "context" + "fmt" + "net/http" + "time" +) + +const ( + databaseBasePath = "/v2/databases" + databaseSinglePath = databaseBasePath + "/%s" + databaseResizePath = databaseBasePath + "/%s/resize" + databaseMigratePath = databaseBasePath + "/%s/migrate" + databaseMaintenancePath = databaseBasePath + "/%s/maintenance" + databaseBackupsPath = databaseBasePath + "/%s/backups" + databaseUsersPath = databaseBasePath + "/%s/users" + databaseUserPath = databaseBasePath + "/%s/users/%s" + databaseDBPath = databaseBasePath + "/%s/dbs/%s" + databaseDBsPath = databaseBasePath + "/%s/dbs" + databasePoolPath = databaseBasePath + "/%s/pools/%s" + databasePoolsPath = databaseBasePath + "/%s/pools" + databaseReplicaPath = databaseBasePath + "/%s/replicas/%s" + databaseReplicasPath = databaseBasePath + "/%s/replicas" +) + +// DatabasesService is an interface for interfacing with the databases endpoints +// of the DigitalOcean API. +// See: https://developers.digitalocean.com/documentation/v2#databases +type DatabasesService interface { + List(context.Context, *ListOptions) ([]Database, *Response, error) + Get(context.Context, string) (*Database, *Response, error) + Create(context.Context, *DatabaseCreateRequest) (*Database, *Response, error) + Delete(context.Context, string) (*Response, error) + Resize(context.Context, string, *DatabaseResizeRequest) (*Response, error) + Migrate(context.Context, string, *DatabaseMigrateRequest) (*Response, error) + UpdateMaintenance(context.Context, string, *DatabaseUpdateMaintenanceRequest) (*Response, error) + ListBackups(context.Context, string, *ListOptions) ([]DatabaseBackup, *Response, error) + GetUser(context.Context, string, string) (*DatabaseUser, *Response, error) + ListUsers(context.Context, string, *ListOptions) ([]DatabaseUser, *Response, error) + CreateUser(context.Context, string, *DatabaseCreateUserRequest) (*DatabaseUser, *Response, error) + DeleteUser(context.Context, string, string) (*Response, error) + ListDBs(context.Context, string, *ListOptions) ([]DatabaseDB, *Response, error) + CreateDB(context.Context, string, *DatabaseCreateDBRequest) (*DatabaseDB, *Response, error) + GetDB(context.Context, string, string) (*DatabaseDB, *Response, error) + DeleteDB(context.Context, string, string) (*Response, error) + ListPools(context.Context, string, *ListOptions) ([]DatabasePool, *Response, error) + CreatePool(context.Context, string, *DatabaseCreatePoolRequest) (*DatabasePool, *Response, error) + GetPool(context.Context, string, string) (*DatabasePool, *Response, error) + DeletePool(context.Context, string, string) (*Response, error) + GetReplica(context.Context, string, string) (*DatabaseReplica, *Response, error) + ListReplicas(context.Context, string, *ListOptions) ([]DatabaseReplica, *Response, error) + CreateReplica(context.Context, string, *DatabaseCreateReplicaRequest) (*DatabaseReplica, *Response, error) + DeleteReplica(context.Context, string, string) (*Response, error) +} + +// DatabasesServiceOp handles communication with the Databases related methods +// of the DigitalOcean API. +type DatabasesServiceOp struct { + client *Client +} + +var _ DatabasesService = &DatabasesServiceOp{} + +// Database represents a DigitalOcean managed database product. These managed databases +// are usually comprised of a cluster of database nodes, a primary and 0 or more replicas. +// The EngineSlug is a string which indicates the type of database service. Some examples are +// "pg", "mysql" or "redis". A Database also includes connection information and other +// properties of the service like region, size and current status. +type Database struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + EngineSlug string `json:"engine,omitempty"` + VersionSlug string `json:"version,omitempty"` + Connection *DatabaseConnection `json:"connection,omitempty"` + Users []DatabaseUser `json:"users,omitempty"` + NumNodes int `json:"num_nodes,omitempty"` + SizeSlug string `json:"size,omitempty"` + DBNames []string `json:"db_names,omitempty"` + RegionSlug string `json:"region,omitempty"` + Status string `json:"status,omitempty"` + MaintenanceWindow *DatabaseMaintenanceWindow `json:"maintenance_window,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` +} + +// DatabaseConnection represents a database connection +type DatabaseConnection struct { + URI string `json:"uri,omitempty"` + Database string `json:"database,omitempty"` + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + SSL bool `json:"ssl,omitempty"` +} + +// DatabaseUser represents a user in the database +type DatabaseUser struct { + Name string `json:"name,omitempty"` + Role string `json:"role,omitempty"` + Password string `json:"password,omitempty"` +} + +// DatabaseMaintenanceWindow represents the maintenance_window of a database +// cluster +type DatabaseMaintenanceWindow struct { + Day string `json:"day,omitempty"` + Hour string `json:"hour,omitempty"` + Pending bool `json:"pending,omitempty"` + Description []string `json:"description,omitempty"` +} + +// DatabaseBackup represents a database backup. +type DatabaseBackup struct { + CreatedAt time.Time `json:"created_at,omitempty"` + SizeGigabytes float64 `json:"size_gigabytes,omitempty"` +} + +// DatabaseCreateRequest represents a request to create a database cluster +type DatabaseCreateRequest struct { + Name string `json:"name,omitempty"` + EngineSlug string `json:"engine,omitempty"` + Version string `json:"version,omitempty"` + SizeSlug string `json:"size,omitempty"` + Region string `json:"region,omitempty"` + NumNodes int `json:"num_nodes,omitempty"` +} + +// DatabaseResizeRequest can be used to initiate a database resize operation. +type DatabaseResizeRequest struct { + SizeSlug string `json:"size,omitempty"` + NumNodes int `json:"num_nodes,omitempty"` +} + +// DatabaseMigrateRequest can be used to initiate a database migrate operation. +type DatabaseMigrateRequest struct { + Region string `json:"region,omitempty"` +} + +// DatabaseUpdateMaintenanceRequest can be used to update the database's maintenance window. +type DatabaseUpdateMaintenanceRequest struct { + Day string `json:"day,omitempty"` + Hour string `json:"hour,omitempty"` +} + +// DatabaseDB represents an engine-specific database created within a database cluster. For SQL +// databases like PostgreSQL or MySQL, a "DB" refers to a database created on the RDBMS. For instance, +// a PostgreSQL database server can contain many database schemas, each with it's own settings, access +// permissions and data. ListDBs will return all databases present on the server. +type DatabaseDB struct { + Name string `json:"name"` +} + +// DatabaseReplica represents a read-only replica of a particular database +type DatabaseReplica struct { + Name string `json:"name"` + Connection *DatabaseConnection `json:"connection"` + Region string `json:"region"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` +} + +// DatabasePool represents a database connection pool +type DatabasePool struct { + User string `json:"user"` + Name string `json:"name"` + Size int `json:"size"` + Database string `json:"db"` + Mode string `json:"mode"` + Connection *DatabaseConnection `json:"connection"` +} + +// DatabaseCreatePoolRequest is used to create a new database connection pool +type DatabaseCreatePoolRequest struct { + Pool *DatabasePool `json:"pool"` +} + +// DatabaseCreateUserRequest is used to create a new database user +type DatabaseCreateUserRequest struct { + Name string `json:"name"` +} + +// DatabaseCreateDBRequest is used to create a new engine-specific database within the cluster +type DatabaseCreateDBRequest struct { + Name string `json:"name"` +} + +// DatabaseCreateReplicaRequest is used to create a new read-only replica +type DatabaseCreateReplicaRequest struct { + Name string `json:"name"` + Region string `json:"region"` + Size string `json:"size"` +} + +type databaseUserRoot struct { + User *DatabaseUser `json:"user"` +} + +type databaseUsersRoot struct { + Users []DatabaseUser `json:"users"` +} + +type databaseDBRoot struct { + DB *DatabaseDB `json:"db"` +} + +type databaseDBsRoot struct { + DBs []DatabaseDB `json:"dbs"` +} + +type databasesRoot struct { + Databases []Database `json:"databases"` +} + +type databaseRoot struct { + Database *Database `json:"database"` +} + +type databaseBackupsRoot struct { + Backups []DatabaseBackup `json:"backups"` +} + +type databasePoolRoot struct { + Pool *DatabasePool `json:"pool"` +} + +type databasePoolsRoot struct { + Pools []DatabasePool `json:"pools"` +} + +type databaseReplicaRoot struct { + Replica *DatabaseReplica `json:"replica"` +} + +type databaseReplicasRoot struct { + Replicas []DatabaseReplica `json:"replicas"` +} + +// List returns a list of the Databases visible with the caller's API token +func (svc *DatabasesServiceOp) List(ctx context.Context, opts *ListOptions) ([]Database, *Response, error) { + path := databaseBasePath + path, err := addOptions(path, opts) + if err != nil { + return nil, nil, err + } + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databasesRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Databases, resp, nil +} + +// Get retrieves the details of a database cluster +func (svc *DatabasesServiceOp) Get(ctx context.Context, databaseID string) (*Database, *Response, error) { + path := fmt.Sprintf(databaseSinglePath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Database, resp, nil +} + +// Create creates a database cluster +func (svc *DatabasesServiceOp) Create(ctx context.Context, create *DatabaseCreateRequest) (*Database, *Response, error) { + path := databaseBasePath + req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create) + if err != nil { + return nil, nil, err + } + root := new(databaseRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Database, resp, nil +} + +// Delete deletes a database cluster. There is no way to recover a cluster once +// it has been destroyed. +func (svc *DatabasesServiceOp) Delete(ctx context.Context, databaseID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", databaseBasePath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// Resize resizes a database cluster by number of nodes or size +func (svc *DatabasesServiceOp) Resize(ctx context.Context, databaseID string, resize *DatabaseResizeRequest) (*Response, error) { + path := fmt.Sprintf(databaseResizePath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPut, path, resize) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// Migrate migrates a database cluster to a new region +func (svc *DatabasesServiceOp) Migrate(ctx context.Context, databaseID string, migrate *DatabaseMigrateRequest) (*Response, error) { + path := fmt.Sprintf(databaseMigratePath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPut, path, migrate) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// UpdateMaintenance updates the maintenance window on a cluster +func (svc *DatabasesServiceOp) UpdateMaintenance(ctx context.Context, databaseID string, maintenance *DatabaseUpdateMaintenanceRequest) (*Response, error) { + path := fmt.Sprintf(databaseMaintenancePath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPut, path, maintenance) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// ListBackups returns a list of the current backups of a database +func (svc *DatabasesServiceOp) ListBackups(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseBackup, *Response, error) { + path := fmt.Sprintf(databaseBackupsPath, databaseID) + path, err := addOptions(path, opts) + if err != nil { + return nil, nil, err + } + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseBackupsRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Backups, resp, nil +} + +// GetUser returns the database user identified by userID +func (svc *DatabasesServiceOp) GetUser(ctx context.Context, databaseID, userID string) (*DatabaseUser, *Response, error) { + path := fmt.Sprintf(databaseUserPath, databaseID, userID) + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseUserRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.User, resp, nil +} + +// ListUsers returns all database users for the database +func (svc *DatabasesServiceOp) ListUsers(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseUser, *Response, error) { + path := fmt.Sprintf(databaseUsersPath, databaseID) + path, err := addOptions(path, opts) + if err != nil { + return nil, nil, err + } + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseUsersRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Users, resp, nil +} + +// CreateUser will create a new database user +func (svc *DatabasesServiceOp) CreateUser(ctx context.Context, databaseID string, createUser *DatabaseCreateUserRequest) (*DatabaseUser, *Response, error) { + path := fmt.Sprintf(databaseUsersPath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createUser) + if err != nil { + return nil, nil, err + } + root := new(databaseUserRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.User, resp, nil +} + +// DeleteUser will delete an existing database user +func (svc *DatabasesServiceOp) DeleteUser(ctx context.Context, databaseID, userID string) (*Response, error) { + path := fmt.Sprintf(databaseUserPath, databaseID, userID) + req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// ListDBs returns all databases for a given database cluster +func (svc *DatabasesServiceOp) ListDBs(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseDB, *Response, error) { + path := fmt.Sprintf(databaseDBsPath, databaseID) + path, err := addOptions(path, opts) + if err != nil { + return nil, nil, err + } + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseDBsRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.DBs, resp, nil +} + +// GetDB returns a single database by name +func (svc *DatabasesServiceOp) GetDB(ctx context.Context, databaseID, name string) (*DatabaseDB, *Response, error) { + path := fmt.Sprintf(databaseDBPath, databaseID, name) + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseDBRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.DB, resp, nil +} + +// CreateDB will create a new database +func (svc *DatabasesServiceOp) CreateDB(ctx context.Context, databaseID string, createDB *DatabaseCreateDBRequest) (*DatabaseDB, *Response, error) { + path := fmt.Sprintf(databaseDBsPath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createDB) + if err != nil { + return nil, nil, err + } + root := new(databaseDBRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.DB, resp, nil +} + +// DeleteDB will delete an existing database +func (svc *DatabasesServiceOp) DeleteDB(ctx context.Context, databaseID, name string) (*Response, error) { + path := fmt.Sprintf(databaseDBPath, databaseID, name) + req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// ListPools returns all connection pools for a given database cluster +func (svc *DatabasesServiceOp) ListPools(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabasePool, *Response, error) { + path := fmt.Sprintf(databasePoolsPath, databaseID) + path, err := addOptions(path, opts) + if err != nil { + return nil, nil, err + } + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databasePoolsRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Pools, resp, nil +} + +// GetPool returns a single database connection pool by name +func (svc *DatabasesServiceOp) GetPool(ctx context.Context, databaseID, name string) (*DatabasePool, *Response, error) { + path := fmt.Sprintf(databasePoolPath, databaseID, name) + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databasePoolRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Pool, resp, nil +} + +// CreatePool will create a new database connection pool +func (svc *DatabasesServiceOp) CreatePool(ctx context.Context, databaseID string, createPool *DatabaseCreatePoolRequest) (*DatabasePool, *Response, error) { + path := fmt.Sprintf(databasePoolsPath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createPool) + if err != nil { + return nil, nil, err + } + root := new(databasePoolRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Pool, resp, nil +} + +// DeletePool will delete an existing database connection pool +func (svc *DatabasesServiceOp) DeletePool(ctx context.Context, databaseID, name string) (*Response, error) { + path := fmt.Sprintf(databasePoolPath, databaseID, name) + req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// GetReplica returns a single database replica +func (svc *DatabasesServiceOp) GetReplica(ctx context.Context, databaseID, name string) (*DatabaseReplica, *Response, error) { + path := fmt.Sprintf(databaseReplicaPath, databaseID, name) + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseReplicaRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Replica, resp, nil +} + +// ListReplicas returns all read-only replicas for a given database cluster +func (svc *DatabasesServiceOp) ListReplicas(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseReplica, *Response, error) { + path := fmt.Sprintf(databaseReplicasPath, databaseID) + path, err := addOptions(path, opts) + if err != nil { + return nil, nil, err + } + req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(databaseReplicasRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Replicas, resp, nil +} + +// CreateReplica will create a new database connection pool +func (svc *DatabasesServiceOp) CreateReplica(ctx context.Context, databaseID string, createReplica *DatabaseCreateReplicaRequest) (*DatabaseReplica, *Response, error) { + path := fmt.Sprintf(databaseReplicasPath, databaseID) + req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createReplica) + if err != nil { + return nil, nil, err + } + root := new(databaseReplicaRoot) + resp, err := svc.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.Replica, resp, nil +} + +// DeleteReplica will delete an existing database replica +func (svc *DatabasesServiceOp) DeleteReplica(ctx context.Context, databaseID, name string) (*Response, error) { + path := fmt.Sprintf(databaseReplicaPath, databaseID, name) + req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + resp, err := svc.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/doc.go b/vendor/github.com/digitalocean/godo/doc.go index e660f794a..e22f69333 100644 --- a/vendor/github.com/digitalocean/godo/doc.go +++ b/vendor/github.com/digitalocean/godo/doc.go @@ -1,2 +1,11 @@ // Package godo is the DigtalOcean API v2 client for Go +// +// Databases +// +// The Databases service provides access to the DigitalOcean managed database +// suite of products. Customers can create new database clusters, migrate them +// between regions, create replicas and interact with their configurations. +// Each database service is refered to as a Database. A SQL database service +// can have multiple databases residing in the system. To help make these +// entities distinct from Databases in godo, we refer to them here as DatabaseDBs. package godo diff --git a/vendor/github.com/digitalocean/godo/droplets.go b/vendor/github.com/digitalocean/godo/droplets.go index ab508f1c0..06d4debf3 100644 --- a/vendor/github.com/digitalocean/godo/droplets.go +++ b/vendor/github.com/digitalocean/godo/droplets.go @@ -60,6 +60,7 @@ type Droplet struct { Kernel *Kernel `json:"kernel,omitempty"` Tags []string `json:"tags,omitempty"` VolumeIDs []string `json:"volume_ids"` + VPCUUID string `json:"vpc_uuid,omitempty"` } // PublicIPv4 returns the public IPv4 address for the Droplet. @@ -222,6 +223,7 @@ type DropletCreateRequest struct { UserData string `json:"user_data,omitempty"` Volumes []DropletCreateVolume `json:"volumes,omitempty"` Tags []string `json:"tags"` + VPCUUID string `json:"vpc_uuid,omitempty"` } // DropletMultiCreateRequest is a request to create multiple Droplets. @@ -237,6 +239,7 @@ type DropletMultiCreateRequest struct { Monitoring bool `json:"monitoring"` UserData string `json:"user_data,omitempty"` Tags []string `json:"tags"` + VPCUUID string `json:"vpc_uuid,omitempty"` } func (d DropletCreateRequest) String() string { diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go index 6362cb6c0..2bb5fa223 100644 --- a/vendor/github.com/digitalocean/godo/godo.go +++ b/vendor/github.com/digitalocean/godo/godo.go @@ -17,7 +17,7 @@ import ( ) const ( - libraryVersion = "1.9.0" + libraryVersion = "1.11.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" @@ -65,6 +65,8 @@ type Client struct { Firewalls FirewallsService Projects ProjectsService Kubernetes KubernetesService + Databases DatabasesService + VPCs VPCsService // Optional function called after every successful request made to the DO APIs onRequestCompleted RequestCompletionCallback @@ -179,6 +181,8 @@ func NewClient(httpClient *http.Client) *Client { c.StorageActions = &StorageActionsServiceOp{client: c} c.Tags = &TagsServiceOp{client: c} c.Kubernetes = &KubernetesServiceOp{client: c} + c.Databases = &DatabasesServiceOp{client: c} + c.VPCs = &VPCsServiceOp{client: c} return c } diff --git a/vendor/github.com/digitalocean/godo/kubernetes.go b/vendor/github.com/digitalocean/godo/kubernetes.go index 4466059cb..e42dfe563 100644 --- a/vendor/github.com/digitalocean/godo/kubernetes.go +++ b/vendor/github.com/digitalocean/godo/kubernetes.go @@ -16,7 +16,7 @@ const ( kubernetesOptionsPath = kubernetesBasePath + "/options" ) -// KubernetesService is an interface for interfacing with the kubernetes endpoints +// KubernetesService is an interface for interfacing with the Kubernetes endpoints // of the DigitalOcean API. // See: https://developers.digitalocean.com/documentation/v2#kubernetes type KubernetesService interface { @@ -50,6 +50,7 @@ type KubernetesClusterCreateRequest struct { RegionSlug string `json:"region,omitempty"` VersionSlug string `json:"version,omitempty"` Tags []string `json:"tags,omitempty"` + VPCUUID string `json:"vpc_uuid,omitempty"` NodePools []*KubernetesNodePoolCreateRequest `json:"node_pools,omitempty"` } @@ -94,6 +95,7 @@ type KubernetesCluster struct { IPv4 string `json:"ipv4,omitempty"` Endpoint string `json:"endpoint,omitempty"` Tags []string `json:"tags,omitempty"` + VPCUUID string `json:"vpc_uuid,omitempty"` NodePools []*KubernetesNodePool `json:"node_pools,omitempty"` diff --git a/vendor/github.com/digitalocean/godo/load_balancers.go b/vendor/github.com/digitalocean/godo/load_balancers.go index 7bac255c7..c565e2294 100644 --- a/vendor/github.com/digitalocean/godo/load_balancers.go +++ b/vendor/github.com/digitalocean/godo/load_balancers.go @@ -43,6 +43,7 @@ type LoadBalancer struct { Tags []string `json:"tags,omitempty"` RedirectHttpToHttps bool `json:"redirect_http_to_https,omitempty"` EnableProxyProtocol bool `json:"enable_proxy_protocol,omitempty"` + VPCUUID string `json:"vpc_uuid,omitempty"` } // String creates a human-readable description of a LoadBalancer. @@ -66,6 +67,7 @@ func (l LoadBalancer) AsRequest() *LoadBalancerRequest { RedirectHttpToHttps: l.RedirectHttpToHttps, EnableProxyProtocol: l.EnableProxyProtocol, HealthCheck: l.HealthCheck, + VPCUUID: l.VPCUUID, } if l.HealthCheck != nil { @@ -138,6 +140,7 @@ type LoadBalancerRequest struct { Tags []string `json:"tags,omitempty"` RedirectHttpToHttps bool `json:"redirect_http_to_https,omitempty"` EnableProxyProtocol bool `json:"enable_proxy_protocol,omitempty"` + VPCUUID string `json:"vpc_uuid,omitempty"` } // String creates a human-readable description of a LoadBalancerRequest. diff --git a/vendor/github.com/digitalocean/godo/vpcs.go b/vendor/github.com/digitalocean/godo/vpcs.go new file mode 100644 index 000000000..24752d373 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/vpcs.go @@ -0,0 +1,183 @@ +package godo + +import ( + "context" + "net/http" + "time" +) + +const vpcsBasePath = "/v2/vpcs" + +// VPCsService is an interface for managing Virtual Private Cloud configurations with the +// DigitalOcean API. +// See: https://developers.digitalocean.com/documentation/v2#vpcs +type VPCsService interface { + Create(context.Context, *VPCCreateRequest) (*VPC, *Response, error) + Get(context.Context, string) (*VPC, *Response, error) + List(context.Context, *ListOptions) ([]*VPC, *Response, error) + Update(context.Context, string, *VPCUpdateRequest) (*VPC, *Response, error) + Set(context.Context, string, ...VPCSetField) (*VPC, *Response, error) + Delete(context.Context, string) (*Response, error) +} + +var _ VPCsService = &VPCsServiceOp{} + +// VPCsServiceOp interfaces with VPC endpoints in the DigitalOcean API. +type VPCsServiceOp struct { + client *Client +} + +// VPCCreateRequest represents a request to create a Virtual Private Cloud. +type VPCCreateRequest struct { + Name string `json:"name,omitempty"` + RegionSlug string `json:"region,omitempty"` +} + +// VPCUpdateRequest represents a request to update a Virtual Private Cloud. +type VPCUpdateRequest struct { + Name string `json:"name,omitempty"` +} + +// VPCSetField allows one to set individual fields within a VPC configuration. +type VPCSetField interface { + vpcSetField(map[string]interface{}) +} + +// VPCSetName is used when one want to set the `name` field of a VPC. +// Ex.: VPCs.Set(..., VPCSetName("new-name")) +type VPCSetName string + +// VPC represents a DigitalOcean Virtual Private Cloud configuration. +type VPC struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + RegionSlug string `json:"region,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + Default bool `json:"default,omitempty"` +} + +type vpcRoot struct { + VPC *VPC `json:"vpc"` +} + +type vpcsRoot struct { + VPCs []*VPC `json:"vpcs"` + Links *Links `json:"links"` +} + +// Get returns the details of a Virtual Private Cloud. +func (v *VPCsServiceOp) Get(ctx context.Context, id string) (*VPC, *Response, error) { + path := vpcsBasePath + "/" + id + req, err := v.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(vpcRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.VPC, resp, nil +} + +// Create creates a new Virtual Private Cloud. +func (v *VPCsServiceOp) Create(ctx context.Context, create *VPCCreateRequest) (*VPC, *Response, error) { + path := vpcsBasePath + req, err := v.client.NewRequest(ctx, http.MethodPost, path, create) + if err != nil { + return nil, nil, err + } + + root := new(vpcRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.VPC, resp, nil +} + +// List returns a list of the caller's VPCs, with optional pagination. +func (v *VPCsServiceOp) List(ctx context.Context, opt *ListOptions) ([]*VPC, *Response, error) { + path, err := addOptions(vpcsBasePath, opt) + if err != nil { + return nil, nil, err + } + req, err := v.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(vpcsRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + + return root.VPCs, resp, nil +} + +// Update updates a Virtual Private Cloud's properties. +func (v *VPCsServiceOp) Update(ctx context.Context, id string, update *VPCUpdateRequest) (*VPC, *Response, error) { + path := vpcsBasePath + "/" + id + req, err := v.client.NewRequest(ctx, http.MethodPut, path, update) + if err != nil { + return nil, nil, err + } + + root := new(vpcRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.VPC, resp, nil +} + +func (n VPCSetName) vpcSetField(in map[string]interface{}) { + in["name"] = n +} + +// Set updates specific properties of a Virtual Private Cloud. +func (v *VPCsServiceOp) Set(ctx context.Context, id string, fields ...VPCSetField) (*VPC, *Response, error) { + path := vpcsBasePath + "/" + id + update := make(map[string]interface{}, len(fields)) + for _, field := range fields { + field.vpcSetField(update) + } + + req, err := v.client.NewRequest(ctx, http.MethodPatch, path, update) + if err != nil { + return nil, nil, err + } + + root := new(vpcRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.VPC, resp, nil +} + +// Delete deletes a Virtual Private Cloud. There is no way to recover a VPC once it has been +// destroyed. +func (v *VPCsServiceOp) Delete(ctx context.Context, id string) (*Response, error) { + path := vpcsBasePath + "/" + id + req, err := v.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + resp, err := v.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} From e6c2907ac9d8e5a63e83a563b95c817da046b5e8 Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Mon, 1 Apr 2019 15:54:32 -0400 Subject: [PATCH 2/8] Sync cluster vpc id using cluster id --- cloud-controller-manager/do/resources.go | 36 ++++++- cloud-controller-manager/do/resources_test.go | 99 ++++++++++++++++++- 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/cloud-controller-manager/do/resources.go b/cloud-controller-manager/do/resources.go index 2fd75a816..fc8bc7333 100644 --- a/cloud-controller-manager/do/resources.go +++ b/cloud-controller-manager/do/resources.go @@ -35,8 +35,10 @@ import ( ) const ( + controllerSyncClusterPeriod = 1 * time.Minute controllerSyncTagsPeriod = 1 * time.Minute controllerSyncResourcesPeriod = 1 * time.Minute + syncClusterTimeout = 1 * time.Minute syncTagsTimeout = 1 * time.Minute syncResourcesTimeout = 3 * time.Minute ) @@ -199,10 +201,11 @@ func (s *tickerSyncer) Sync(name string, period time.Duration, stopCh <-chan str // resources. It maintains a local state of the resources and // synchronizes when needed. type ResourcesController struct { - clusterID string - kclient kubernetes.Interface - gclient *godo.Client - svcLister v1lister.ServiceLister + clusterID string + clusterVPCID string + kclient kubernetes.Interface + gclient *godo.Client + svcLister v1lister.ServiceLister resources *resources syncer syncer @@ -231,9 +234,10 @@ func (r *ResourcesController) Run(stopCh <-chan struct{}) { go r.syncer.Sync("resources syncer", controllerSyncResourcesPeriod, stopCh, r.syncResources) if r.clusterID == "" { - glog.Info("No cluster ID configured -- skipping tags syncing.") + glog.Info("No cluster ID configured -- skipping cluster dependent syncers.") return } + go r.syncer.Sync("cluster syncer", controllerSyncClusterPeriod, stopCh, r.syncCluster) go r.syncer.Sync("tags syncer", controllerSyncTagsPeriod, stopCh, r.syncTags) } @@ -264,6 +268,28 @@ func (r *ResourcesController) syncResources() error { return nil } +// syncCluster updates the local cluster state to match what is reported by the +// DigitalOcean API. +func (r *ResourcesController) syncCluster() error { + ctx, cancel := context.WithTimeout(context.Background(), syncClusterTimeout) + defer cancel() + + if r.clusterVPCID != "" { + glog.V(2).Info("cluster VPC ID present, skipping sync.") + return nil + } + + glog.V(2).Info("cluster VPC ID missing, syncing cluster.") + cluster, _, err := r.gclient.Kubernetes.Get(ctx, r.clusterID) + if err != nil { + return err + } + r.clusterVPCID = cluster.VPCUUID + glog.V(2).Info("synced cluster.") + + return nil +} + // syncTags synchronizes tags. Currently, this is only needed to associate // cluster ID tags with LoadBalancer resources. func (r *ResourcesController) syncTags() error { diff --git a/cloud-controller-manager/do/resources_test.go b/cloud-controller-manager/do/resources_test.go index da0ffac4a..20da1da39 100644 --- a/cloud-controller-manager/do/resources_test.go +++ b/cloud-controller-manager/do/resources_test.go @@ -39,6 +39,16 @@ import ( "k8s.io/kubernetes/pkg/cloudprovider" ) +type fakeKubernetesService struct { + godo.KubernetesService + + getFunc func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) +} + +func (s *fakeKubernetesService) Get(ctx context.Context, id string) (*godo.KubernetesCluster, *godo.Response, error) { + return s.getFunc(ctx, id) +} + func TestResources_DropletByID(t *testing.T) { tests := []struct { name string @@ -314,6 +324,14 @@ func TestResourcesController_Run(t *testing.T) { return []godo.LoadBalancer{{ID: "2", Name: "two"}}, newFakeOKResponse(), nil }, }, + Kubernetes: &fakeKubernetesService{ + getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { + return &godo.KubernetesCluster{ + ID: "uuid", + VPCUUID: "vpc_uuid", + }, newFakeOKResponse(), nil + }, + }, } res := NewResourcesController(clusterID, fakeResources, inf.Core().V1().Services(), kclient, gclient) @@ -329,7 +347,7 @@ func TestResourcesController_Run(t *testing.T) { case <-time.After(3 * time.Second): // Terminate goroutines just in case. close(stop) - t.Errorf("resourcer calls: %d tags calls: %d", syncer.synced["resources syncer"], syncer.synced["tags syncer"]) + t.Errorf("resources calls: %d cluster calls: %d tags calls: %d", syncer.synced["resources syncer"], syncer.synced["cluster syncer"], syncer.synced["tags syncer"]) } } @@ -429,6 +447,85 @@ func TestResourcesController_SyncResources(t *testing.T) { } } +func TestResourcesController_SyncCluster(t *testing.T) { + tests := []struct { + name string + kubernetesSvc godo.KubernetesService + initalVPCID string + expectedVPCID string + err error + }{ + { + name: "happy path", + kubernetesSvc: &fakeKubernetesService{ + getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { + return &godo.KubernetesCluster{ + ID: clusterID, + VPCUUID: "vpc_uuid", + }, newFakeOKResponse(), nil + }, + }, + initalVPCID: "", + expectedVPCID: "vpc_uuid", + }, + { + name: "vpc id already present", + kubernetesSvc: &fakeKubernetesService{ + getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { + return &godo.KubernetesCluster{ + ID: clusterID, + VPCUUID: "vpc_uuid", + }, newFakeOKResponse(), nil + }, + }, + initalVPCID: "vpc_uuid", + expectedVPCID: "vpc_uuid", + }, + { + name: "kubernetes svc failure", + kubernetesSvc: &fakeKubernetesService{ + getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { + return nil, newFakeNotOKResponse(), errors.New("fail") + }, + }, + err: errors.New("fail"), + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + fakeResources := newResources() + kclient := fake.NewSimpleClientset() + inf := informers.NewSharedInformerFactory(kclient, 0) + gclient := &godo.Client{ + Kubernetes: test.kubernetesSvc, + } + res := NewResourcesController(clusterID, fakeResources, inf.Core().V1().Services(), kclient, gclient) + res.clusterVPCID = test.initalVPCID + + err := res.syncCluster() + if test.err != nil { + if err == nil { + t.Fatal("expected error but got none") + } + if want, got := test.err, err; !reflect.DeepEqual(want, got) { + t.Errorf("incorrect err\nwant: %#v\n got: %#v", want, got) + } + return + } + if err != nil { + t.Fatalf("unexpected err: %s", err) + } + if want, got := test.expectedVPCID, res.clusterVPCID; want != got { + t.Errorf("incorrect vpc id\nwant: %#v\n got: %#v", want, got) + } + }) + } +} + func lbName(idx int) string { svc := createSvc(idx, false) return cloudprovider.GetLoadBalancerName(svc) From 413f69d579669101ecb98d7feee012018a88a12d Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Mon, 1 Apr 2019 16:11:22 -0400 Subject: [PATCH 3/8] Move cluster id and cluster vpc id into shared resources --- cloud-controller-manager/do/cloud.go | 6 ++-- .../do/loadbalancers_test.go | 10 +++---- cloud-controller-manager/do/resources.go | 29 ++++++++++--------- cloud-controller-manager/do/resources_test.go | 28 +++++++++--------- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/cloud-controller-manager/do/cloud.go b/cloud-controller-manager/do/cloud.go index 30d92f9ae..cdf984fca 100644 --- a/cloud-controller-manager/do/cloud.go +++ b/cloud-controller-manager/do/cloud.go @@ -49,7 +49,6 @@ func (t *tokenSource) Token() (*oauth2.Token, error) { } type cloud struct { - clusterID string client *godo.Client instances cloudprovider.Instances zones cloudprovider.Zones @@ -87,10 +86,9 @@ func newCloud() (cloudprovider.Interface, error) { } clusterID := os.Getenv(doClusterIDEnv) - resources := newResources() + resources := newResources(clusterID) return &cloud{ - clusterID: clusterID, client: doClient, instances: newInstances(resources, region), zones: newZones(resources, region), @@ -110,7 +108,7 @@ func (c *cloud) Initialize(clientBuilder controller.ControllerClientBuilder) { clientset := clientBuilder.ClientOrDie("do-shared-informers") sharedInformer := informers.NewSharedInformerFactory(clientset, 0) - res := NewResourcesController(c.clusterID, c.resources, sharedInformer.Core().V1().Services(), clientset, c.client) + res := NewResourcesController(c.resources, sharedInformer.Core().V1().Services(), clientset, c.client) sharedInformer.Start(nil) sharedInformer.WaitForCacheSync(nil) diff --git a/cloud-controller-manager/do/loadbalancers_test.go b/cloud-controller-manager/do/loadbalancers_test.go index eeca19593..682f51a64 100644 --- a/cloud-controller-manager/do/loadbalancers_test.go +++ b/cloud-controller-manager/do/loadbalancers_test.go @@ -1958,7 +1958,7 @@ func Test_buildLoadBalancerRequest(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources() + fakeResources := newResources(clusterID) fakeResources.UpdateDroplets(test.droplets) lb := &loadBalancers{ @@ -2011,7 +2011,7 @@ func Test_buildLoadBalancerRequestWithClusterID(t *testing.T) { }, } fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources() + fakeResources := newResources(clusterID) fakeResources.UpdateDroplets([]godo.Droplet{ { ID: 100, @@ -2131,7 +2131,7 @@ func Test_nodeToDropletIDs(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources() + fakeResources := newResources(clusterID) fakeResources.UpdateDroplets(test.droplets) lb := &loadBalancers{ @@ -2236,7 +2236,7 @@ func Test_GetLoadBalancer(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { - fakeResources := newResources() + fakeResources := newResources(clusterID) fakeResources.UpdateLoadBalancers(test.lbs) lb := &loadBalancers{ @@ -2440,7 +2440,7 @@ func Test_EnsureLoadBalancer(t *testing.T) { updateFn: test.updateFn, } fakeClient := newFakeLBClient(fakeLB) - fakeResources := newResources() + fakeResources := newResources(clusterID) fakeResources.UpdateDroplets(test.droplets) fakeResources.UpdateLoadBalancers(test.lbs) diff --git a/cloud-controller-manager/do/resources.go b/cloud-controller-manager/do/resources.go index fc8bc7333..7f26bc418 100644 --- a/cloud-controller-manager/do/resources.go +++ b/cloud-controller-manager/do/resources.go @@ -48,6 +48,9 @@ type tagMissingError struct { } type resources struct { + clusterID string + clusterVPCID string + dropletIDMap map[int]*godo.Droplet dropletNameMap map[string]*godo.Droplet loadBalancerIDMap map[string]*godo.LoadBalancer @@ -56,8 +59,10 @@ type resources struct { mutex sync.RWMutex } -func newResources() *resources { +func newResources(clusterID string) *resources { return &resources{ + clusterID: clusterID, + dropletIDMap: make(map[int]*godo.Droplet), dropletNameMap: make(map[string]*godo.Droplet), loadBalancerIDMap: make(map[string]*godo.LoadBalancer), @@ -201,11 +206,9 @@ func (s *tickerSyncer) Sync(name string, period time.Duration, stopCh <-chan str // resources. It maintains a local state of the resources and // synchronizes when needed. type ResourcesController struct { - clusterID string - clusterVPCID string - kclient kubernetes.Interface - gclient *godo.Client - svcLister v1lister.ServiceLister + kclient kubernetes.Interface + gclient *godo.Client + svcLister v1lister.ServiceLister resources *resources syncer syncer @@ -213,14 +216,12 @@ type ResourcesController struct { // NewResourcesController returns a new resource controller. func NewResourcesController( - clusterID string, r *resources, inf v1informers.ServiceInformer, k kubernetes.Interface, g *godo.Client, ) *ResourcesController { return &ResourcesController{ - clusterID: clusterID, resources: r, kclient: k, gclient: g, @@ -233,7 +234,7 @@ func NewResourcesController( func (r *ResourcesController) Run(stopCh <-chan struct{}) { go r.syncer.Sync("resources syncer", controllerSyncResourcesPeriod, stopCh, r.syncResources) - if r.clusterID == "" { + if r.resources.clusterID == "" { glog.Info("No cluster ID configured -- skipping cluster dependent syncers.") return } @@ -274,17 +275,17 @@ func (r *ResourcesController) syncCluster() error { ctx, cancel := context.WithTimeout(context.Background(), syncClusterTimeout) defer cancel() - if r.clusterVPCID != "" { + if r.resources.clusterVPCID != "" { glog.V(2).Info("cluster VPC ID present, skipping sync.") return nil } glog.V(2).Info("cluster VPC ID missing, syncing cluster.") - cluster, _, err := r.gclient.Kubernetes.Get(ctx, r.clusterID) + cluster, _, err := r.gclient.Kubernetes.Get(ctx, r.resources.clusterID) if err != nil { return err } - r.clusterVPCID = cluster.VPCUUID + r.resources.clusterVPCID = cluster.VPCUUID glog.V(2).Info("synced cluster.") return nil @@ -329,7 +330,7 @@ func (r *ResourcesController) syncTags() error { return nil } - tag := buildK8sTag(r.clusterID) + tag := buildK8sTag(r.resources.clusterID) // Tag collected resources with the cluster ID. If the tag does not exist // (for reasons outlined below), we will create it and retry tagging again. err = r.tagResources(res) @@ -360,7 +361,7 @@ func (r *ResourcesController) syncTags() error { func (r *ResourcesController) tagResources(res []godo.Resource) error { ctx, cancel := context.WithTimeout(context.Background(), syncTagsTimeout) defer cancel() - tag := buildK8sTag(r.clusterID) + tag := buildK8sTag(r.resources.clusterID) resp, err := r.gclient.Tags.TagResources(ctx, tag, &godo.TagResourcesRequest{ Resources: res, }) diff --git a/cloud-controller-manager/do/resources_test.go b/cloud-controller-manager/do/resources_test.go index 20da1da39..ec07eee12 100644 --- a/cloud-controller-manager/do/resources_test.go +++ b/cloud-controller-manager/do/resources_test.go @@ -78,7 +78,7 @@ func TestResources_DropletByID(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources() + resources := newResources(clusterID) resources.UpdateDroplets(test.droplets) droplet, found := resources.DropletByID(test.findID) @@ -121,7 +121,7 @@ func TestResources_DropletByName(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources() + resources := newResources(clusterID) resources.UpdateDroplets(test.droplets) droplet, found := resources.DropletByName(test.findName) @@ -183,7 +183,7 @@ func TestResources_LoadBalancerByID(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources() + resources := newResources(clusterID) resources.UpdateLoadBalancers(test.lbs) lb, found := resources.LoadBalancerByID(test.findID) @@ -236,7 +236,7 @@ func TestResources_AddLoadBalancer(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources() + resources := newResources(clusterID) resources.UpdateLoadBalancers(test.lbs) resources.AddLoadBalancer(test.newLB) @@ -310,7 +310,7 @@ var ( ) func TestResourcesController_Run(t *testing.T) { - fakeResources := newResources() + fakeResources := newResources(clusterID) kclient := fake.NewSimpleClientset() inf := informers.NewSharedInformerFactory(kclient, 0) gclient := &godo.Client{ @@ -334,7 +334,7 @@ func TestResourcesController_Run(t *testing.T) { }, } - res := NewResourcesController(clusterID, fakeResources, inf.Core().V1().Services(), kclient, gclient) + res := NewResourcesController(fakeResources, inf.Core().V1().Services(), kclient, gclient) stop := make(chan struct{}) syncer := newRecordingSyncer(2, stop) res.syncer = syncer @@ -425,7 +425,7 @@ func TestResourcesController_SyncResources(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - fakeResources := newResources() + fakeResources := newResources("") fakeResources.UpdateDroplets([]godo.Droplet{ {ID: 1, Name: "one"}, }) @@ -438,7 +438,7 @@ func TestResourcesController_SyncResources(t *testing.T) { Droplets: test.dropletsSvc, LoadBalancers: test.lbsSvc, } - res := NewResourcesController(clusterID, fakeResources, inf.Core().V1().Services(), kclient, gclient) + res := NewResourcesController(fakeResources, inf.Core().V1().Services(), kclient, gclient) res.syncResources() if want, got := test.expectedResources, res.resources; !reflect.DeepEqual(want, got) { t.Errorf("incorrect resources\nwant: %#v\n got: %#v", want, got) @@ -497,14 +497,14 @@ func TestResourcesController_SyncCluster(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - fakeResources := newResources() + fakeResources := newResources(clusterID) + fakeResources.clusterVPCID = test.initalVPCID kclient := fake.NewSimpleClientset() inf := informers.NewSharedInformerFactory(kclient, 0) gclient := &godo.Client{ Kubernetes: test.kubernetesSvc, } - res := NewResourcesController(clusterID, fakeResources, inf.Core().V1().Services(), kclient, gclient) - res.clusterVPCID = test.initalVPCID + res := NewResourcesController(fakeResources, inf.Core().V1().Services(), kclient, gclient) err := res.syncCluster() if test.err != nil { @@ -519,7 +519,7 @@ func TestResourcesController_SyncCluster(t *testing.T) { if err != nil { t.Fatalf("unexpected err: %s", err) } - if want, got := test.expectedVPCID, res.clusterVPCID; want != got { + if want, got := test.expectedVPCID, fakeResources.clusterVPCID; want != got { t.Errorf("incorrect vpc id\nwant: %#v\n got: %#v", want, got) } }) @@ -642,7 +642,7 @@ func TestResourcesController_SyncTags(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - fakeResources := newResources() + fakeResources := newResources(clusterID) for _, lb := range test.lbs { lb := lb fakeResources.loadBalancerIDMap[lb.ID] = lb @@ -664,7 +664,7 @@ func TestResourcesController_SyncTags(t *testing.T) { } sharedInformer := informers.NewSharedInformerFactory(kclient, 0) - res := NewResourcesController(clusterID, fakeResources, sharedInformer.Core().V1().Services(), kclient, gclient) + res := NewResourcesController(fakeResources, sharedInformer.Core().V1().Services(), kclient, gclient) sharedInformer.Start(nil) sharedInformer.WaitForCacheSync(nil) From 38d8737da9ffa7ac54f4054e34f3cd1306ca9b6e Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Mon, 1 Apr 2019 16:22:09 -0400 Subject: [PATCH 4/8] Create LBs in the same vpc as a cluster --- cloud-controller-manager/do/cloud.go | 2 +- cloud-controller-manager/do/loadbalancers.go | 8 ++++---- cloud-controller-manager/do/loadbalancers_test.go | 14 +++++++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/cloud-controller-manager/do/cloud.go b/cloud-controller-manager/do/cloud.go index cdf984fca..4ab90ee46 100644 --- a/cloud-controller-manager/do/cloud.go +++ b/cloud-controller-manager/do/cloud.go @@ -92,7 +92,7 @@ func newCloud() (cloudprovider.Interface, error) { client: doClient, instances: newInstances(resources, region), zones: newZones(resources, region), - loadbalancers: newLoadBalancers(resources, doClient, region, clusterID), + loadbalancers: newLoadBalancers(resources, doClient, region), resources: resources, }, nil diff --git a/cloud-controller-manager/do/loadbalancers.go b/cloud-controller-manager/do/loadbalancers.go index 87bc74a29..9049b5d2c 100644 --- a/cloud-controller-manager/do/loadbalancers.go +++ b/cloud-controller-manager/do/loadbalancers.go @@ -145,12 +145,11 @@ type loadBalancers struct { } // newLoadbalancers returns a cloudprovider.LoadBalancer whose concrete type is a *loadbalancer. -func newLoadBalancers(resources *resources, client *godo.Client, region, clusterID string) cloudprovider.LoadBalancer { +func newLoadBalancers(resources *resources, client *godo.Client, region string) cloudprovider.LoadBalancer { return &loadBalancers{ resources: resources, client: client, region: region, - clusterID: clusterID, lbActiveTimeout: defaultActiveTimeout, lbActiveCheckTick: defaultActiveCheckTick, } @@ -345,8 +344,8 @@ func (l *loadBalancers) buildLoadBalancerRequest(service *v1.Service, nodes []*v } var tags []string - if l.clusterID != "" { - tags = []string{buildK8sTag(l.clusterID)} + if l.resources.clusterID != "" { + tags = []string{buildK8sTag(l.resources.clusterID)} } return &godo.LoadBalancerRequest{ @@ -360,6 +359,7 @@ func (l *loadBalancers) buildLoadBalancerRequest(service *v1.Service, nodes []*v Algorithm: algorithm, RedirectHttpToHttps: redirectHTTPToHTTPS, EnableProxyProtocol: enableProxyProtocol, + VPCUUID: l.resources.clusterVPCID, }, nil } diff --git a/cloud-controller-manager/do/loadbalancers_test.go b/cloud-controller-manager/do/loadbalancers_test.go index 682f51a64..ef48a703f 100644 --- a/cloud-controller-manager/do/loadbalancers_test.go +++ b/cloud-controller-manager/do/loadbalancers_test.go @@ -1958,7 +1958,7 @@ func Test_buildLoadBalancerRequest(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources(clusterID) + fakeResources := newResources("") fakeResources.UpdateDroplets(test.droplets) lb := &loadBalancers{ @@ -2012,6 +2012,7 @@ func Test_buildLoadBalancerRequestWithClusterID(t *testing.T) { } fakeClient := newFakeLBClient(&fakeLBService{}) fakeResources := newResources(clusterID) + fakeResources.clusterVPCID = "vpc_uuid" fakeResources.UpdateDroplets([]godo.Droplet{ { ID: 100, @@ -2019,7 +2020,6 @@ func Test_buildLoadBalancerRequestWithClusterID(t *testing.T) { }, }) - clusterID := "fdda2d9d-0856-4ca4-b8ee-27ca8bfecc77" lb := &loadBalancers{ resources: fakeResources, client: fakeClient, @@ -2036,6 +2036,10 @@ func Test_buildLoadBalancerRequestWithClusterID(t *testing.T) { if !reflect.DeepEqual(lbr.Tags, wantTags) { t.Errorf("got tags %q, want %q", lbr.Tags, wantTags) } + + if want, got := "vpc_uuid", lbr.VPCUUID; want != got { + t.Errorf("incorrect vpc uuid\nwant: %#v\n got: %#v", want, got) + } } func Test_nodeToDropletIDs(t *testing.T) { @@ -2131,7 +2135,7 @@ func Test_nodeToDropletIDs(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources(clusterID) + fakeResources := newResources("") fakeResources.UpdateDroplets(test.droplets) lb := &loadBalancers{ @@ -2236,7 +2240,7 @@ func Test_GetLoadBalancer(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { - fakeResources := newResources(clusterID) + fakeResources := newResources("") fakeResources.UpdateLoadBalancers(test.lbs) lb := &loadBalancers{ @@ -2440,7 +2444,7 @@ func Test_EnsureLoadBalancer(t *testing.T) { updateFn: test.updateFn, } fakeClient := newFakeLBClient(fakeLB) - fakeResources := newResources(clusterID) + fakeResources := newResources("") fakeResources.UpdateDroplets(test.droplets) fakeResources.UpdateLoadBalancers(test.lbs) From 0a7f07d65c3302ccf6152868e5e430aa029c0082 Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Mon, 1 Apr 2019 16:38:37 -0400 Subject: [PATCH 5/8] Handle DOKS case where vpc id is missing --- cloud-controller-manager/do/loadbalancers.go | 6 + .../do/loadbalancers_test.go | 128 ++++++++++++------ 2 files changed, 92 insertions(+), 42 deletions(-) diff --git a/cloud-controller-manager/do/loadbalancers.go b/cloud-controller-manager/do/loadbalancers.go index 9049b5d2c..332902462 100644 --- a/cloud-controller-manager/do/loadbalancers.go +++ b/cloud-controller-manager/do/loadbalancers.go @@ -313,6 +313,12 @@ func (l *loadBalancers) nodesToDropletIDs(nodes []*v1.Node) ([]int, error) { // buildLoadBalancerRequest returns a *godo.LoadBalancerRequest to balance // requests for service across nodes. func (l *loadBalancers) buildLoadBalancerRequest(service *v1.Service, nodes []*v1.Node) (*godo.LoadBalancerRequest, error) { + // when a cluster has a cluster id it is a DOKS managed cluster, these cluster + // must specify a vpc id + if l.resources.clusterID != "" && l.resources.clusterVPCID == "" { + return nil, errors.New("missing cluster vpc id") + } + lbName := cloudprovider.GetLoadBalancerName(service) dropletIDs, err := l.nodesToDropletIDs(nodes) diff --git a/cloud-controller-manager/do/loadbalancers_test.go b/cloud-controller-manager/do/loadbalancers_test.go index ef48a703f..632aa1511 100644 --- a/cloud-controller-manager/do/loadbalancers_test.go +++ b/cloud-controller-manager/do/loadbalancers_test.go @@ -1988,57 +1988,101 @@ func Test_buildLoadBalancerRequest(t *testing.T) { } func Test_buildLoadBalancerRequestWithClusterID(t *testing.T) { - service := &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Name: "test", - Protocol: "TCP", - Port: int32(80), - NodePort: int32(30000), - }, - }, + tests := []struct { + name string + clusterID string + vpcID string + err error + }{ + { + name: "happy path", + clusterID: clusterID, + vpcID: "vpc_uuid", }, - } - nodes := []*v1.Node{ { - ObjectMeta: metav1.ObjectMeta{ - Name: "node-1", - }, + name: "missing cluster id", + clusterID: "", + vpcID: "vpc_uuid", }, - } - fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources(clusterID) - fakeResources.clusterVPCID = "vpc_uuid" - fakeResources.UpdateDroplets([]godo.Droplet{ { - ID: 100, - Name: "node-1", + name: "missing vpc id", + clusterID: clusterID, + vpcID: "", + err: errors.New("missing cluster vpc id"), }, - }) - - lb := &loadBalancers{ - resources: fakeResources, - client: fakeClient, - region: "nyc3", - clusterID: clusterID, } - lbr, err := lb.buildLoadBalancerRequest(service, nodes) - if err != nil { - t.Errorf("got error: %s", err) - } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() - wantTags := []string{buildK8sTag(clusterID)} - if !reflect.DeepEqual(lbr.Tags, wantTags) { - t.Errorf("got tags %q, want %q", lbr.Tags, wantTags) - } + service := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Name: "test", + Protocol: "TCP", + Port: int32(80), + NodePort: int32(30000), + }, + }, + }, + } + nodes := []*v1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node-1", + }, + }, + } + fakeClient := newFakeLBClient(&fakeLBService{}) + fakeResources := newResources(test.clusterID) + fakeResources.clusterVPCID = test.vpcID + fakeResources.UpdateDroplets([]godo.Droplet{ + { + ID: 100, + Name: "node-1", + }, + }) - if want, got := "vpc_uuid", lbr.VPCUUID; want != got { - t.Errorf("incorrect vpc uuid\nwant: %#v\n got: %#v", want, got) + lb := &loadBalancers{ + resources: fakeResources, + client: fakeClient, + region: "nyc3", + clusterID: clusterID, + } + + lbr, err := lb.buildLoadBalancerRequest(service, nodes) + if test.err != nil { + if err == nil { + t.Fatal("expected error but got none") + } + + if want, got := test.err, err; !reflect.DeepEqual(want, got) { + t.Errorf("incorrect err\nwant: %#v\n got: %#v", want, got) + } + return + } + if err != nil { + t.Errorf("got error: %s", err) + } + + var wantTags []string + if test.clusterID != "" { + wantTags = []string{buildK8sTag(clusterID)} + } + if !reflect.DeepEqual(lbr.Tags, wantTags) { + t.Errorf("got tags %q, want %q", lbr.Tags, wantTags) + } + + if want, got := "vpc_uuid", lbr.VPCUUID; want != got { + t.Errorf("incorrect vpc uuid\nwant: %#v\n got: %#v", want, got) + } + }) } } From 20ce1e14bf54e68084be9aaa5bae9e87ef8dd4c6 Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Mon, 1 Apr 2019 16:49:35 -0400 Subject: [PATCH 6/8] Support manually specifying VPC ID via env var for DIY clusters --- cloud-controller-manager/do/cloud.go | 4 +++- .../do/loadbalancers_test.go | 10 +++++----- cloud-controller-manager/do/resources.go | 5 +++-- cloud-controller-manager/do/resources_test.go | 17 ++++++++--------- docs/getting-started.md | 6 +++++- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/cloud-controller-manager/do/cloud.go b/cloud-controller-manager/do/cloud.go index 4ab90ee46..70c7b7663 100644 --- a/cloud-controller-manager/do/cloud.go +++ b/cloud-controller-manager/do/cloud.go @@ -34,6 +34,7 @@ const ( doAccessTokenEnv string = "DO_ACCESS_TOKEN" doOverrideAPIURLEnv string = "DO_OVERRIDE_URL" doClusterIDEnv string = "DO_CLUSTER_ID" + doClusterVPCIDEnv string = "DO_CLUSTER_VPC_ID" providerName string = "digitalocean" ) @@ -86,7 +87,8 @@ func newCloud() (cloudprovider.Interface, error) { } clusterID := os.Getenv(doClusterIDEnv) - resources := newResources(clusterID) + clusterVPCID := os.Getenv(doClusterVPCIDEnv) + resources := newResources(clusterID, clusterVPCID) return &cloud{ client: doClient, diff --git a/cloud-controller-manager/do/loadbalancers_test.go b/cloud-controller-manager/do/loadbalancers_test.go index 632aa1511..6c68c80df 100644 --- a/cloud-controller-manager/do/loadbalancers_test.go +++ b/cloud-controller-manager/do/loadbalancers_test.go @@ -1958,7 +1958,7 @@ func Test_buildLoadBalancerRequest(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources("") + fakeResources := newResources("", "") fakeResources.UpdateDroplets(test.droplets) lb := &loadBalancers{ @@ -2040,7 +2040,7 @@ func Test_buildLoadBalancerRequestWithClusterID(t *testing.T) { }, } fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources(test.clusterID) + fakeResources := newResources(test.clusterID, test.vpcID) fakeResources.clusterVPCID = test.vpcID fakeResources.UpdateDroplets([]godo.Droplet{ { @@ -2179,7 +2179,7 @@ func Test_nodeToDropletIDs(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { fakeClient := newFakeLBClient(&fakeLBService{}) - fakeResources := newResources("") + fakeResources := newResources("", "") fakeResources.UpdateDroplets(test.droplets) lb := &loadBalancers{ @@ -2284,7 +2284,7 @@ func Test_GetLoadBalancer(t *testing.T) { for _, test := range testcases { t.Run(test.name, func(t *testing.T) { - fakeResources := newResources("") + fakeResources := newResources("", "") fakeResources.UpdateLoadBalancers(test.lbs) lb := &loadBalancers{ @@ -2488,7 +2488,7 @@ func Test_EnsureLoadBalancer(t *testing.T) { updateFn: test.updateFn, } fakeClient := newFakeLBClient(fakeLB) - fakeResources := newResources("") + fakeResources := newResources("", "") fakeResources.UpdateDroplets(test.droplets) fakeResources.UpdateLoadBalancers(test.lbs) diff --git a/cloud-controller-manager/do/resources.go b/cloud-controller-manager/do/resources.go index 7f26bc418..e03a1667a 100644 --- a/cloud-controller-manager/do/resources.go +++ b/cloud-controller-manager/do/resources.go @@ -59,9 +59,10 @@ type resources struct { mutex sync.RWMutex } -func newResources(clusterID string) *resources { +func newResources(clusterID, clusterVPCID string) *resources { return &resources{ - clusterID: clusterID, + clusterID: clusterID, + clusterVPCID: clusterVPCID, dropletIDMap: make(map[int]*godo.Droplet), dropletNameMap: make(map[string]*godo.Droplet), diff --git a/cloud-controller-manager/do/resources_test.go b/cloud-controller-manager/do/resources_test.go index ec07eee12..697984a00 100644 --- a/cloud-controller-manager/do/resources_test.go +++ b/cloud-controller-manager/do/resources_test.go @@ -78,7 +78,7 @@ func TestResources_DropletByID(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources(clusterID) + resources := newResources("", "") resources.UpdateDroplets(test.droplets) droplet, found := resources.DropletByID(test.findID) @@ -121,7 +121,7 @@ func TestResources_DropletByName(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources(clusterID) + resources := newResources("", "") resources.UpdateDroplets(test.droplets) droplet, found := resources.DropletByName(test.findName) @@ -183,7 +183,7 @@ func TestResources_LoadBalancerByID(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources(clusterID) + resources := newResources("", "") resources.UpdateLoadBalancers(test.lbs) lb, found := resources.LoadBalancerByID(test.findID) @@ -236,7 +236,7 @@ func TestResources_AddLoadBalancer(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - resources := newResources(clusterID) + resources := newResources("", "") resources.UpdateLoadBalancers(test.lbs) resources.AddLoadBalancer(test.newLB) @@ -310,7 +310,7 @@ var ( ) func TestResourcesController_Run(t *testing.T) { - fakeResources := newResources(clusterID) + fakeResources := newResources(clusterID, "") kclient := fake.NewSimpleClientset() inf := informers.NewSharedInformerFactory(kclient, 0) gclient := &godo.Client{ @@ -425,7 +425,7 @@ func TestResourcesController_SyncResources(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - fakeResources := newResources("") + fakeResources := newResources("", "") fakeResources.UpdateDroplets([]godo.Droplet{ {ID: 1, Name: "one"}, }) @@ -497,8 +497,7 @@ func TestResourcesController_SyncCluster(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - fakeResources := newResources(clusterID) - fakeResources.clusterVPCID = test.initalVPCID + fakeResources := newResources(clusterID, test.initalVPCID) kclient := fake.NewSimpleClientset() inf := informers.NewSharedInformerFactory(kclient, 0) gclient := &godo.Client{ @@ -642,7 +641,7 @@ func TestResourcesController_SyncTags(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - fakeResources := newResources(clusterID) + fakeResources := newResources("", "") for _, lb := range test.lbs { lb := lb fakeResources.loadBalancerIDMap[lb.ID] = lb diff --git a/docs/getting-started.md b/docs/getting-started.md index d2b6ed5a0..43af94a11 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -53,10 +53,14 @@ In the future, it may implement: ### Resource Tagging -When the environment variable `DO_CLOUD_ID` is given, `digitalocean-cloud-controller-manager` will use it to tag DigitalOcean resources additionally created during runtime (such us load-balancers) accordingly. The cloud ID is usually represented by a UUID and prefixed with `k8s:` when tagging, e.g., `k8s:c63024c5-adf7-4459-8547-9c0501ad5a51`. +When the environment variable `DO_CLUSTER_ID` is given, `digitalocean-cloud-controller-manager` will use it to tag DigitalOcean resources additionally created during runtime (such us load-balancers) accordingly. The cloud ID is usually represented by a UUID and prefixed with `k8s:` when tagging, e.g., `k8s:c63024c5-adf7-4459-8547-9c0501ad5a51`. The primary purpose of the variable is to allow DigitalOcean customers to easily understand which resources belong to the same DOKS cluster. Specifically, it is not needed (nor helpful) to have in DIY cluster installations. +### Custom VPC + +When a cluster is created in a non-default VPC for the region, the environment variable `DO_CLUSTER_VPC_ID` must be specified or Load Balancer creation for services will fail. + ## Deployment ### Token From 7a9b482b79e30083ff0d5e9f9a588c27c725d429 Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Tue, 2 Apr 2019 12:32:11 -0400 Subject: [PATCH 7/8] Remove syncCluster loop and vpc id + cluster id check --- cloud-controller-manager/do/loadbalancers.go | 6 -- .../do/loadbalancers_test.go | 6 -- cloud-controller-manager/do/resources.go | 25 ------ cloud-controller-manager/do/resources_test.go | 80 +------------------ 4 files changed, 1 insertion(+), 116 deletions(-) diff --git a/cloud-controller-manager/do/loadbalancers.go b/cloud-controller-manager/do/loadbalancers.go index 332902462..9049b5d2c 100644 --- a/cloud-controller-manager/do/loadbalancers.go +++ b/cloud-controller-manager/do/loadbalancers.go @@ -313,12 +313,6 @@ func (l *loadBalancers) nodesToDropletIDs(nodes []*v1.Node) ([]int, error) { // buildLoadBalancerRequest returns a *godo.LoadBalancerRequest to balance // requests for service across nodes. func (l *loadBalancers) buildLoadBalancerRequest(service *v1.Service, nodes []*v1.Node) (*godo.LoadBalancerRequest, error) { - // when a cluster has a cluster id it is a DOKS managed cluster, these cluster - // must specify a vpc id - if l.resources.clusterID != "" && l.resources.clusterVPCID == "" { - return nil, errors.New("missing cluster vpc id") - } - lbName := cloudprovider.GetLoadBalancerName(service) dropletIDs, err := l.nodesToDropletIDs(nodes) diff --git a/cloud-controller-manager/do/loadbalancers_test.go b/cloud-controller-manager/do/loadbalancers_test.go index 6c68c80df..1bd219640 100644 --- a/cloud-controller-manager/do/loadbalancers_test.go +++ b/cloud-controller-manager/do/loadbalancers_test.go @@ -2004,12 +2004,6 @@ func Test_buildLoadBalancerRequestWithClusterID(t *testing.T) { clusterID: "", vpcID: "vpc_uuid", }, - { - name: "missing vpc id", - clusterID: clusterID, - vpcID: "", - err: errors.New("missing cluster vpc id"), - }, } for _, test := range tests { diff --git a/cloud-controller-manager/do/resources.go b/cloud-controller-manager/do/resources.go index e03a1667a..b65cc7da1 100644 --- a/cloud-controller-manager/do/resources.go +++ b/cloud-controller-manager/do/resources.go @@ -35,10 +35,8 @@ import ( ) const ( - controllerSyncClusterPeriod = 1 * time.Minute controllerSyncTagsPeriod = 1 * time.Minute controllerSyncResourcesPeriod = 1 * time.Minute - syncClusterTimeout = 1 * time.Minute syncTagsTimeout = 1 * time.Minute syncResourcesTimeout = 3 * time.Minute ) @@ -239,7 +237,6 @@ func (r *ResourcesController) Run(stopCh <-chan struct{}) { glog.Info("No cluster ID configured -- skipping cluster dependent syncers.") return } - go r.syncer.Sync("cluster syncer", controllerSyncClusterPeriod, stopCh, r.syncCluster) go r.syncer.Sync("tags syncer", controllerSyncTagsPeriod, stopCh, r.syncTags) } @@ -270,28 +267,6 @@ func (r *ResourcesController) syncResources() error { return nil } -// syncCluster updates the local cluster state to match what is reported by the -// DigitalOcean API. -func (r *ResourcesController) syncCluster() error { - ctx, cancel := context.WithTimeout(context.Background(), syncClusterTimeout) - defer cancel() - - if r.resources.clusterVPCID != "" { - glog.V(2).Info("cluster VPC ID present, skipping sync.") - return nil - } - - glog.V(2).Info("cluster VPC ID missing, syncing cluster.") - cluster, _, err := r.gclient.Kubernetes.Get(ctx, r.resources.clusterID) - if err != nil { - return err - } - r.resources.clusterVPCID = cluster.VPCUUID - glog.V(2).Info("synced cluster.") - - return nil -} - // syncTags synchronizes tags. Currently, this is only needed to associate // cluster ID tags with LoadBalancer resources. func (r *ResourcesController) syncTags() error { diff --git a/cloud-controller-manager/do/resources_test.go b/cloud-controller-manager/do/resources_test.go index 697984a00..2931cee14 100644 --- a/cloud-controller-manager/do/resources_test.go +++ b/cloud-controller-manager/do/resources_test.go @@ -347,7 +347,7 @@ func TestResourcesController_Run(t *testing.T) { case <-time.After(3 * time.Second): // Terminate goroutines just in case. close(stop) - t.Errorf("resources calls: %d cluster calls: %d tags calls: %d", syncer.synced["resources syncer"], syncer.synced["cluster syncer"], syncer.synced["tags syncer"]) + t.Errorf("resources calls: %d tags calls: %d", syncer.synced["resources syncer"], syncer.synced["tags syncer"]) } } @@ -447,84 +447,6 @@ func TestResourcesController_SyncResources(t *testing.T) { } } -func TestResourcesController_SyncCluster(t *testing.T) { - tests := []struct { - name string - kubernetesSvc godo.KubernetesService - initalVPCID string - expectedVPCID string - err error - }{ - { - name: "happy path", - kubernetesSvc: &fakeKubernetesService{ - getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { - return &godo.KubernetesCluster{ - ID: clusterID, - VPCUUID: "vpc_uuid", - }, newFakeOKResponse(), nil - }, - }, - initalVPCID: "", - expectedVPCID: "vpc_uuid", - }, - { - name: "vpc id already present", - kubernetesSvc: &fakeKubernetesService{ - getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { - return &godo.KubernetesCluster{ - ID: clusterID, - VPCUUID: "vpc_uuid", - }, newFakeOKResponse(), nil - }, - }, - initalVPCID: "vpc_uuid", - expectedVPCID: "vpc_uuid", - }, - { - name: "kubernetes svc failure", - kubernetesSvc: &fakeKubernetesService{ - getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { - return nil, newFakeNotOKResponse(), errors.New("fail") - }, - }, - err: errors.New("fail"), - }, - } - - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - fakeResources := newResources(clusterID, test.initalVPCID) - kclient := fake.NewSimpleClientset() - inf := informers.NewSharedInformerFactory(kclient, 0) - gclient := &godo.Client{ - Kubernetes: test.kubernetesSvc, - } - res := NewResourcesController(fakeResources, inf.Core().V1().Services(), kclient, gclient) - - err := res.syncCluster() - if test.err != nil { - if err == nil { - t.Fatal("expected error but got none") - } - if want, got := test.err, err; !reflect.DeepEqual(want, got) { - t.Errorf("incorrect err\nwant: %#v\n got: %#v", want, got) - } - return - } - if err != nil { - t.Fatalf("unexpected err: %s", err) - } - if want, got := test.expectedVPCID, fakeResources.clusterVPCID; want != got { - t.Errorf("incorrect vpc id\nwant: %#v\n got: %#v", want, got) - } - }) - } -} - func lbName(idx int) string { svc := createSvc(idx, false) return cloudprovider.GetLoadBalancerName(svc) From 19afa09baca97c26ae825e51f6f5b6e7bedd7769 Mon Sep 17 00:00:00 2001 From: Nan Zhong Date: Wed, 3 Apr 2019 09:00:04 -0400 Subject: [PATCH 8/8] Remove unused fake testing service --- cloud-controller-manager/do/resources_test.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/cloud-controller-manager/do/resources_test.go b/cloud-controller-manager/do/resources_test.go index 2931cee14..9f23e1ba5 100644 --- a/cloud-controller-manager/do/resources_test.go +++ b/cloud-controller-manager/do/resources_test.go @@ -39,16 +39,6 @@ import ( "k8s.io/kubernetes/pkg/cloudprovider" ) -type fakeKubernetesService struct { - godo.KubernetesService - - getFunc func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) -} - -func (s *fakeKubernetesService) Get(ctx context.Context, id string) (*godo.KubernetesCluster, *godo.Response, error) { - return s.getFunc(ctx, id) -} - func TestResources_DropletByID(t *testing.T) { tests := []struct { name string @@ -324,14 +314,6 @@ func TestResourcesController_Run(t *testing.T) { return []godo.LoadBalancer{{ID: "2", Name: "two"}}, newFakeOKResponse(), nil }, }, - Kubernetes: &fakeKubernetesService{ - getFunc: func(context.Context, string) (*godo.KubernetesCluster, *godo.Response, error) { - return &godo.KubernetesCluster{ - ID: "uuid", - VPCUUID: "vpc_uuid", - }, newFakeOKResponse(), nil - }, - }, } res := NewResourcesController(fakeResources, inf.Core().V1().Services(), kclient, gclient)