Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/rsearch #1

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rsearch/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vendor/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They seem to use godeps instead of vendoring, so this probably isn't needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I maintain godep directory, but with VENDOREXPERIMENT vendor/ folder gets created automatically. This line is to prevent accidental commits including this folder.

23 changes: 23 additions & 0 deletions rsearch/Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions rsearch/Godeps/Readme

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions rsearch/TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1. When starting server read through namespace list and fire up ns.Producer per namespace.
Read throug resource list per namespace and send them to Processor
2. Import Kubernetes and use Objects from there
1 change: 1 addition & 0 deletions rsearch/bin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin
65 changes: 65 additions & 0 deletions rsearch/bin/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"log"

search "github.com/romana/contrib/rsearch"
)

func main() {
var cfgFile = flag.String("c", "", "Kubernetes reverse search config file")
var server = flag.Bool("server-mode", false, "Start a server")
var host = flag.String("host", "", "Host for client to connect to")
var proto = flag.String("protocol", "", "Protocol to use for client connect to")
var port = flag.String("port", "", "TCP port for client to connect to")
var searchTag = flag.String("r", "", "Search resources by tag")
flag.Parse()

done := make(chan search.Done)

config, err := search.NewConfig(*cfgFile)
if err != nil {
log.Fatalf("Can not read config file %s, %s\n", *cfgFile, err)
}

if *host != "" {
config.Server.Host = *host

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is config a useful value if an error occured?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not an error check, though there should be one.

}

if *proto != "" {
config.Server.Proto = *proto
}

if *port != "" {
config.Server.Port = *port
}

if *server {
log.Println("Starting server")
nsUrl := fmt.Sprintf("%s/%s", config.Api.Url, config.Api.NamespaceUrl)
nsEvents, err := search.NsWatch(done, nsUrl, config)
if err != nil {
log.Fatal("Namespace watcher failed to start", err)
}

events := search.Conductor(nsEvents, done, config)
req := search.Process(events, done, config)
log.Println("All routines started")
search.Serve(config, req)
} else if len(*searchTag) > 0 {
if config.Server.Debug {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config.Server.Debug is controlling the debug level of a client?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And a server.

log.Println("Making request to the server")
}
r := search.SearchResource(config, search.SearchRequest{Tag: *searchTag})
response, err := json.Marshal(r)
if err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This err is from a higher scope.

log.Fatal("Failed to parse out server response, ", err)
}
fmt.Println(string(response))
} else {
log.Fatal("Either -s or -r must be given")
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if it were neither?

}
5 changes: 5 additions & 0 deletions rsearch/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing else has a build.sh, so this is a bit odd.
And because of Godeps, it should probably munge the GOPATH to include the Godeps/_workspace path before running go build.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh that's just a helper for myself - shouldn't be in the repo.

find . -type d | while read line; do
( echo Building in the $line ....; cd $line && go build .)
done
65 changes: 65 additions & 0 deletions rsearch/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) 2016 Pani Networks
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package rsearch

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
)

// SearchResource makes HTTP request to the server
// instance if this library.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this library - what? Incomplete sentence.

func SearchResource(config Config, req SearchRequest) SearchResponse {
// TODO need to make url configurable
url := fmt.Sprintf("%s://%s:%s", config.Server.Proto, config.Server.Host, config.Server.Port)
data := []byte(`{ "tag" : "` + req.Tag + `"}`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something's happening with data, but I have no idea what's going on. Some comments would be really useful.

if config.Server.Debug {
log.Printf("Making request with %s to the %s\n", string(data), url)
}

// Making request.
request, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
if err != nil {
log.Fatal("Failed to connect to server", url)
}

request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
response, err := client.Do(request)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might as well use http.DefaultClient if you're not doing any special setup of the client or transport.

if err != nil {
log.Fatal("HTTP request failed", url, err)
}

defer response.Body.Close()
decoder := json.NewDecoder(response.Body)
sr := SearchResponse{}
if config.Server.Debug {
log.Println("Trying to decode", response.Body)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response.Body won't be a stringified version of the response, but some arbitrary pointers and struct values like &{0xc8201040e0 {0 0} false <nil> 0xd4840 0xd47e0}

}
err = decoder.Decode(&sr)
if err != nil {
log.Println("Failed to decode", response.Body)
panic(err)
}

if config.Server.Debug {
fmt.Println("Decoded response form a server", sr)
}
return sr
}
76 changes: 76 additions & 0 deletions rsearch/conductor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2016 Pani Networks
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package rsearch

import (
"log"
)

// manageResources manages map of termination channels and fires up new
// per-namespace gorotines when needed.
func manageResources(ns Event, terminators map[string]chan Done, config Config, out chan Event) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring

uid := ns.Object.Metadata.Uid
if ns.Type == KubeEventAdded {
if _, ok := terminators[uid]; ok {
log.Println("Received ADDED event for uid that is already known, ignoring ", uid)
return
}

done := make(chan Done)
terminators[uid] = done

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it already existed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't really run into such case but if we are we just update termination channel for such goroutine and let it's previous termination channel to be garbage collected.

ns.Object.Produce(out, terminators[uid], config)
} else if ns.Type == KubeEventDeleted {
if _, ok := terminators[uid]; !ok {
log.Println("Received DELETED event for uid that is not known, ignoring ", uid)
return
}

close(terminators[uid])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it doesn't exist?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't really happen but we will blow up in this case. However, spitting out readable message could be helpful.

delete(terminators, uid)
} else if ns.Type == InternalEventDeleteAll {
for uid, c := range terminators {
close(c)
delete(terminators, uid)
}
}
}

