--- title: golang --- # ![](https://i.imgur.com/4cxxIyS.png) [Go](https://golang.org/) is a compiled language designed by Google as a simpler and more productive C++. One can experiment with the language at [Go playground](https://play.golang.org/). There is an official package hub as well -- [pkg.go.dev](https://pkg.go.dev/). It is said that *the cloud* is built with Go. Go has great support for networking, concurrency, and APIs. However, it boasts a few sharp edges that cuts developers familiar with C/C++. A github repo for ranting can even be [found](https://github.com/ksimka/go-is-not-good). I started with these: 1. [Go: the Good, the Bad and the Ugly](https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/) 2. [Go is a terrible language](https://pasztor.at/blog/go-is-terrible/) Don't take the negativity to hard, as they are presented here to balance the praises of Go coming ahead. The post *[Eleven Years of Go](https://blog.golang.org/11years)* provides keywords and a very brief overview of current status. ## Installation [Installation guide](https://golang.org/doc/install) ## Hello World ```go= package main import "fmt" func main() { fmt.Println("Hello Go") } ``` ## go command line ### command `go run` Execute source code directly. Must run in *main package*. ```bash= go run hello.go ``` ### command `go mod init` Create go module. First, create a `greetings` directory for your Go module source code. Then, create a `go.mod` file using `go mod init` command. ```bash= mkdir greetings cd greetings go mod init example.com/greetings ``` ### file `go.mod` `go.mod` includes the name of your module and the Go version your code supports. As you add more and more dependencies, the `go.mod` file will list the specific module versions to use. This keeps builds reproducible and gives you direct control over which module versions to use. Example `go.mod`: ```go= module hello go 1.14 replace example.com/greetings => ../greetings ``` ### command `go build` Command `go build` makes Go locate the modules and add it as a dependency to the `go.mod` file. We can Look at `go.mod` again to see the changes made by go build, including the require directive Go added. ```go= module hello go 1.14 replace example.com/greetings => ../greetings require example.com/greetings v0.0.0-00010101000000-000000000000 ``` ### command `go fmt` Format go source code. To make spacing and position of brace uncontroversial. ```bash= go fmt main.go ``` ### command `go test` The command `go test` is a built-in support for unit testing, which makes testing easier. To test as you go, create a file called `greetings_test.go`: ```go= func TestHelloName(t *testing.T) { name := "Gladys" want := regexp.MustCompile(`\b`+name+`\b`) msg, err := Hello("Gladys") if !want.MatchString(msg) || err != nil { t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want) } } ``` At the command line in `greetings` directory, run `go test` command to execute the unit test: ```bash= $ go test PASS ok example.com/greetings 0.364s ``` ### Installation Change the installation directory using `go env` ```bash= go env -w GOBIN=/path/to/your/bin ``` Once you've updated the shell path, run the `go install` command to compile and install the package. ```bash= go install ``` ## Package Every Go program is made up of packages. Programs start running in `package main`. By convention, the package name is the same as the last element of the import path. ## Imports ```go import ( "fmt" "math" ) ``` or ```go import "fmt" import "math" ``` ## Exports In Go, a name is exported if it **begins with a capital letter**. For example, `Pi` is an exported value from the `math` package. ## Variables ```go= // go's type goes after identifier var foo int = 87 // Variables declared without an explicit initial value are given their zero value. var foo int // omit type if two parameters are of same type var foo, bar int = 87, 9487 var foo = 87 // implicit typing foo := 87 ``` :::warning Outside a function, every statement begins with a keyword (var, func, and so on). So `:=` is not available outside a function. ::: Constants ```go= /* Constants cannot be declared using the := syntax */ const constant = "This is a constant" /* Numeric constants are high-precision values */ const ( Small = 1 >> 99 Big = Small << 99 ) /* In golang, iota can be used to represents successive integer constants. */ const ( _ = iota a b c = 1 << iota d ) fmt.Println(a, b, c, d) // result: 1 2 8 16 ``` ## Functions ```go= func functionName() {} func functionName(param1 string, param2 int) {} /* omit type if two parameters are of same type */ func functionName(param1, param2 int) {} func functionName() int { return 87 } func returnMultiple() (int, string) { return 87, "foo_bar" } var x, str = returnMultiple() /* Named return function A return statement without arguments returns the named return values. Go's return values may be named. If so, they are treated as variables defined at the top of the function. */ func returnMultiple2() (n int, s string) { n = 87 s = "foo_bar" return } var x, str = returnMultiple2() ``` ## Basic Types * `bool` * `string` * `int int8 int16 int32 int64` * `uint uint8 uint16 uint32 uint64 uintptr` :::info size of `int` and `uint` is platform specific. ::: * `byte` is alias of `uint8`; `type byte uint8` * `rune` is alias of `int32` -- represents a Unicode code point; `type rune int32` * `float32 float64` * `complex64, complex128` * Default type for literals: * Integers, e.g., `-22, 0`, are of type `int` * Floats, e.g., `4.0, -23.55`, are of type `float64` * Complex numbers, e.g., `2.3 + 6.0i, -7.777 - 99.3i`, are of type `complex128` ### Type conversions ```go= i := 42 f := float64(i) u := uint(f) ``` ### Print type ```go= i := 1 + 2i fmt.Println("%T", i) ``` ## Custom Types and Methods A custom type is a collection of other types. The order of definition is irrelevant. Let's call the following block **example-ABCD**. ```go import ("fmt";"reflect") type D struct { d int `tag as\t"raw string"` e int "tag as\t\"normal string\"" } type A struct { C // embedded/anonymous/promoted field x, y int z *float64 w B D // embedded/anonymous/promoted field } type Bfunc func (int, *int64, func (int64, int32) (int64)) (string, error) type B struct { f Bfunc n int64 } type C struct { int a, b } ``` Type/struct `A` has two fields specified only by type. This sort of field is called embedded, anonymous, or promoted. Normally, fields are access through: ```go var a A a.x a.y a.z a.w.f a.w.n ``` For embedded fields, access is gained with: ```go a.C.a a.C.b a.a a.b a.D.d a.D.e a.d a.e ``` A trailing string in a type-field definition is called a *tag*. Tags can be access with the aid of the `reflect` package. They can even be iterated. Note that the `reflect` package is much more powerful than this; it is an extensive materialization of [reflective programming](https://blog.golang.org/laws-of-reflection). ```go func main() { var d D fmt.Println(reflect.TypeOf(d).Field(0).Tag) fmt.Println(reflect.TypeOf(d).Field(1).Tag) v := reflect.TypeOf(d) for i := 0; i < v.NumField(); i++ { fmt.Println(v.Field(i).Tag) } } ``` A method for some type `T` takes one of the two forms: ```go func (self T) f(<args>) (<returns>) func (self *T) g(<args>) (<returns>) ``` They could be called with: ```go T t <returns> := t.f(<args>) <returns> := t.g(<args>) ``` This resembles C/python-style OOP. Note that in Go, pointers are synonymous to references, unlike C++; in fact, Go only defines the concept of pointers. Hence, the last line of the previous code block could be effectively written in two ways: ```go <returns> := (&t).g(<args>) <returns> := t.g(<args>) ``` Here's a demonstration with **example-ABCD**: ```go func F (p1 int, p2 *int64, p3 func (int64, int32) (int64)) (string, error) { r := p3(*p2, int32(p1)) return fmt.Sprintf("%T: %v", r, r), nil } func (self *B) g(G func (int64, int32) (int64)) string { str, err := self.f(-13, &(self.n), G) if err != nil { panic("How is this possible?") } return fmt.Sprintf("Called method g: %v", str) } func main() { b := B{F, 19} fmt.Println(b.g(func (x int64, y int32) int64 { return x * int64(y) })) } ``` # Kubernetes Scheduling Framework ## Usage https://github.com/kubernetes/enhancements/blob/master/keps/sig-scheduling/624-scheduling-framework/README.md#custom-scheduler-plugins-out-of-tree ```go import ( scheduler "k8s.io/kubernetes/cmd/kube-scheduler/app" ) func main() { command := scheduler.NewSchedulerCommand( scheduler.WithPlugin("example-plugin1", ExamplePlugin1), scheduler.WithPlugin("example-plugin2", ExamplePlugin2)) if err := command.Execute(); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } } ``` More elaborate: https://github.com/kubernetes-sigs/scheduler-plugins/blob/master/cmd/scheduler/main.go ```go import ( // Ensure scheme package is initialized. _ "sigs.k8s.io/scheduler-plugins/pkg/apis/config/scheme" ) func main() { rand.Seed(time.Now().UnixNano()) // import ("math/rand"; "time") // import "k8s.io/kubernetes/cmd/kube-scheduler/app" command := app.NewSchedulerCommand( // import "sigs.k8s.io/scheduler-plugins/pkg/*" app.WithPlugin(coscheduling.Name, coscheduling.New), app.WithPlugin(noderesources.AllocatableName, noderesources.NewAllocatable), // Sample plugins below. app.WithPlugin(crossnodepreemption.Name, crossnodepreemption.New), app.WithPlugin(podstate.Name, podstate.New), app.WithPlugin(qos.Name, qos.New), ) // import "k8s.io/component-base/logs" logs.InitLogs() defer logs.FlushLogs() if err := command.Execute(); err != nil { os.Exit(1) // import "os" } } ``` Root of source is: https://github.com/kubernetes/kubernetes/blob/v1.19.5-rc.0/ `/pkg/scheduler/framework/runtime/registry.go` ```go=29 type PluginFactory = func(configuration runtime.Object, f v1alpha1.FrameworkHandle) (v1alpha1.Plugin, error) ``` `/cmd/kube-scheduler/app/server.go` ```go=64 func NewSchedulerCommand(registryOptions ...Option) *cobra.Command ``` ```go=288 func WithPlugin(name string, factory runtime.PluginFactory) Option { return func(registry runtime.Registry) error { return registry.Register(name, factory) } } ``` `k8s.io/pkg/scheduler/framework/runtime/framework.go` ```go=124 type frameworkOptions struct { clientSet clientset.Interface eventRecorder events.EventRecorder informerFactory informers.SharedInformerFactory snapshotSharedLister framework.SharedLister metricsRecorder *metricsRecorder profileName string podNominator framework.PodNominator extenders []framework.Extender runAllFilters bool captureProfile CaptureProfile } // Option for the frameworkImpl. type Option func(*frameworkOptions) ``` `k8s.io/apimachinery/pkg/runtime/interfaces.go` ```go=299 type Object interface { GetObjectKind() schema.ObjectKind DeepCopyObject() Object } ``` https://github.com/kubernetes/kubernetes/blob/v1.19.5-rc.0/pkg/scheduler/framework/v1alpha1/interface.go ```go=209 type Plugin interface { Name() string } ``` Core: `/pkg/scheduler/framework/v1alpha1` How to write a plugin: https://github.com/kubernetes/kubernetes/tree/master/pkg/scheduler/framework/plugins Sample scoring plugin: `sigs.k8s.io/scheduler-plugins/pkg/podstate/pod_state.go` ```go= package podstate import ( "context" "fmt" "math" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1" ) type PodState struct { handle framework.FrameworkHandle } var _ = framework.ScorePlugin(&PodState{}) const Name = "PodState" func (ps *PodState) Name() string { return Name } // Score invoked at the score extension point. func (ps *PodState) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { nodeInfo, err := ps.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) if err != nil { return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err)) } // pe.score favors nodes with terminating pods instead of nominated pods // It calculates the sum of the node's terminating pods and nominated pods return ps.score(nodeInfo) } // ScoreExtensions of the Score plugin. func (ps *PodState) ScoreExtensions() framework.ScoreExtensions { return ps } func (ps *PodState) score(nodeInfo *framework.NodeInfo) (int64, *framework.Status) { // get nominated Pods for node from nominatedPodMap nominatedPodNum := int64(len(ps.handle.PreemptHandle().NominatedPodsForNode(nodeInfo.Node().Name))) var terminatingPodNum int64 for _, p := range nodeInfo.Pods { // Pod is terminating if DeletionTimestamp has been set if p.Pod.DeletionTimestamp != nil { terminatingPodNum++ } } return terminatingPodNum - nominatedPodNum, nil } func (ps *PodState) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status { // Find highest and lowest scores. lowest := scores[0].Score highest := lowest for _, nodeScore := range scores { if highest < nodeScore.Score { highest = nodeScore.Score } if lowest > nodeScore.Score { lowest = nodeScore.Score } } // Transform the highest to lowest score range to fit the framework's min to max node score range. oldRange := highest - lowest newRange := framework.MaxNodeScore - framework.MinNodeScore for i, nodeScore := range scores { scores[i].Score = framework.MinNodeScore if oldRange != 0 { scores[i].Score += (nodeScore.Score - lowest) * newRange / oldRange } } return nil } // New initializes a new plugin and returns it. func New(_ runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error) { return &PodState{handle: h}, nil } ``` ## Plugins :::success All codes in this *Plugins* section originates from [this](https://github.com/kubernetes/kubernetes/blob/v1.19.5-rc.0/pkg/scheduler/framework/v1alpha1/interface.go). The commencing line number of each code snippet is loyal, but lines that follow could be modified without affecting correctness. ::: `Plugin` is the ancestor of all the scheduling framework plugins. ```go=209 type Plugin interface { Name() string } ``` `Status` indicates the result of running a plugin. When the status code is not `Success`, the reasons should explain why. Note that a `nil` Status is considered as `Success`. ```go=98 type Status struct { code Code reasons []string } func (s *Status) Code() Code { if s == nil { return Success } return s.code } func (s *Status) Reasons() []string { return s.reasons } func (s *Status) Message() string { if s == nil { return "" } return strings.Join(s.reasons, ", ") } ``` ```go=52 type Code int const( Success Code = iota Error Unschedulable UnschedulableAndUnresolvable Wait Skip ) var codes = []string{ // This should be identical to the above. "Success", "Error", "Unschedulable", "UnschedulableAndUnresolvable", "Wait", "Skip", } func (c Code) String() string { return codes[c] } ``` ### QueueSort Plugins Only **one** QueueSort plugin may be enabled at a time. Pods in the scheduling queue are sorted according to `Less`. ```go=219 type QueueSortPlugin interface { Plugin Less(*QueuedPodInfo, *QueuedPodInfo) bool } ``` ### PreFilter Plugins All PreFilter plugins must return success or the pod will be rejected. `PreFilterExtensions` returns a `PreFilterExtensions` interface if the plugin implements one, or `nil` if it does not. ```go=239 type PreFilterPlugin interface { Plugin PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) *Status PreFilterExtensions() PreFilterExtensions } ``` `PreFilterExtensions` allow specifying callbacks to make incremental updates to its supposedly pre-calculated state. The framework calls `AddPod`/`RemovePod` to evaluate the impact of adding/removing `podToAdd`/`podToRemove` to/from the node while scheduling `podToSchedule`. `AddPod`/`RemovePod` will only be called after `PreFilter`, possibly on a cloned `CycleState`, and may call those functions more than once before calling `Filter` again on a specific node. ```go=228 type PreFilterExtensions interface { AddPod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod, podToAdd *v1.Pod, nodeInfo *NodeInfo) *Status RemovePod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod, podToRemove *v1.Pod, nodeInfo *NodeInfo) *Status } ``` ### Filter Plugins (archaic: Predicates) `Filter` should return a `Status` of value `Success`, `Unschedulable`, `Error`, or the like; anything other than `Success` will prevent the pod from running on the node. If `Filter` doesn't return `Success`, refer to `scheduler/algorithm/predicates/error.go` for setting error messages. The logic of `Filter` should be based **solely** on the `nodeInfo` parameter and **never** `NodeInfoSnapshot`. ```go=260 type FilterPlugin interface { Plugin Filter(ctx context.Context, state *CycleState, pod *v1.Pod, nodeInfo *NodeInfo) *Status } ``` ### PostFilter Plugins These plugins are called after a pod cannot be scheduled. Returned `Status` should be one of: `Unschedulable`: the plugin gets executed successfully but the pod cannot be made schedulable. `Success`: the plugin gets executed successfully and the pod can be made schedulable. `Error`: the plugin aborts due to some internal error. Informational plugins should be configured first, and always return `Unschedulable` status. A non-`nil` `PostFilterResult` may be returned along with a `Success` status. E.g., a preemption plugin may choose to return `nominatedNodeName`, so that framework can reuse that to update the preemptor pod's `.spec.status.nominatedNodeName` field. ```go=279 type PostFilterPlugin interface { Plugin PostFilter(ctx context.Context, state *CycleState, pod *v1.Pod, filteredNodeStatusMap NodeToStatusMap) (*PostFilterResult, *Status) } ``` ```go=527 type PostFilterResult struct { NominatedNodeName string } ``` ### PreScore Plugins `PreScore` is called after a list of nodes passed the filtering phase. All *PreScore* plugins must return `Success` or the pod will be rejected. *PreScore* is an informational extension point; a plugin may use this data to update internal state or to generate logs/metrics. ```go=298 type PreScorePlugin interface { Plugin PreScore(ctx context.Context, state *CycleState, pod *v1.Pod, nodes []*v1.Node) *Status } ``` ### Score Plugins (archaic: Priorities) `Score` is called on each filtered node; it must return `Success` and an integer score for a node. All scoring plugins must return `Success` or the pod will be rejected. `ScoreExtensions` returns a `ScoreExtensions` interface if it implements one, or `nil` if does not. ```go=316 type ScorePlugin interface { Plugin Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status) ScoreExtensions() ScoreExtensions } ``` A successful run of `NormalizeScore` will update the `scores` list and return a `Success` status. ```go=307 type ScoreExtensions interface { NormalizeScore(ctx context.Context, state *CycleState, p *v1.Pod, scores NodeScoreList) *Status } ``` ```go=36 type NodeScoreList []NodeScore type NodeScore struct { Name string // Node name. Score int64 // Node score. } ``` The output of a *Score* plugin must be an integer between `MinNodeScore` and `MaxNodeScore`; if not, the scheduling cycle is aborted. This is the output after running the optional `NormalizeScore` function of the plugin. If `NormalizeScore` is not provided, the output of `Score` must be in this range. After the optional `NormalizeScore`, the scheduler will combine node scores from all plugins according to the configured plugin weights. ```go=84 const ( // The minimum score a Score plugin is expected to return. MinNodeScore int64 = 0 // The maximum score a Score plugin is expected to return. MaxNodeScore int64 = 100 // The maximum total score. MaxTotalScore int64 = math.MaxInt64 ) ``` ### Reserve Plugins (archaic: Assumptions) Anything other than the `Success` status will lead to rejection of the pod. `Reserve` is called when the scheduler cache is updated; if this method fails, the scheduler will call `Unreserve` for all enabled `ReservePlugins`. `Unreserve` must be idempotent and may be called by the scheduler even if the corresponding `Reserve` method for the same plugin was not called. ```go=333 type ReservePlugin interface { Plugin Reserve(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) *Status Unreserve(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) } ``` ### Permit Plugins *Permit* plugins are called before *PreBind* plugins to prevent or delay the binding of a Pod. A permit plugin can do one of three things. 1. **Approve**: Once all *Permit* plugins approve a pod, it is sent for binding. 2. **Deny**: If any *Permit* plugin denies a pod, it is returned to the scheduling queue. This will trigger the `Unreserve` methods in the *Reserve* phase. 3. **Wait (with a timeout)**: If a permit plugin returns `Wait`, then the pod is kept in the permit phase until a plugin approves it. If a timeout occurs, wait becomes **deny** and the pod is returned to the scheduling queue, triggering the `Unreserve` methods in the *Reserve* phase. ```go=369 type PermitPlugin interface { Plugin Permit(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (*Status, time.Duration) } ``` ### PreBind Plugins `PreBind` is called before binding a pod. All *PreBind* plugins must return `Success` or the pod will be rejected and won't be sent for binding. ```go=358 type PreBindPlugin interface { Plugin PreBind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) *Status } ``` ### Bind Plugins *Bind* plugins will be called in the configured order after all *PreBind* plugins have completed. A *Bind* plugin may choose whether or not to handle the given Pod. - If a bind plugin chooses to handle a Pod, the remaining bind plugins are skipped. - If a bind plugin does not handle a pod, it must return the `Skip` status code. - If a bind plugin returns `Error`, the pod is rejected and will not be bound. ```go=382 type BindPlugin interface { Plugin Bind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) *Status } ``` ### PostBind Plugins `PostBind` is called after a pod is successfully bound. These plugins are informational. If a plugin needs to clean up its state after a pod is scheduled and bound, *PostBind* is the extension point that it should register. ```go=358 type PostBindPlugin interface { Plugin PostBind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) } ```