{"action":"created","comment":{"url":"https://api.github.com/repos/contiv/netplugin/pulls/comments/52267346","id":52267346,"diff_hunk":"@@ -0,0 +1,339 @@\n+/***\n+Copyright 2014 Cisco Systems Inc. All rights reserved.\n+\n+Licensed under the Apache License, Version 2.0 (the \"License\");\n+you may not use this file except in compliance with the License.\n+You may obtain a copy of the License at\n+http://www.apache.org/licenses/LICENSE-2.0\n+\n+Unless required by applicable law or agreed to in writing, software\n+distributed under the License is distributed on an \"AS IS\" BASIS,\n+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+See the License for the specific language governing permissions and\n+limitations under the License.\n+*/\n+\n+package main\n+\n+import (\n+\t\"fmt\"\n+\t\"net\"\n+\t\"net/http\"\n+\t\"os\"\n+\t\"sync\"\n+\n+\t\"github.com/contiv/netplugin/core\"\n+\t\"github.com/contiv/netplugin/drivers\"\n+\t\"github.com/contiv/netplugin/netmaster/master\"\n+\t\"github.com/contiv/netplugin/netmaster/mastercfg\"\n+\t\"github.com/contiv/netplugin/netmaster/objApi\"\n+\t\"github.com/contiv/netplugin/utils\"\n+\t\"github.com/contiv/objdb\"\n+\t\"github.com/contiv/ofnet\"\n+\t\"github.com/gorilla/mux\"\n+\n+\tlog \"github.com/Sirupsen/logrus\"\n+)\n+\n+const leaderLockTTL = 10\n+\n+type daemon struct {\n+\tlistenURL        string                // URL where netmaster needs to listen\n+\tcurrState        string                // Current state of the daemon\n+\tapiController    *objApi.APIController // API controller for contiv model\n+\tstateDriver      core.StateDriver      // KV store\n+\tobjdbClient      objdb.API             // Objdb client\n+\tofnetMaster      *ofnet.OfnetMaster    // Ofnet master instance\n+\tlistenerMutex    sync.Mutex            // Mutex for HTTP listener\n+\tstopLeaderChan   chan bool             // Channel to stop the leader listener\n+\tstopFollowerChan chan bool             // Channel to stop the follower listener\n+}\n+\n+var leaderLock objdb.LockInterface // leader lock\n+\n+func (d *daemon) registerService() {\n+\t// Get the address to be used for local communication\n+\tlocalIP, err := d.objdbClient.GetLocalAddr()\n+\tif err != nil {\n+\t\tlog.Fatalf(\"Error getting local IP address. Err: %v\", err)\n+\t}\n+\n+\t// service info\n+\tsrvInfo := objdb.ServiceInfo{\n+\t\tServiceName: \"netmaster\",\n+\t\tHostAddr:    localIP,\n+\t\tPort:        9999,\n+\t\tRole:        d.currState,\n+\t}\n+\n+\t// Register the node with service registry\n+\terr = d.objdbClient.RegisterService(srvInfo)\n+\tif err != nil {\n+\t\tlog.Fatalf(\"Error registering service. Err: %v\", err)\n+\t}\n+\n+\tlog.Infof(\"Registered netmaster service with registry\")\n+}\n+\n+// registerWebuiHandler registers handlers for serving web UI\n+func (d *daemon) registerWebuiHandler(router *mux.Router) {\n+\t// Setup the router to serve the web UI\n+\tgoPath := os.Getenv(\"GOPATH\")\n+\tif goPath != \"\" {\n+\t\twebPath := goPath + \"/src/github.com/contiv/contivmodel/www/\"\n+\n+\t\t// Make sure we have the web UI files\n+\t\t_, err := os.Stat(webPath)\n+\t\tif err != nil {\n+\t\t\twebPath = goPath + \"/src/github.com/contiv/netplugin/\" +\n+\t\t\t\t\"Godeps/_workspace/src/github.com/contiv/contivmodel/www/\"\n+\t\t\t_, err := os.Stat(webPath)\n+\t\t\tif err != nil {\n+\t\t\t\tlog.Errorf(\"Can not find the web UI directory\")\n+\t\t\t}\n+\t\t}\n+\n+\t\tlog.Infof(\"Using webPath: %s\", webPath)\n+\n+\t\t// serve static files\n+\t\trouter.PathPrefix(\"/web/\").Handler(http.StripPrefix(\"/web/\", http.FileServer(http.Dir(webPath))))\n+\n+\t\t// Special case to serve main index.html\n+\t\trouter.HandleFunc(\"/\", func(rw http.ResponseWriter, req *http.Request) {\n+\t\t\thttp.ServeFile(rw, req, webPath+\"index.html\")\n+\t\t})\n+\t}\n+\n+\t// proxy Handler\n+\trouter.PathPrefix(\"/proxy/\").HandlerFunc(proxyHandler)\n+}\n+\n+// registerRoutes registers HTTP route handlers\n+func (d *daemon) registerRoutes(router *mux.Router) {\n+\t// register web ui handlers\n+\td.registerWebuiHandler(router)\n+\n+\t// Add REST routes\n+\ts := router.Headers(\"Content-Type\", \"application/json\").Methods(\"Post\").Subrouter()\n+\n+\ts.HandleFunc(\"/plugin/allocAddress\", makeHTTPHandler(master.AllocAddressHandler))\n+\ts.HandleFunc(\"/plugin/releaseAddress\", makeHTTPHandler(master.ReleaseAddressHandler))\n+\ts.HandleFunc(\"/plugin/createEndpoint\", makeHTTPHandler(master.CreateEndpointHandler))\n+\ts.HandleFunc(\"/plugin/deleteEndpoint\", makeHTTPHandler(master.DeleteEndpointHandler))\n+\n+\ts = router.Methods(\"Get\").Subrouter()\n+\ts.HandleFunc(fmt.Sprintf(\"/%s/%s\", master.GetEndpointRESTEndpoint, \"{id}\"),\n+\t\tget(false, d.endpoints))\n+\ts.HandleFunc(fmt.Sprintf(\"/%s\", master.GetEndpointsRESTEndpoint),\n+\t\tget(true, d.endpoints))\n+\ts.HandleFunc(fmt.Sprintf(\"/%s/%s\", master.GetNetworkRESTEndpoint, \"{id}\"),\n+\t\tget(false, d.networks))\n+\ts.HandleFunc(fmt.Sprintf(\"/%s\", master.GetNetworksRESTEndpoint),\n+\t\tget(true, d.networks))\n+\ts.HandleFunc(fmt.Sprintf(\"/%s\", master.GetVersionRESTEndpoint), getVersion)\n+\n+\t// See if we need to create the default tenant\n+\tgo objApi.CreateDefaultTenant()\n+}\n+\n+// XXX: This function should be returning logical state instead of driver state\n+func (d *daemon) endpoints(id string) ([]core.State, error) {\n+\tvar (\n+\t\terr error\n+\t\tep  *drivers.OvsOperEndpointState\n+\t)\n+\n+\tep = &drivers.OvsOperEndpointState{}\n+\tif ep.StateDriver, err = utils.GetStateDriver(); err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tif id == \"all\" {\n+\t\teps, err := ep.ReadAll()\n+\t\tif err != nil {\n+\t\t\treturn []core.State{}, nil\n+\t\t}\n+\t\treturn eps, nil\n+\t}\n+\n+\terr = ep.Read(id)\n+\tif err == nil {\n+\t\treturn []core.State{core.State(ep)}, nil\n+\t}\n+\n+\treturn nil, core.Errorf(\"Unexpected code path. Recieved error during read: %v\", err)\n+}\n+\n+// XXX: This function should be returning logical state instead of driver state\n+func (d *daemon) networks(id string) ([]core.State, error) {\n+\tvar (\n+\t\terr error\n+\t\tnw  *mastercfg.CfgNetworkState\n+\t)\n+\n+\tnw = &mastercfg.CfgNetworkState{}\n+\tif nw.StateDriver, err = utils.GetStateDriver(); err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tif id == \"all\" {\n+\t\treturn nw.ReadAll()\n+\t} else if err := nw.Read(id); err == nil {\n+\t\treturn []core.State{core.State(nw)}, nil\n+\t}\n+\n+\treturn nil, core.Errorf(\"Unexpected code path\")\n+}\n+\n+// runLeader runs leader loop\n+func (d *daemon) runLeader() {\n+\trouter := mux.NewRouter()\n+\n+\t// acquire listener mutex\n+\td.listenerMutex.Lock()\n+\tdefer d.listenerMutex.Unlock()\n+\n+\t// Create a new api controller\n+\td.apiController = objApi.NewAPIController(router)\n+\n+\t// initialize policy manager\n+\tmastercfg.InitPolicyMgr(d.stateDriver, d.ofnetMaster)\n+\n+\t// Register netmaster service\n+\td.registerService()\n+\n+\t// setup HTTP routes\n+\td.registerRoutes(router)\n+\n+\t// Create HTTP server and listener\n+\tserver := &http.Server{Handler: router}\n+\tlistener, err := net.Listen(\"tcp\", d.listenURL)\n+\tif nil != err {\n+\t\tlog.Fatalln(err)\n+\t}\n+\n+\tlog.Infof(\"Netmaster listening on %s\", d.listenURL)\n+\n+\t// start server\n+\tgo server.Serve(listener)\n+\n+\t// Wait till we are asked to stop\n+\t<-d.stopLeaderChan\n+\n+\t// Close the listener and exit\n+\tlistener.Close()\n+\tlog.Infof(\"Exiting Leader mode\")\n+}\n+\n+// runFollower runs the follower FSM loop\n+func (d *daemon) runFollower() {\n+\trouter := mux.NewRouter()\n+\trouter.PathPrefix(\"/\").HandlerFunc(slaveProxyHandler)\n+\n+\t// acquire listener mutex\n+\td.listenerMutex.Lock()\n+\tdefer d.listenerMutex.Unlock()\n+\n+\t// start server\n+\tserver := &http.Server{Handler: router}\n+\tlistener, err := net.Listen(\"tcp\", d.listenURL)\n+\tif nil != err {\n+\t\tlog.Fatalln(err)\n+\t}\n+\n+\t// start server\n+\tgo server.Serve(listener)\n+\n+\t// Register netmaster service\n+\td.registerService()\n+\n+\t// just wait on stop channel\n+\tlog.Infof(\"Listening in follower mode\")\n+\t<-d.stopFollowerChan\n+\n+\t// Close the listener and exit\n+\tlistener.Close()\n+\tlog.Info(\"Exiting follower mode\")\n+}\n+\n+// becomeLeader changes daemon FSM state to master\n+func (d *daemon) becomeLeader() {\n+\t// ask listener to stop\n+\td.stopFollowerChan <- true\n+\n+\t// set current state\n+\td.currState = \"leader\"\n+\n+\t// Run the HTTP listener\n+\tgo d.runLeader()\n+}\n+\n+// becomeFollower changes FSM state to follower\n+func (d *daemon) becomeFollower() {\n+\t// ask listener to stop\n+\td.stopLeaderChan <- true\n+\n+\t// set current state\n+\td.currState = \"follower\"\n+\n+\t// run follower loop\n+\tgo d.runFollower()\n+}\n+\n+// runMasterFsm runs netmaster FSM\n+func (d *daemon) runMasterFsm() {\n+\t// create new ofnet master\n+\td.ofnetMaster = ofnet.NewOfnetMaster(ofnet.OFNET_MASTER_PORT)\n+\tif d.ofnetMaster == nil {\n+\t\tlog.Fatalf(\"Error creating ofnet master\")\n+\t}\n+\n+\t// Create an objdb client\n+\td.objdbClient = objdb.NewClient(\"\")\n+\n+\t// Get the address to be used for local communication\n+\tlocalIP, err := d.objdbClient.GetLocalAddr()","path":"netmaster/daemon.go","position":295,"original_position":295,"commit_id":"7ae9c3297e50b41282a9ba3a37739ccf3c9d1ac9","original_commit_id":"7ae9c3297e50b41282a9ba3a37739ccf3c9d1ac9","user":{"login":"jojimt","id":1606547,"avatar_url":"https://avatars.githubusercontent.com/u/1606547?v=3","gravatar_id":"","url":"https://api.github.com/users/jojimt","html_url":"https://github.com/jojimt","followers_url":"https://api.github.com/users/jojimt/followers","following_url":"https://api.github.com/users/jojimt/following{/other_user}","gists_url":"https://api.github.com/users/jojimt/gists{/gist_id}","starred_url":"https://api.github.com/users/jojimt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jojimt/subscriptions","organizations_url":"https://api.github.com/users/jojimt/orgs","repos_url":"https://api.github.com/users/jojimt/repos","events_url":"https://api.github.com/users/jojimt/events{/privacy}","received_events_url":"https://api.github.com/users/jojimt/received_events","type":"User","site_admin":false},"body":"This means that the netmasters can run only on the nodes where etcd run in non-proxy mode. If we want to avoid this restriction, we should take a control-ip command line arg like netplugin.","created_at":"2016-02-09T04:45:42Z","updated_at":"2016-02-09T04:45:42Z","html_url":"https://github.com/contiv/netplugin/pull/273#discussion_r52267346","pull_request_url":"https://api.github.com/repos/contiv/netplugin/pulls/273","_links":{"self":{"href":"https://api.github.com/repos/contiv/netplugin/pulls/comments/52267346"},"html":{"href":"https://github.com/contiv/netplugin/pull/273#discussion_r52267346"},"pull_request":{"href":"https://api.github.com/repos/contiv/netplugin/pulls/273"}}},"pull_request":{"url":"https://api.github.com/repos/contiv/netplugin/pulls/273","id":58681217,"html_url":"https://github.com/contiv/netplugin/pull/273","diff_url":"https://github.com/contiv/netplugin/pull/273.diff","patch_url":"https://github.com/contiv/netplugin/pull/273.patch","issue_url":"https://api.github.com/repos/contiv/netplugin/issues/273","number":273,"state":"open","locked":false,"title":"Netmaster HA changes","user":{"login":"shaleman","id":5446536,"avatar_url":"https://avatars.githubusercontent.com/u/5446536?v=3","gravatar_id":"","url":"https://api.github.com/users/shaleman","html_url":"https://github.com/shaleman","followers_url":"https://api.github.com/users/shaleman/followers","following_url":"https://api.github.com/users/shaleman/following{/other_user}","gists_url":"https://api.github.com/users/shaleman/gists{/gist_id}","starred_url":"https://api.github.com/users/shaleman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shaleman/subscriptions","organizations_url":"https://api.github.com/users/shaleman/orgs","repos_url":"https://api.github.com/users/shaleman/repos","events_url":"https://api.github.com/users/shaleman/events{/privacy}","received_events_url":"https://api.github.com/users/shaleman/received_events","type":"User","site_admin":false},"body":"This adds netmaster hot standby support.\r\n- Netmaster tries to take an etcd lock when it starts up. The one acquiring the lock is the leader and others are followers. \r\n- There can be any number of netmasters in the cluster. When current leader fails, one of the followers acquire the lock and become new leader.\r\n- Leader is responsible for making all state changes. Followers simply proxy all REST calls to current leader.\r\n- Netplugins detect the current leader using etcd and send all REST calls to current leader.\r\n- External clients(like netctl or web ui) can use either virtual-IP or loadbalancer to access netmaster's REST API.\r\n- All netmasters participate in ofnet route distribution in active-active manner.\r\n\r\nSumary of changes:\r\n- Restructure netmaster main package to introduce a master FSM(This includes leader lock mgmt).\r\n- Change netplugin to send REST requests to current leader..\r\n- Minor bug fixes in policy/rule handling.\r\n- Add test cases to test netmaster HA and switchover.\r\n- Add missing test cases on some of the policy features like match on IP address, match on network, ICMP etc..","created_at":"2016-02-08T23:04:10Z","updated_at":"2016-02-09T04:45:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2ab50b45f22c1852d362feefc14d4cf8028cead6","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/contiv/netplugin/pulls/273/commits","review_comments_url":"https://api.github.com/repos/contiv/netplugin/pulls/273/comments","review_comment_url":"https://api.github.com/repos/contiv/netplugin/pulls/comments{/number}","comments_url":"https://api.github.com/repos/contiv/netplugin/issues/273/comments","statuses_url":"https://api.github.com/repos/contiv/netplugin/statuses/7ae9c3297e50b41282a9ba3a37739ccf3c9d1ac9","head":{"label":"shaleman:standby","ref":"standby","sha":"7ae9c3297e50b41282a9ba3a37739ccf3c9d1ac9","user":{"login":"shaleman","id":5446536,"avatar_url":"https://avatars.githubusercontent.com/u/5446536?v=3","gravatar_id":"","url":"https://api.github.com/users/shaleman","html_url":"https://github.com/shaleman","followers_url":"https://api.github.com/users/shaleman/followers","following_url":"https://api.github.com/users/shaleman/following{/other_user}","gists_url":"https://api.github.com/users/shaleman/gists{/gist_id}","starred_url":"https://api.github.com/users/shaleman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shaleman/subscriptions","organizations_url":"https://api.github.com/users/shaleman/orgs","repos_url":"https://api.github.com/users/shaleman/repos","events_url":"https://api.github.com/users/shaleman/events{/privacy}","received_events_url":"https://api.github.com/users/shaleman/received_events","type":"User","site_admin":false},"repo":{"id":50529302,"name":"netplugin","full_name":"shaleman/netplugin","owner":{"login":"shaleman","id":5446536,"avatar_url":"https://avatars.githubusercontent.com/u/5446536?v=3","gravatar_id":"","url":"https://api.github.com/users/shaleman","html_url":"https://github.com/shaleman","followers_url":"https://api.github.com/users/shaleman/followers","following_url":"https://api.github.com/users/shaleman/following{/other_user}","gists_url":"https://api.github.com/users/shaleman/gists{/gist_id}","starred_url":"https://api.github.com/users/shaleman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shaleman/subscriptions","organizations_url":"https://api.github.com/users/shaleman/orgs","repos_url":"https://api.github.com/users/shaleman/repos","events_url":"https://api.github.com/users/shaleman/events{/privacy}","received_events_url":"https://api.github.com/users/shaleman/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/shaleman/netplugin","description":"Container networking for various use cases","fork":true,"url":"https://api.github.com/repos/shaleman/netplugin","forks_url":"https://api.github.com/repos/shaleman/netplugin/forks","keys_url":"https://api.github.com/repos/shaleman/netplugin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/shaleman/netplugin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/shaleman/netplugin/teams","hooks_url":"https://api.github.com/repos/shaleman/netplugin/hooks","issue_events_url":"https://api.github.com/repos/shaleman/netplugin/issues/events{/number}","events_url":"https://api.github.com/repos/shaleman/netplugin/events","assignees_url":"https://api.github.com/repos/shaleman/netplugin/assignees{/user}","branches_url":"https://api.github.com/repos/shaleman/netplugin/branches{/branch}","tags_url":"https://api.github.com/repos/shaleman/netplugin/tags","blobs_url":"https://api.github.com/repos/shaleman/netplugin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/shaleman/netplugin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/shaleman/netplugin/git/refs{/sha}","trees_url":"https://api.github.com/repos/shaleman/netplugin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/shaleman/netplugin/statuses/{sha}","languages_url":"https://api.github.com/repos/shaleman/netplugin/languages","stargazers_url":"https://api.github.com/repos/shaleman/netplugin/stargazers","contributors_url":"https://api.github.com/repos/shaleman/netplugin/contributors","subscribers_url":"https://api.github.com/repos/shaleman/netplugin/subscribers","subscription_url":"https://api.github.com/repos/shaleman/netplugin/subscription","commits_url":"https://api.github.com/repos/shaleman/netplugin/commits{/sha}","git_commits_url":"https://api.github.com/repos/shaleman/netplugin/git/commits{/sha}","comments_url":"https://api.github.com/repos/shaleman/netplugin/comments{/number}","issue_comment_url":"https://api.github.com/repos/shaleman/netplugin/issues/comments{/number}","contents_url":"https://api.github.com/repos/shaleman/netplugin/contents/{+path}","compare_url":"https://api.github.com/repos/shaleman/netplugin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/shaleman/netplugin/merges","archive_url":"https://api.github.com/repos/shaleman/netplugin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/shaleman/netplugin/downloads","issues_url":"https://api.github.com/repos/shaleman/netplugin/issues{/number}","pulls_url":"https://api.github.com/repos/shaleman/netplugin/pulls{/number}","milestones_url":"https://api.github.com/repos/shaleman/netplugin/milestones{/number}","notifications_url":"https://api.github.com/repos/shaleman/netplugin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/shaleman/netplugin/labels{/name}","releases_url":"https://api.github.com/repos/shaleman/netplugin/releases{/id}","deployments_url":"https://api.github.com/repos/shaleman/netplugin/deployments","created_at":"2016-01-27T18:41:57Z","updated_at":"2016-01-27T18:41:58Z","pushed_at":"2016-02-08T23:38:16Z","git_url":"git://github.com/shaleman/netplugin.git","ssh_url":"git@github.com:shaleman/netplugin.git","clone_url":"https://github.com/shaleman/netplugin.git","svn_url":"https://github.com/shaleman/netplugin","homepage":"","size":14156,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"contiv:master","ref":"master","sha":"8b02e0fb6097f826bf53ca6e40fbf7423d46c9e1","user":{"login":"contiv","id":10272443,"avatar_url":"https://avatars.githubusercontent.com/u/10272443?v=3","gravatar_id":"","url":"https://api.github.com/users/contiv","html_url":"https://github.com/contiv","followers_url":"https://api.github.com/users/contiv/followers","following_url":"https://api.github.com/users/contiv/following{/other_user}","gists_url":"https://api.github.com/users/contiv/gists{/gist_id}","starred_url":"https://api.github.com/users/contiv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/contiv/subscriptions","organizations_url":"https://api.github.com/users/contiv/orgs","repos_url":"https://api.github.com/users/contiv/repos","events_url":"https://api.github.com/users/contiv/events{/privacy}","received_events_url":"https://api.github.com/users/contiv/received_events","type":"Organization","site_admin":false},"repo":{"id":28353322,"name":"netplugin","full_name":"contiv/netplugin","owner":{"login":"contiv","id":10272443,"avatar_url":"https://avatars.githubusercontent.com/u/10272443?v=3","gravatar_id":"","url":"https://api.github.com/users/contiv","html_url":"https://github.com/contiv","followers_url":"https://api.github.com/users/contiv/followers","following_url":"https://api.github.com/users/contiv/following{/other_user}","gists_url":"https://api.github.com/users/contiv/gists{/gist_id}","starred_url":"https://api.github.com/users/contiv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/contiv/subscriptions","organizations_url":"https://api.github.com/users/contiv/orgs","repos_url":"https://api.github.com/users/contiv/repos","events_url":"https://api.github.com/users/contiv/events{/privacy}","received_events_url":"https://api.github.com/users/contiv/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/contiv/netplugin","description":"Container networking for various use cases","fork":false,"url":"https://api.github.com/repos/contiv/netplugin","forks_url":"https://api.github.com/repos/contiv/netplugin/forks","keys_url":"https://api.github.com/repos/contiv/netplugin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/contiv/netplugin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/contiv/netplugin/teams","hooks_url":"https://api.github.com/repos/contiv/netplugin/hooks","issue_events_url":"https://api.github.com/repos/contiv/netplugin/issues/events{/number}","events_url":"https://api.github.com/repos/contiv/netplugin/events","assignees_url":"https://api.github.com/repos/contiv/netplugin/assignees{/user}","branches_url":"https://api.github.com/repos/contiv/netplugin/branches{/branch}","tags_url":"https://api.github.com/repos/contiv/netplugin/tags","blobs_url":"https://api.github.com/repos/contiv/netplugin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/contiv/netplugin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/contiv/netplugin/git/refs{/sha}","trees_url":"https://api.github.com/repos/contiv/netplugin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/contiv/netplugin/statuses/{sha}","languages_url":"https://api.github.com/repos/contiv/netplugin/languages","stargazers_url":"https://api.github.com/repos/contiv/netplugin/stargazers","contributors_url":"https://api.github.com/repos/contiv/netplugin/contributors","subscribers_url":"https://api.github.com/repos/contiv/netplugin/subscribers","subscription_url":"https://api.github.com/repos/contiv/netplugin/subscription","commits_url":"https://api.github.com/repos/contiv/netplugin/commits{/sha}","git_commits_url":"https://api.github.com/repos/contiv/netplugin/git/commits{/sha}","comments_url":"https://api.github.com/repos/contiv/netplugin/comments{/number}","issue_comment_url":"https://api.github.com/repos/contiv/netplugin/issues/comments{/number}","contents_url":"https://api.github.com/repos/contiv/netplugin/contents/{+path}","compare_url":"https://api.github.com/repos/contiv/netplugin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/contiv/netplugin/merges","archive_url":"https://api.github.com/repos/contiv/netplugin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/contiv/netplugin/downloads","issues_url":"https://api.github.com/repos/contiv/netplugin/issues{/number}","pulls_url":"https://api.github.com/repos/contiv/netplugin/pulls{/number}","milestones_url":"https://api.github.com/repos/contiv/netplugin/milestones{/number}","notifications_url":"https://api.github.com/repos/contiv/netplugin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/contiv/netplugin/labels{/name}","releases_url":"https://api.github.com/repos/contiv/netplugin/releases{/id}","deployments_url":"https://api.github.com/repos/contiv/netplugin/deployments","created_at":"2014-12-22T18:45:21Z","updated_at":"2016-02-06T03:22:55Z","pushed_at":"2016-02-08T23:38:16Z","git_url":"git://github.com/contiv/netplugin.git","ssh_url":"git@github.com:contiv/netplugin.git","clone_url":"https://github.com/contiv/netplugin.git","svn_url":"https://github.com/contiv/netplugin","homepage":"","size":14291,"stargazers_count":73,"watchers_count":73,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":34,"mirror_url":null,"open_issues_count":48,"forks":34,"open_issues":48,"watchers":73,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/contiv/netplugin/pulls/273"},"html":{"href":"https://github.com/contiv/netplugin/pull/273"},"issue":{"href":"https://api.github.com/repos/contiv/netplugin/issues/273"},"comments":{"href":"https://api.github.com/repos/contiv/netplugin/issues/273/comments"},"review_comments":{"href":"https://api.github.com/repos/contiv/netplugin/pulls/273/comments"},"review_comment":{"href":"https://api.github.com/repos/contiv/netplugin/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/contiv/netplugin/pulls/273/commits"},"statuses":{"href":"https://api.github.com/repos/contiv/netplugin/statuses/7ae9c3297e50b41282a9ba3a37739ccf3c9d1ac9"}}}}