forked from hswaw/hscloud
initial commit
This commit is contained in:
commit
753d63f893
7 changed files with 874 additions and 0 deletions
189
Gopkg.lock
generated
Normal file
189
Gopkg.lock
generated
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:2af0bc306f77bef606f6f6033652f0de3cc80e77008f4da52197b4b0a6be5853"
|
||||||
|
name = "code.hackerspace.pl/q3k/hspki"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "624295da664fea57a1e3bd8cee639ae04f4c0ccf"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:b2619fc3d4ced0542d16962c73812d2e70ed09eb29cdfc3d767a85b726dab150"
|
||||||
|
name = "code.hackerspace.pl/q3k/mirko"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "4f7ee51e9a73e05e5b2dbc6cfa178ace2f83b155"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:e92f5581902c345eb4ceffdcd4a854fb8f73cf436d47d837d1ec98ef1fe0a214"
|
||||||
|
name = "github.com/StackExchange/wmi"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:64a5a67c69b70c2420e607a8545d674a23778ed9c3e80607bfd17b77c6c87f6a"
|
||||||
|
name = "github.com/go-ole/go-ole"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"oleutil",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "a41e3c4b706f6ae8dfbff342b06e40fa4d2d0506"
|
||||||
|
version = "v1.2.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467"
|
||||||
|
name = "github.com/golang/glog"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:5d1b5a25486fc7d4e133646d834f6fca7ba1cef9903d40e7aa786c41b89e9e91"
|
||||||
|
name = "github.com/golang/protobuf"
|
||||||
|
packages = [
|
||||||
|
"proto",
|
||||||
|
"protoc-gen-go/descriptor",
|
||||||
|
"ptypes",
|
||||||
|
"ptypes/any",
|
||||||
|
"ptypes/duration",
|
||||||
|
"ptypes/timestamp",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
|
||||||
|
version = "v1.2.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:a7cc4447e0c2432f217fd2bed937c100ec475272a7a9306477170c2934297357"
|
||||||
|
name = "github.com/q3k/statusz"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "924f04ea71149b75f5b10f426cc4c39d6d90f6f2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:8f042d4b4c48d38d7a1201096299bd26d044c97c032ab20ee171fe5b0d201d2e"
|
||||||
|
name = "github.com/shirou/gopsutil"
|
||||||
|
packages = [
|
||||||
|
"internal/common",
|
||||||
|
"load",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "a11c78ba2c13c5b1ee59c53296ba35f92f0ce658"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:505dbee0833715a72a529bb57c354826ad42a4496fad787fa143699b4de1a6d0"
|
||||||
|
name = "golang.org/x/net"
|
||||||
|
packages = [
|
||||||
|
"context",
|
||||||
|
"http/httpguts",
|
||||||
|
"http2",
|
||||||
|
"http2/hpack",
|
||||||
|
"idna",
|
||||||
|
"internal/timeseries",
|
||||||
|
"trace",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:f5aa274a0377f85735edc7fedfb0811d3cbc20af91633797cb359e29c3272271"
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
packages = [
|
||||||
|
"unix",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "fa43e7bc11baaae89f3f902b2b4d832b68234844"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||||
|
name = "golang.org/x/text"
|
||||||
|
packages = [
|
||||||
|
"collate",
|
||||||
|
"collate/build",
|
||||||
|
"internal/colltab",
|
||||||
|
"internal/gen",
|
||||||
|
"internal/tag",
|
||||||
|
"internal/triegen",
|
||||||
|
"internal/ucd",
|
||||||
|
"language",
|
||||||
|
"secure/bidirule",
|
||||||
|
"transform",
|
||||||
|
"unicode/bidi",
|
||||||
|
"unicode/cldr",
|
||||||
|
"unicode/norm",
|
||||||
|
"unicode/rangetable",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||||
|
version = "v0.3.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a"
|
||||||
|
name = "google.golang.org/genproto"
|
||||||
|
packages = ["googleapis/rpc/status"]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "af9cb2a35e7f169ec875002c1829c9b315cddc04"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:dc0c170b110c22d9a4eccf08ab58490608053eac450bf456f3aaf9b30a668781"
|
||||||
|
name = "google.golang.org/grpc"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"balancer",
|
||||||
|
"balancer/base",
|
||||||
|
"balancer/roundrobin",
|
||||||
|
"codes",
|
||||||
|
"connectivity",
|
||||||
|
"credentials",
|
||||||
|
"encoding",
|
||||||
|
"encoding/proto",
|
||||||
|
"grpclog",
|
||||||
|
"internal",
|
||||||
|
"internal/backoff",
|
||||||
|
"internal/channelz",
|
||||||
|
"internal/envconfig",
|
||||||
|
"internal/grpcrand",
|
||||||
|
"internal/transport",
|
||||||
|
"keepalive",
|
||||||
|
"metadata",
|
||||||
|
"naming",
|
||||||
|
"peer",
|
||||||
|
"reflection",
|
||||||
|
"reflection/grpc_reflection_v1alpha",
|
||||||
|
"resolver",
|
||||||
|
"resolver/dns",
|
||||||
|
"resolver/passthrough",
|
||||||
|
"stats",
|
||||||
|
"status",
|
||||||
|
"tap",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1"
|
||||||
|
version = "v1.15.0"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
input-imports = [
|
||||||
|
"code.hackerspace.pl/q3k/mirko",
|
||||||
|
"github.com/golang/glog",
|
||||||
|
"github.com/golang/protobuf/proto",
|
||||||
|
"golang.org/x/net/context",
|
||||||
|
"google.golang.org/grpc",
|
||||||
|
"google.golang.org/grpc/codes",
|
||||||
|
"google.golang.org/grpc/status",
|
||||||
|
]
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
50
Gopkg.toml
Normal file
50
Gopkg.toml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
#
|
||||||
|
# [prune]
|
||||||
|
# non-go = false
|
||||||
|
# go-tests = true
|
||||||
|
# unused-packages = true
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "code.hackerspace.pl/q3k/mirko"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/golang/glog"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/golang/protobuf"
|
||||||
|
version = "1.2.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/net"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "google.golang.org/grpc"
|
||||||
|
version = "1.15.0"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
348
client.go
Normal file
348
client.go
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.hackerspace.pl/q3k/mirko"
|
||||||
|
"github.com/cenkalti/backoff"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
reSessionCookie = regexp.MustCompile("'SESSION_COOKIE' : '([^']*)'")
|
||||||
|
reIpmiPriv = regexp.MustCompile("'IPMI_PRIV' : ([^,]*)")
|
||||||
|
reExtPriv = regexp.MustCompile("'EXT_PRIV' : ([^,]*)")
|
||||||
|
reSystemModel = regexp.MustCompile("'SYSTEM_MODEL' : '([^']*)'")
|
||||||
|
reArgument = regexp.MustCompile("<argument>([^<]*)</argument>")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorNoFreeSlot = fmt.Errorf("iDRAC reports no free slot")
|
||||||
|
)
|
||||||
|
|
||||||
|
type cmcRequestType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmcRequestKVMDetails cmcRequestType = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
type cmcResponse struct {
|
||||||
|
data interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmcRequest struct {
|
||||||
|
t cmcRequestType
|
||||||
|
req interface{}
|
||||||
|
res chan cmcResponse
|
||||||
|
canceled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type KVMDetails struct {
|
||||||
|
arguments []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmcClient struct {
|
||||||
|
session string
|
||||||
|
req chan *cmcRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) RequestKVMDetails(ctx context.Context, slot int) (*KVMDetails, error) {
|
||||||
|
r := &cmcRequest{
|
||||||
|
t: cmcRequestKVMDetails,
|
||||||
|
req: slot,
|
||||||
|
res: make(chan cmcResponse, 1),
|
||||||
|
}
|
||||||
|
mirko.Trace(ctx, "cmcRequestKVMDetails: requesting...")
|
||||||
|
c.req <- r
|
||||||
|
mirko.Trace(ctx, "cmcRequestKVMDetails: requested.")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
r.canceled = true
|
||||||
|
return nil, context.Canceled
|
||||||
|
case res := <-r.res:
|
||||||
|
mirko.Trace(ctx, "cmcRequestKVMDetails: got response")
|
||||||
|
if res.err != nil {
|
||||||
|
return nil, res.err
|
||||||
|
}
|
||||||
|
return res.data.(*KVMDetails), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCMCClient() *cmcClient {
|
||||||
|
return &cmcClient{
|
||||||
|
req: make(chan *cmcRequest, 4),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) Run(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
c.logout()
|
||||||
|
return
|
||||||
|
case msg := <-c.req:
|
||||||
|
c.handle(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) handle(r *cmcRequest) {
|
||||||
|
switch {
|
||||||
|
case r.t == cmcRequestKVMDetails:
|
||||||
|
var details *KVMDetails
|
||||||
|
slot := r.req.(int)
|
||||||
|
err := backoff.Retry(func() error {
|
||||||
|
if err := c.login(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url, err := c.getiDRACURL(slot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
details, err = c.getiDRACJNLP(url)
|
||||||
|
return err
|
||||||
|
}, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 2))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
r.res <- cmcResponse{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.res <- cmcResponse{data: details}
|
||||||
|
default:
|
||||||
|
panic("invalid cmcRequestType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUrl(path string) string {
|
||||||
|
if strings.HasSuffix(flagCMCAddress, "/") {
|
||||||
|
return flagCMCAddress + path
|
||||||
|
}
|
||||||
|
return flagCMCAddress + "/" + path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) transport() *http.Transport {
|
||||||
|
return &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) addCookies(req *http.Request) {
|
||||||
|
req.AddCookie(&http.Cookie{Name: "custom_domain", Value: ""})
|
||||||
|
req.AddCookie(&http.Cookie{Name: "domain_selected", Value: "This Chassis"})
|
||||||
|
if c.session != "" {
|
||||||
|
glog.Infof("Adding session: %v", c.session)
|
||||||
|
req.AddCookie(&http.Cookie{Name: "sid", Value: c.session})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) getiDRACURL(slot int) (string, error) {
|
||||||
|
if c.session == "" {
|
||||||
|
return "", fmt.Errorf("not logged in")
|
||||||
|
}
|
||||||
|
|
||||||
|
url := makeUrl(pathiDRACURL) + fmt.Sprintf("?vKVM=1&serverSlot=%d", slot)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("GET prepare to %s failed: %v", pathLogin, err)
|
||||||
|
}
|
||||||
|
c.addCookies(req)
|
||||||
|
|
||||||
|
cl := &http.Client{
|
||||||
|
Transport: c.transport(),
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := cl.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("GET to %s failed: %v", pathLogin, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 302 {
|
||||||
|
return "", fmt.Errorf("expected 302 on iDRAC URL redirect, got %v instead", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
loc, _ := resp.Location()
|
||||||
|
|
||||||
|
if !strings.Contains(loc.String(), "cmc_sess_id") {
|
||||||
|
c.session = ""
|
||||||
|
return "", fmt.Errorf("redirect URL contains no session ID - session timed out?")
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) getiDRACJNLP(loginUrl string) (*KVMDetails, error) {
|
||||||
|
lurl, err := url.Parse(loginUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sessid := lurl.Query().Get("cmc_sess_id")
|
||||||
|
if sessid == "" {
|
||||||
|
return nil, fmt.Errorf("no cmc_sess_id in iDRAC login URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
createURL := *lurl
|
||||||
|
createURL.Path = "/Applications/dellUI/RPC/WEBSES/create.asp"
|
||||||
|
createURL.RawQuery = ""
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("WEBVAR_USERNAME", "cmc")
|
||||||
|
values.Set("WEBVAR_PASSWORD", sessid)
|
||||||
|
values.Set("WEBVAR_ISCMCLOGIN", "1")
|
||||||
|
valuesString := values.Encode()
|
||||||
|
req, err := http.NewRequest("POST", createURL.String(), strings.NewReader(valuesString))
|
||||||
|
|
||||||
|
cl := &http.Client{
|
||||||
|
Transport: c.transport(),
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := cl.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
first := func(v [][]byte) string {
|
||||||
|
if len(v) < 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(v[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCookie := first(reSessionCookie.FindSubmatch(data))
|
||||||
|
ipmiPriv := first(reIpmiPriv.FindSubmatch(data))
|
||||||
|
extPriv := first(reExtPriv.FindSubmatch(data))
|
||||||
|
systemModel := first(reSystemModel.FindSubmatch(data))
|
||||||
|
|
||||||
|
if sessionCookie == "Failure_No_Free_Slot" {
|
||||||
|
return nil, ErrorNoFreeSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
jnlpURL := *lurl
|
||||||
|
jnlpURL.Path = "/Applications/dellUI/Java/jviewer.jnlp"
|
||||||
|
jnlpURL.RawQuery = ""
|
||||||
|
|
||||||
|
req, err = http.NewRequest("GET", jnlpURL.String(), nil)
|
||||||
|
for _, cookie := range resp.Cookies() {
|
||||||
|
glog.Infof("%+v", cookie)
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
req.AddCookie(&http.Cookie{Name: "SessionCookie", Value: sessionCookie})
|
||||||
|
req.AddCookie(&http.Cookie{Name: "SessionCookieUser", Value: "cmc"})
|
||||||
|
req.AddCookie(&http.Cookie{Name: "IPMIPriv", Value: ipmiPriv})
|
||||||
|
req.AddCookie(&http.Cookie{Name: "ExtPriv", Value: extPriv})
|
||||||
|
req.AddCookie(&http.Cookie{Name: "SystemModel", Value: systemModel})
|
||||||
|
|
||||||
|
resp, err = cl.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err = ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// yes we do parse xml with regex why are you asking
|
||||||
|
matches := reArgument.FindAllSubmatch(data, -1)
|
||||||
|
|
||||||
|
res := &KVMDetails{
|
||||||
|
arguments: []string{},
|
||||||
|
}
|
||||||
|
for _, match := range matches {
|
||||||
|
res.arguments = append(res.arguments, string(match[1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) login() error {
|
||||||
|
if c.session != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("ST2", "NOTSET")
|
||||||
|
values.Set("user", flagCMCUsername)
|
||||||
|
values.Set("user_id", flagCMCUsername)
|
||||||
|
values.Set("password", flagCMCPassword)
|
||||||
|
values.Set("WEBSERVER_timeout", "1800")
|
||||||
|
values.Set("WEBSERVER_timeout_select", "1800")
|
||||||
|
valuesString := values.Encode()
|
||||||
|
glog.Info(valuesString)
|
||||||
|
req, err := http.NewRequest("POST", makeUrl(pathLogin), strings.NewReader(valuesString))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("POST prepare to %s failed: %v", pathLogin, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
c.addCookies(req)
|
||||||
|
|
||||||
|
cl := &http.Client{
|
||||||
|
Transport: c.transport(),
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := cl.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("POST to %s failed: %v", pathLogin, err)
|
||||||
|
}
|
||||||
|
glog.Infof("Login response: %s", resp.Status)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
for _, cookie := range resp.Cookies() {
|
||||||
|
if cookie.Name == "sid" {
|
||||||
|
c.session = cookie.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.session == "" {
|
||||||
|
return fmt.Errorf("login unsuccesful")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmcClient) logout() {
|
||||||
|
glog.Infof("Killing session..")
|
||||||
|
if c.session == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", makeUrl(pathLogout), nil)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("GET prepare to %s failed: %v", pathLogin, err)
|
||||||
|
}
|
||||||
|
c.addCookies(req)
|
||||||
|
|
||||||
|
cl := &http.Client{
|
||||||
|
Transport: c.transport(),
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := cl.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("GET to %s failed: %v", pathLogin, err)
|
||||||
|
}
|
||||||
|
glog.Infof("Logout response: %s", resp.Status)
|
||||||
|
return
|
||||||
|
}
|
75
main.go
Normal file
75
main.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"code.hackerspace.pl/q3k/mirko"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
pb "code.hackerspace.pl/q3k/cmc-proxy/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagCMCAddress string
|
||||||
|
flagCMCUsername string
|
||||||
|
flagCMCPassword string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pathLogin = "cgi-bin/webcgi/login"
|
||||||
|
pathLogout = "cgi-bin/webcgi/logout"
|
||||||
|
pathiDRACURL = "cgi-bin/webcgi/blade_iDRAC_url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Set("logtostderr", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
cmc *cmcClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetKVMData(ctx context.Context, req *pb.GetKVMDataRequest) (*pb.GetKVMDataResponse, error) {
|
||||||
|
if req.BladeNum < 1 || req.BladeNum > 16 {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "blade_num must be [1,16]")
|
||||||
|
}
|
||||||
|
|
||||||
|
details, err := s.cmc.RequestKVMDetails(ctx, int(req.BladeNum))
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("RequestKVMDetails(_, %d): %v", req.BladeNum, err)
|
||||||
|
return nil, status.Error(codes.Unavailable, "CMC unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.GetKVMDataResponse{
|
||||||
|
Arguments: details.arguments,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&flagCMCAddress, "cmc_address", "https://10.10.10.10", "URL of Dell M1000e CMC")
|
||||||
|
flag.StringVar(&flagCMCUsername, "cmc_username", "root", "Login username for CMC")
|
||||||
|
flag.StringVar(&flagCMCPassword, "cmc_password", "", "Login password for CMC")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
m := mirko.New()
|
||||||
|
if err := m.Listen(); err != nil {
|
||||||
|
glog.Exitf("Could not listen: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &service{
|
||||||
|
cmc: NewCMCClient(),
|
||||||
|
}
|
||||||
|
pb.RegisterCMCProxyServer(m.GRPC(), s)
|
||||||
|
|
||||||
|
if err := m.Serve(); err != nil {
|
||||||
|
glog.Exitf("Could not run: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.cmc.Run(m.Context())
|
||||||
|
glog.Info("Running.")
|
||||||
|
|
||||||
|
<-m.Done()
|
||||||
|
}
|
3
proto/generate.go
Normal file
3
proto/generate.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
//go:generate protoc -I.. ../proxy.proto --go_out=plugins=grpc:.
|
||||||
|
|
||||||
|
package proto
|
194
proto/proxy.pb.go
Normal file
194
proto/proxy.pb.go
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// source: proxy.proto
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
math "math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type GetKVMDataRequest struct {
|
||||||
|
BladeNum int64 `protobuf:"varint,1,opt,name=blade_num,json=bladeNum,proto3" json:"blade_num,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetKVMDataRequest) Reset() { *m = GetKVMDataRequest{} }
|
||||||
|
func (m *GetKVMDataRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetKVMDataRequest) ProtoMessage() {}
|
||||||
|
func (*GetKVMDataRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_700b50b08ed8dbaf, []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetKVMDataRequest) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_GetKVMDataRequest.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_GetKVMDataRequest.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataRequest) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_GetKVMDataRequest.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataRequest) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_GetKVMDataRequest.Size(m)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataRequest) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_GetKVMDataRequest.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_GetKVMDataRequest proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *GetKVMDataRequest) GetBladeNum() int64 {
|
||||||
|
if m != nil {
|
||||||
|
return m.BladeNum
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetKVMDataResponse struct {
|
||||||
|
Arguments []string `protobuf:"bytes,1,rep,name=arguments,proto3" json:"arguments,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetKVMDataResponse) Reset() { *m = GetKVMDataResponse{} }
|
||||||
|
func (m *GetKVMDataResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetKVMDataResponse) ProtoMessage() {}
|
||||||
|
func (*GetKVMDataResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_700b50b08ed8dbaf, []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetKVMDataResponse) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_GetKVMDataResponse.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_GetKVMDataResponse.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataResponse) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_GetKVMDataResponse.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataResponse) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_GetKVMDataResponse.Size(m)
|
||||||
|
}
|
||||||
|
func (m *GetKVMDataResponse) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_GetKVMDataResponse.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_GetKVMDataResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *GetKVMDataResponse) GetArguments() []string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Arguments
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*GetKVMDataRequest)(nil), "proto.GetKVMDataRequest")
|
||||||
|
proto.RegisterType((*GetKVMDataResponse)(nil), "proto.GetKVMDataResponse")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("proxy.proto", fileDescriptor_700b50b08ed8dbaf) }
|
||||||
|
|
||||||
|
var fileDescriptor_700b50b08ed8dbaf = []byte{
|
||||||
|
// 156 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x28, 0xca, 0xaf,
|
||||||
|
0xa8, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x06, 0x5c, 0x82, 0xee,
|
||||||
|
0xa9, 0x25, 0xde, 0x61, 0xbe, 0x2e, 0x89, 0x25, 0x89, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25,
|
||||||
|
0x42, 0xd2, 0x5c, 0x9c, 0x49, 0x39, 0x89, 0x29, 0xa9, 0xf1, 0x79, 0xa5, 0xb9, 0x12, 0x8c, 0x0a,
|
||||||
|
0x8c, 0x1a, 0xcc, 0x41, 0x1c, 0x60, 0x01, 0xbf, 0xd2, 0x5c, 0x25, 0x23, 0x2e, 0x21, 0x64, 0x1d,
|
||||||
|
0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x32, 0x5c, 0x9c, 0x89, 0x45, 0xe9, 0xa5, 0xb9, 0xa9,
|
||||||
|
0x79, 0x25, 0xc5, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0x9c, 0x41, 0x08, 0x01, 0x23, 0x5f, 0x2e, 0x0e,
|
||||||
|
0x67, 0x5f, 0xe7, 0x00, 0x90, 0xf5, 0x42, 0x8e, 0x5c, 0x5c, 0x08, 0xfd, 0x42, 0x12, 0x10, 0xe7,
|
||||||
|
0xe8, 0x61, 0x38, 0x42, 0x4a, 0x12, 0x8b, 0x0c, 0xc4, 0xb2, 0x24, 0x36, 0xb0, 0x8c, 0x31, 0x20,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x09, 0x00, 0x2b, 0x54, 0xd1, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ context.Context
|
||||||
|
var _ grpc.ClientConn
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
const _ = grpc.SupportPackageIsVersion4
|
||||||
|
|
||||||
|
// CMCProxyClient is the client API for CMCProxy service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||||
|
type CMCProxyClient interface {
|
||||||
|
GetKVMData(ctx context.Context, in *GetKVMDataRequest, opts ...grpc.CallOption) (*GetKVMDataResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type cMCProxyClient struct {
|
||||||
|
cc *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCMCProxyClient(cc *grpc.ClientConn) CMCProxyClient {
|
||||||
|
return &cMCProxyClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cMCProxyClient) GetKVMData(ctx context.Context, in *GetKVMDataRequest, opts ...grpc.CallOption) (*GetKVMDataResponse, error) {
|
||||||
|
out := new(GetKVMDataResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/proto.CMCProxy/GetKVMData", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMCProxyServer is the server API for CMCProxy service.
|
||||||
|
type CMCProxyServer interface {
|
||||||
|
GetKVMData(context.Context, *GetKVMDataRequest) (*GetKVMDataResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterCMCProxyServer(s *grpc.Server, srv CMCProxyServer) {
|
||||||
|
s.RegisterService(&_CMCProxy_serviceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CMCProxy_GetKVMData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetKVMDataRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CMCProxyServer).GetKVMData(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/proto.CMCProxy/GetKVMData",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CMCProxyServer).GetKVMData(ctx, req.(*GetKVMDataRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _CMCProxy_serviceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "proto.CMCProxy",
|
||||||
|
HandlerType: (*CMCProxyServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "GetKVMData",
|
||||||
|
Handler: _CMCProxy_GetKVMData_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "proxy.proto",
|
||||||
|
}
|
15
proxy.proto
Normal file
15
proxy.proto
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package proto;
|
||||||
|
|
||||||
|
message GetKVMDataRequest {
|
||||||
|
int64 blade_num = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetKVMDataResponse {
|
||||||
|
repeated string arguments = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service CMCProxy {
|
||||||
|
rpc GetKVMData(GetKVMDataRequest) returns (GetKVMDataResponse);
|
||||||
|
}
|
Loading…
Reference in a new issue