From 015dce0d7903a0d60792f956b6c48574fb7023f8 Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Wed, 19 Feb 2020 12:14:59 +0100 Subject: [PATCH] Add ServerGroupID to OpenstackProviderSpec With this change, it is possible to pass an OpenStack Server Group[1] in the machine configuration, so that the machine will be assigned to it. Prerequisite for OSASINFRA-1300 (soft-anti-affinity) [1]: https://docs.openstack.org/python-openstackclient/latest/cli/command-objects/server-group.html --- Gopkg.lock | 4 +- .../openstackproviderconfig/v1alpha1/types.go | 3 + pkg/cloud/openstack/clients/machineservice.go | 11 ++ .../v2/extensions/schedulerhints/doc.go | 76 ++++++++ .../v2/extensions/schedulerhints/requests.go | 176 ++++++++++++++++++ 5 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go diff --git a/Gopkg.lock b/Gopkg.lock index 05861635be..737a6a749e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -216,7 +216,7 @@ [[projects]] branch = "master" - digest = "1:11a1c19c48edfb00986c3d11842b5aae6c47453ce3b5e10e2f19541e97764dfe" + digest = "1:4f251dbf4e7aeee58fc200e91330cb5bfc32530504c714dd18ae80297a91dc7a" name = "github.com/gophercloud/gophercloud" packages = [ ".", @@ -228,6 +228,7 @@ "openstack/compute/v2/extensions/bootfromvolume", "openstack/compute/v2/extensions/floatingips", "openstack/compute/v2/extensions/keypairs", + "openstack/compute/v2/extensions/schedulerhints", "openstack/compute/v2/flavors", "openstack/compute/v2/images", "openstack/compute/v2/servers", @@ -1025,6 +1026,7 @@ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume", "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips", "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs", + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints", "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", "github.com/gophercloud/gophercloud/openstack/compute/v2/servers", "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", diff --git a/pkg/apis/openstackproviderconfig/v1alpha1/types.go b/pkg/apis/openstackproviderconfig/v1alpha1/types.go index 8cb14be922..da8edf55fe 100644 --- a/pkg/apis/openstackproviderconfig/v1alpha1/types.go +++ b/pkg/apis/openstackproviderconfig/v1alpha1/types.go @@ -82,6 +82,9 @@ type OpenstackProviderSpec struct { // The volume metadata to boot from RootVolume *RootVolume `json:"rootVolume,omitempty"` + + // The server group to assign the machine to + ServerGroupID string `json:"serverGroupID,omitempty"` } type SecurityGroupParam struct { diff --git a/pkg/cloud/openstack/clients/machineservice.go b/pkg/cloud/openstack/clients/machineservice.go index 3623a6dad9..bac0b417d1 100644 --- a/pkg/cloud/openstack/clients/machineservice.go +++ b/pkg/cloud/openstack/clients/machineservice.go @@ -37,6 +37,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" @@ -724,6 +725,16 @@ func (is *InstanceService) InstanceCreate(clusterName string, name string, clust } + // If the spec sets a server group, then add scheduler hint + if config.ServerGroupID != "" { + serverCreateOpts = schedulerhints.CreateOptsExt{ + CreateOptsBuilder: serverCreateOpts, + SchedulerHints: schedulerhints.SchedulerHints{ + Group: config.ServerGroupID, + }, + } + } + server, err := servers.Create(is.computeClient, keypairs.CreateOptsExt{ CreateOptsBuilder: serverCreateOpts, KeyName: keyName, diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go new file mode 100644 index 0000000000..2d9d3acdec --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/doc.go @@ -0,0 +1,76 @@ +/* +Package schedulerhints extends the server create request with the ability to +specify additional parameters which determine where the server will be +created in the OpenStack cloud. + +Example to Add a Server to a Server Group + + schedulerHints := schedulerhints.SchedulerHints{ + Group: "servergroup-uuid", + } + + serverCreateOpts := servers.CreateOpts{ + Name: "server_name", + ImageRef: "image-uuid", + FlavorRef: "flavor-uuid", + } + + createOpts := schedulerhints.CreateOptsExt{ + CreateOptsBuilder: serverCreateOpts, + SchedulerHints: schedulerHints, + } + + server, err := servers.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Place Server B on a Different Host than Server A + + schedulerHints := schedulerhints.SchedulerHints{ + DifferentHost: []string{ + "server-a-uuid", + } + } + + serverCreateOpts := servers.CreateOpts{ + Name: "server_b", + ImageRef: "image-uuid", + FlavorRef: "flavor-uuid", + } + + createOpts := schedulerhints.CreateOptsExt{ + CreateOptsBuilder: serverCreateOpts, + SchedulerHints: schedulerHints, + } + + server, err := servers.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Place Server B on the Same Host as Server A + + schedulerHints := schedulerhints.SchedulerHints{ + SameHost: []string{ + "server-a-uuid", + } + } + + serverCreateOpts := servers.CreateOpts{ + Name: "server_b", + ImageRef: "image-uuid", + FlavorRef: "flavor-uuid", + } + + createOpts := schedulerhints.CreateOptsExt{ + CreateOptsBuilder: serverCreateOpts, + SchedulerHints: schedulerHints, + } + + server, err := servers.Create(computeClient, createOpts).Extract() + if err != nil { + panic(err) + } +*/ +package schedulerhints diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go new file mode 100644 index 0000000000..963934507f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints/requests.go @@ -0,0 +1,176 @@ +package schedulerhints + +import ( + "encoding/json" + "net" + "regexp" + "strings" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" +) + +// SchedulerHints represents a set of scheduling hints that are passed to the +// OpenStack scheduler. +type SchedulerHints struct { + // Group specifies a Server Group to place the instance in. + Group string + + // DifferentHost will place the instance on a compute node that does not + // host the given instances. + DifferentHost []string + + // SameHost will place the instance on a compute node that hosts the given + // instances. + SameHost []string + + // Query is a conditional statement that results in compute nodes able to + // host the instance. + Query []interface{} + + // TargetCell specifies a cell name where the instance will be placed. + TargetCell string `json:"target_cell,omitempty"` + + // BuildNearHostIP specifies a subnet of compute nodes to host the instance. + BuildNearHostIP string + + // AdditionalProperies are arbitrary key/values that are not validated by nova. + AdditionalProperties map[string]interface{} +} + +// CreateOptsBuilder builds the scheduler hints into a serializable format. +type CreateOptsBuilder interface { + ToServerSchedulerHintsCreateMap() (map[string]interface{}, error) +} + +// ToServerSchedulerHintsMap builds the scheduler hints into a serializable format. +func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interface{}, error) { + sh := make(map[string]interface{}) + + uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$") + + if opts.Group != "" { + if !uuidRegex.MatchString(opts.Group) { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.Group" + err.Value = opts.Group + err.Info = "Group must be a UUID" + return nil, err + } + sh["group"] = opts.Group + } + + if len(opts.DifferentHost) > 0 { + for _, diffHost := range opts.DifferentHost { + if !uuidRegex.MatchString(diffHost) { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.DifferentHost" + err.Value = opts.DifferentHost + err.Info = "The hosts must be in UUID format." + return nil, err + } + } + sh["different_host"] = opts.DifferentHost + } + + if len(opts.SameHost) > 0 { + for _, sameHost := range opts.SameHost { + if !uuidRegex.MatchString(sameHost) { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.SameHost" + err.Value = opts.SameHost + err.Info = "The hosts must be in UUID format." + return nil, err + } + } + sh["same_host"] = opts.SameHost + } + + /* + Query can be something simple like: + [">=", "$free_ram_mb", 1024] + + Or more complex like: + ['and', + ['>=', '$free_ram_mb', 1024], + ['>=', '$free_disk_mb', 200 * 1024] + ] + + Because of the possible complexity, just make sure the length is a minimum of 3. + */ + if len(opts.Query) > 0 { + if len(opts.Query) < 3 { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.Query" + err.Value = opts.Query + err.Info = "Must be a conditional statement in the format of [op,variable,value]" + return nil, err + } + + // The query needs to be sent as a marshalled string. + b, err := json.Marshal(opts.Query) + if err != nil { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.Query" + err.Value = opts.Query + err.Info = "Must be a conditional statement in the format of [op,variable,value]" + return nil, err + } + + sh["query"] = string(b) + } + + if opts.TargetCell != "" { + sh["target_cell"] = opts.TargetCell + } + + if opts.BuildNearHostIP != "" { + if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil { + err := gophercloud.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.BuildNearHostIP" + err.Value = opts.BuildNearHostIP + err.Info = "Must be a valid subnet in the form 192.168.1.1/24" + return nil, err + } + ipParts := strings.Split(opts.BuildNearHostIP, "/") + sh["build_near_host_ip"] = ipParts[0] + sh["cidr"] = "/" + ipParts[1] + } + + if opts.AdditionalProperties != nil { + for k, v := range opts.AdditionalProperties { + sh[k] = v + } + } + + return sh, nil +} + +// CreateOptsExt adds a SchedulerHints option to the base CreateOpts. +type CreateOptsExt struct { + servers.CreateOptsBuilder + + // SchedulerHints provides a set of hints to the scheduler. + SchedulerHints CreateOptsBuilder +} + +// ToServerCreateMap adds the SchedulerHints option to the base server creation options. +func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToServerCreateMap() + if err != nil { + return nil, err + } + + schedulerHints, err := opts.SchedulerHints.ToServerSchedulerHintsCreateMap() + if err != nil { + return nil, err + } + + if len(schedulerHints) == 0 { + return base, nil + } + + base["os:scheduler_hints"] = schedulerHints + + return base, nil +}