// Conductor manages a set of goroutines one per namespace.
func Conductor(in <-chan Event, done <-chan Done, config Config) <-chan Event {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of done's in here. It's a bit confusing to follow the purpose of each of them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, i don't see a better way of doing it since each goroutine must receive some done channel and some goroutines are managing other goroutines so some goroutines would have their done channel and a set of done channels for their children.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For starters, you might add a comment that explains what's happening. Then this could also help to clear up 'done' ambiguities.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// done in arguments is a channel that can be used to stop Conductor itsefl
// while map of Done's below is for terminating managed gorotines.

// Idea of this map is to keep termination channels organized
// so when DELETED event occurs on a namespace it would be possible
// to terminater related goroutine.
terminators := map[string]chan Done{}

ns := Event{}
out := make(chan Event)

go func() {
for {
select {
case ns = <-in:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case ns := <-in:, then you won't need to predeclare it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is deliberate to show what kind of things coming out.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the declaration of in make that clear?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically yes. I just like it when i read it. I understand it's unnecessary, do you think it's also wrong thing to do ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not wrong, it was just surprising. It's a subjective thing.

manageResources(ns, terminators, config, out)
case <-done:
return
}
}
}()

return out
}
65 changes: 65 additions & 0 deletions rsearch/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) 2016 Pani Networks
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package rsearch

import (
"gopkg.in/gcfg.v1"
)

// Done is an alias for empty struct, used to make broadcast channels
// for terminating goroutines.
type Done struct{}

// Config is a top level struct describing expected structure of config file.
type Config struct {
Resource Resource
Server Server
Api Api
}

// Server is a config section describing server instance of this package.
type Server struct {
Port string
Host string
Proto string
Debug bool
}

// API is a config section describing kubernetes API parameters.
type Api struct {
Url string
NamespaceUrl string
}

// Resource is a config section describing kubernetes resource to be cached and searched for.
type Resource struct {
Name string
Type string
Selector string
Namespaced string
UrlPrefix string
UrlPostfix string
}

// NewConfig parsing config file and returning initialized instance of Config structure.
func NewConfig(configFile string) (Config, error) {
cfg := Config{}
if err := gcfg.ReadFileInto(&cfg, configFile); err != nil {
return Config{}, err
}

return cfg, nil
}
20 changes: 20 additions & 0 deletions rsearch/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
; comment
[api]
url=http://192.168.0.10:8080

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this just be watchURL as a single item? Does it actually vary between deployments?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kube api url does change, namespace endpoint might not change but we want to be able to define api url separately as it's going to be used in few places.

namespaceUrl=api/v1/namespaces/?watch=true

[resource]
;type=builtin # TBD
type=3rdParty
;namespaced=false # TBD

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add some comments after or before each config item to explain what the config is for? For example, it's not clear what "namespaced" means.

namespaced=true
urlPrefix=apis/romana.io/demo/v1/namespaces
urlPostfix=networkpolicys/?watch=true
name=NetworkPolicy
selector=podSelector

[server]
port=9700
proto=http
host=localhost
debug=false
18 changes: 18 additions & 0 deletions rsearch/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2016 Pani Networks
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// This package provides means of searching kubernets objects by their selectors.
// The goal is to give the answer to a question - which resources are selecting pods with given label
package rsearch
Loading