diff --git a/cmd/bootstrap/bootstrap.go b/cmd/bootstrap/bootstrap.go index 8d740a90e..ce39fd51e 100644 --- a/cmd/bootstrap/bootstrap.go +++ b/cmd/bootstrap/bootstrap.go @@ -16,5 +16,8 @@ func NewBootstrapCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Co ISOGenCmd := NewISOGenCommand(bootstrapRootCmd, rootSettings) bootstrapRootCmd.AddCommand(ISOGenCmd) + remoteDirectCmd := NewRemoteDirectCommand(rootSettings) + bootstrapRootCmd.AddCommand(remoteDirectCmd) + return bootstrapRootCmd } diff --git a/cmd/bootstrap/bootstrap_remotedirect.go b/cmd/bootstrap/bootstrap_remotedirect.go new file mode 100644 index 000000000..149dec3a1 --- /dev/null +++ b/cmd/bootstrap/bootstrap_remotedirect.go @@ -0,0 +1,73 @@ +package bootstrap + +import ( + "github.com/spf13/cobra" + + "opendev.org/airship/airshipctl/pkg/environment" + alog "opendev.org/airship/airshipctl/pkg/log" + remote "opendev.org/airship/airshipctl/pkg/remote" +) + +// RemoteDirect settings for remotedirect command +type RemoteDirectSettings struct { + *environment.AirshipCTLSettings + + RemoteConfig remote.RemoteDirectConfig +} + +// InitFlags adds flags and their default settings for Remote Direct command +func (cmdSetting *RemoteDirectSettings) InitFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + // TODO: Remove CLI flags after reading configuration from config documents + // ======================================================================== + flags.StringVar(&cmdSetting.RemoteConfig.RemoteURL, + "remote-url", + "http://localhost:8000", + "[Temporary. Will be removed] Remote Redfish/Smash URL") + + flags.StringVar(&cmdSetting.RemoteConfig.EphemeralNodeId, + "eph-node-id", + "", + "[Temporary. Will be removed] Ephemeral Node ID") + + flags.StringVar(&cmdSetting.RemoteConfig.IsoPath, + "iso-path", + "", + "[Temporary. Will be removed] Remote ISO path for ephemeral node") + + flags.StringVar(&cmdSetting.RemoteConfig.RemoteType, + "remote-type", + "redfish", + "Remote type e.g. redfish, smash etc.") + + err := cmd.MarkFlagRequired("eph-node-id") + if err != nil { + alog.Fatal(err) + } + + err = cmd.MarkFlagRequired("iso-path") + if err != nil { + alog.Fatal(err) + } +} + +// New Bootstrap remote direct subcommand +func NewRemoteDirectCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command { + settings := &RemoteDirectSettings{AirshipCTLSettings: rootSettings} + remoteDirect := &cobra.Command{ + Use: "remotedirect", + Short: "Bootstrap ephemeral node", + RunE: func(cmd *cobra.Command, args []string) error { + + /* TODO: Get config from settings.GetCurrentContext() and remove cli arguments */ + + /* Trigger remotedirect based on remote type */ + return remote.DoRemoteDirect(settings.RemoteConfig) + }, + } + + settings.InitFlags(remoteDirect) + + return remoteDirect +} diff --git a/cmd/bootstrap/bootstrap_remotedirect_test.go b/cmd/bootstrap/bootstrap_remotedirect_test.go new file mode 100644 index 000000000..548501465 --- /dev/null +++ b/cmd/bootstrap/bootstrap_remotedirect_test.go @@ -0,0 +1,20 @@ +package bootstrap + +import ( + "testing" + + "opendev.org/airship/airshipctl/testutil" +) + +func TestRemoteDirect(t *testing.T) { + tests := []*testutil.CmdTest{ + { + Name: "remotedirect-cmd-with-help", + CmdLine: "remotedirect --help", + Cmd: NewRemoteDirectCommand(nil), + }, + } + for _, tt := range tests { + testutil.RunTest(t, tt) + } +} diff --git a/cmd/bootstrap/testdata/TestBootstrapGoldenOutput/bootstrap-cmd-with-defaults.golden b/cmd/bootstrap/testdata/TestBootstrapGoldenOutput/bootstrap-cmd-with-defaults.golden index 69b9f2045..7b5f7349d 100644 --- a/cmd/bootstrap/testdata/TestBootstrapGoldenOutput/bootstrap-cmd-with-defaults.golden +++ b/cmd/bootstrap/testdata/TestBootstrapGoldenOutput/bootstrap-cmd-with-defaults.golden @@ -4,8 +4,9 @@ Usage: bootstrap [command] Available Commands: - help Help about any command - isogen Generate bootstrap ISO image + help Help about any command + isogen Generate bootstrap ISO image + remotedirect Bootstrap ephemeral node Flags: -h, --help help for bootstrap diff --git a/cmd/bootstrap/testdata/TestRemoteDirectGoldenOutput/remotedirect-cmd-with-help.golden b/cmd/bootstrap/testdata/TestRemoteDirectGoldenOutput/remotedirect-cmd-with-help.golden new file mode 100644 index 000000000..70dbce427 --- /dev/null +++ b/cmd/bootstrap/testdata/TestRemoteDirectGoldenOutput/remotedirect-cmd-with-help.golden @@ -0,0 +1,11 @@ +Bootstrap ephemeral node + +Usage: + remotedirect [flags] + +Flags: + --eph-node-id string [Temporary. Will be removed] Ephemeral Node ID + -h, --help help for remotedirect + --iso-path string [Temporary. Will be removed] Remote ISO path for ephemeral node + --remote-type string Remote type e.g. redfish, smash etc. (default "redfish") + --remote-url string [Temporary. Will be removed] Remote Redfish/Smash URL (default "http://localhost:8000") diff --git a/go.mod b/go.mod index 72ec9ea97..d7cd03a5b 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/Azure/go-autorest/autorest v0.2.0 // indirect github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e // indirect github.com/Microsoft/go-winio v0.4.12 // indirect + github.com/Nordix/go-redfish v0.0.0-20191016123452-b7790941aa86 + github.com/Nordix/go-redfish/client v0.0.0-20191016123452-b7790941aa86 github.com/argoproj/argo v2.3.0+incompatible github.com/argoproj/pkg v0.0.0-20190409001913-7e3ef65c8d44 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index f9ab8708d..831583936 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,8 @@ -cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.2.0 h1:zBtSTOQTtjzHVRe+mhkiHvHwRTKHhjBEyo1m6DfI3So= github.com/Azure/go-autorest/autorest v0.2.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= @@ -12,7 +10,6 @@ github.com/Azure/go-autorest/autorest/adal v0.1.0 h1:RSw/7EAullliqwkZvgIGDYZWQm1 github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0 h1:Kx+AUU2Te+A3JIyYn6Dfs+cFgx5XorQKuIXrZGoq/SI= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= @@ -22,9 +19,12 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc= github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Nordix/go-redfish v0.0.0-20191016123452-b7790941aa86 h1:+Myox4ydvtWzVSnvF/ZjABEA7RDiPRgE3sS5u4oQsHk= +github.com/Nordix/go-redfish v0.0.0-20191016123452-b7790941aa86/go.mod h1:xlgfxiVLo/0lxBF98IhyCkAboUX27SOXieV1LsbyA30= +github.com/Nordix/go-redfish/client v0.0.0-20191016123452-b7790941aa86 h1:Frmy6I0XnpsCrM+FFrXq3354M3dVSrR+fFGIGb3fxXk= +github.com/Nordix/go-redfish/client v0.0.0-20191016123452-b7790941aa86/go.mod h1:44cqm4BYTmR3MAfUFZmYYOXZ4S9rDr8QM6MlQagq4M8= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -54,15 +54,11 @@ github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlR github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/colinmarc/hdfs v0.0.0-20180802165501-48eb8d6c34a9 h1:hJqIlFDcaaBLEBkCuqyEtzSsloo/h+lm08Qrq1OM/e8= github.com/colinmarc/hdfs v0.0.0-20180802165501-48eb8d6c34a9/go.mod h1:0DumPviB681UcSuJErAbDIOx6SIaJWj463TymfZG02I= -github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -89,11 +85,8 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs= github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f h1:AUj1VoZUfhPhOPHULCQQDnGhRelpFWHMLhQVWDsS0v4= github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w= github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -107,7 +100,6 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= @@ -131,11 +123,9 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -144,15 +134,11 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450 h1:7xqw01UYS+KCI25bMrPxwNYkSns2Db1ziQPpVq99FpE= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= -github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995 h1:f5gsjBiF9tRRVomCvrkGMMWI8W1f2OBFar2c5oakAP0= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e h1:KhcknUwkWHKZPbFy2P7jH5LKJ3La+0ZeknkkmrSgqb0= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= @@ -167,15 +153,12 @@ github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/ github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -185,11 +168,9 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= @@ -204,14 +185,11 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -240,7 +218,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -249,26 +226,21 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -292,11 +264,9 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -311,11 +281,9 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -325,20 +293,14 @@ github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e h1:f1yevOHP+Su github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936 h1:J9gO8RJCAFlln1jsvRba/CWVUnMHwObklfxxjErl1uk= github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -366,10 +328,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -394,7 +355,6 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg= golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -411,7 +371,6 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw google.golang.org/api v0.3.1 h1:oJra/lMfmtm13/rgY/8i3MzjFWYXvQIAKjQ3HqofMk8= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk= @@ -422,9 +381,7 @@ google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -432,7 +389,6 @@ gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hr gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v2 v2.0.0 h1:6Bmcdaxb0dD3HyHbo/MtJ2Q1wXLDuZJFwXZmuZvM+zw= gopkg.in/jcmturner/goidentity.v2 v2.0.0/go.mod h1:vCwK9HeXksMeUmQ4SxDd1tRz4LejrKh3KRVjQWhjvZI= gopkg.in/jcmturner/gokrb5.v5 v5.3.0 h1:RS1MYApX27Hx1Xw7NECs7XxGxxrm69/4OmaRuX9kwec= gopkg.in/jcmturner/gokrb5.v5 v5.3.0/go.mod h1:oQz8Wc5GsctOTgCVyKad1Vw4TCWz5G6gfIQr88RPv4k= @@ -443,14 +399,11 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -475,10 +428,8 @@ k8s.io/cluster-bootstrap v0.0.0-20190516232516-d7d78ab2cfe7 h1:5wvjieVoU4oovHlke k8s.io/cluster-bootstrap v0.0.0-20190516232516-d7d78ab2cfe7/go.mod h1:iBSm2nwo3OaiuW8VDvc3ySDXK5SKfUrxwPvBloKG7zg= k8s.io/component-base v0.0.0-20190516230545-9b07ebd4193b h1:uGhPlhc5zuKFBhA2A94CFQjr1R9gz3u65BQFGqcV+rE= k8s.io/component-base v0.0.0-20190516230545-9b07ebd4193b/go.mod h1:DMaomcf3j3MM2j1FsvlLVVlc7wA2jPytEur3cP9zRxQ= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c= k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/pkg/error/error.go b/pkg/error/error.go new file mode 100644 index 000000000..1822acf7b --- /dev/null +++ b/pkg/error/error.go @@ -0,0 +1,14 @@ +package error + +// AirshipError is the base error type +// used to create extended error types +// in other airshipctl packages. +type AirshipError struct { + Message string +} + +// Error function implments the golang +// error interface +func (ae *AirshipError) Error() string { + return ae.Message +} diff --git a/pkg/remote/errors.go b/pkg/remote/errors.go new file mode 100644 index 000000000..4fea0965b --- /dev/null +++ b/pkg/remote/errors.go @@ -0,0 +1,17 @@ +package remote + +import ( + "fmt" + + aerror "opendev.org/airship/airshipctl/pkg/error" +) + +type RemoteDirectError struct { + aerror.AirshipError +} + +func NewRemoteDirectErrorf(format string, v ...interface{}) error { + e := &RemoteDirectError{} + e.Message = fmt.Sprintf(format, v...) + return e +} diff --git a/pkg/remote/redfish/errors.go b/pkg/remote/redfish/errors.go new file mode 100644 index 000000000..d0d02f50a --- /dev/null +++ b/pkg/remote/redfish/errors.go @@ -0,0 +1,42 @@ +package redfish + +import ( + "fmt" + "net/http" + + aerror "opendev.org/airship/airshipctl/pkg/error" +) + +type RedfishClientError struct { + aerror.AirshipError +} + +func NewRedfishClientErrorf(format string, v ...interface{}) error { + e := &RedfishClientError{} + e.Message = fmt.Sprintf(format, v...) + return e +} + +type RedfishConfigError struct { + aerror.AirshipError +} + +func NewRedfishConfigErrorf(format string, v ...interface{}) error { + e := &RedfishConfigError{} + e.Message = fmt.Sprintf(format, v...) + return e +} + +// Redfish client return error if response has no body. +// In this case this function further checks the +// HTTP response code. +func ScreenRedfishError(httpResp *http.Response, err error) error { + if err != nil && httpResp != nil { + if httpResp.StatusCode < 200 || httpResp.StatusCode >= 400 { + return NewRedfishClientErrorf("%s", err.Error()) + } + } else if err != nil { + return NewRedfishClientErrorf("%s", err.Error()) + } + return nil +} diff --git a/pkg/remote/redfish/redfish.go b/pkg/remote/redfish/redfish.go new file mode 100644 index 000000000..425f27e2c --- /dev/null +++ b/pkg/remote/redfish/redfish.go @@ -0,0 +1,127 @@ +package redfish + +import ( + "context" + "net/url" + + redfishApi "github.com/Nordix/go-redfish/api" + redfishClient "github.com/Nordix/go-redfish/client" + + alog "opendev.org/airship/airshipctl/pkg/log" +) + +type RedfishRemoteDirect struct { + + // Context + Context context.Context + + // remote URL + RemoteURL url.URL + + // ephemeral Host ID + EphemeralNodeId string + + // ISO URL + IsoPath string + + // Redfish Client implementation + Api redfishApi.RedfishAPI +} + +// Top level function to handle Redfish remote direct +func (cfg RedfishRemoteDirect) DoRemoteDirect() error { + + alog.Debugf("Using Redfish Endpoint: '%s'", cfg.RemoteURL.String()) + + /* TODO: Add Authentication when redfish library supports it. */ + + /* Get system details */ + systemId := cfg.EphemeralNodeId + system, _, err := cfg.Api.GetSystem(cfg.Context, systemId) + if err != nil { + return NewRedfishClientErrorf("Get System[%s] failed with err: %s", systemId, err.Error()) + } + alog.Debugf("Ephemeral Node System ID: '%s'", systemId) + + /* get manager for system */ + managerId, err := GetResourceIDFromURL(system.Links.ManagedBy[0].OdataId) + if err != nil { + return err + } + alog.Debugf("Ephemeral node managerId: '%s'", managerId) + + /* Get manager's Cd or DVD virtual media ID */ + vMediaId, vMediaType, err := GetVirtualMediaId(cfg.Context, cfg.Api, managerId) + if err != nil { + return err + } + alog.Debugf("Ephemeral Node Virtual Media Id: '%s'", vMediaId) + + /* Load ISO in manager's virtual media */ + err = SetVirtualMedia(cfg.Context, cfg.Api, managerId, vMediaId, cfg.IsoPath) + if err != nil { + return err + } + alog.Debugf("Successfully loaded virtual media: '%s'", cfg.IsoPath) + + /* Set system's bootsource to selected media */ + err = SetSystemBootSourceForMediaType(cfg.Context, cfg.Api, systemId, vMediaType) + if err != nil { + return err + } + + /* Reboot system */ + err = RebootSystem(cfg.Context, cfg.Api, systemId) + if err != nil { + return err + } + alog.Debug("Restarted ephemeral host") + + return nil +} + +// Creates a new Redfish remote direct client. +func NewRedfishRemoteDirectClient(ctx context.Context, + remoteURL string, + ephNodeID string, + isoPath string, +) (RedfishRemoteDirect, error) { + + if remoteURL == "" { + return RedfishRemoteDirect{}, + NewRedfishConfigErrorf("redfish remote url empty") + } + + if ephNodeID == "" { + return RedfishRemoteDirect{}, + NewRedfishConfigErrorf("redfish ephemeral node id empty") + } + + if isoPath == "" { + return RedfishRemoteDirect{}, + NewRedfishConfigErrorf("redfish ephemeral node iso Path empty") + } + + cfg := &redfishClient.Configuration{ + BasePath: remoteURL, + DefaultHeader: make(map[string]string), + UserAgent: "airshipctl/client", + } + + var api redfishApi.RedfishAPI = redfishClient.NewAPIClient(cfg).DefaultApi + + url, err := url.Parse(remoteURL) + if err != nil { + return RedfishRemoteDirect{}, NewRedfishConfigErrorf("Invalid URL format: %v", err) + } + + client := RedfishRemoteDirect{ + Context: ctx, + RemoteURL: *url, + EphemeralNodeId: ephNodeID, + IsoPath: isoPath, + Api: api, + } + + return client, nil +} diff --git a/pkg/remote/redfish/redfish_test.go b/pkg/remote/redfish/redfish_test.go new file mode 100644 index 000000000..339da6d22 --- /dev/null +++ b/pkg/remote/redfish/redfish_test.go @@ -0,0 +1,266 @@ +package redfish_test + +import ( + "context" + "fmt" + "net/http" + "testing" + + redfishAPI "github.com/Nordix/go-redfish/api" + redfishMocks "github.com/Nordix/go-redfish/api/mocks" + redfishClient "github.com/Nordix/go-redfish/client" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + . "opendev.org/airship/airshipctl/pkg/remote/redfish" +) + +const ( + computerSystemID = "server-100" +) + +var ( + ctx = context.Background() + defaultURL = "https://localhost:1234" + + SampleSystem = redfishClient.ComputerSystem{ + Id: computerSystemID, + Name: "server-100", + UUID: "58893887-8974-2487-2389-841168418919", + Status: redfishClient.Status{ + State: "Enabled", + Health: "OK", + }, + Links: redfishClient.SystemLinks{ + ManagedBy: []redfishClient.IdRef{ + {OdataId: "/redfish/v1/Managers/manager-1"}, + }, + }, + Boot: redfishClient.Boot{ + BootSourceOverrideTarget: redfishClient.BOOTSOURCE_CD, + BootSourceOverrideEnabled: redfishClient.BOOTSOURCEOVERRIDEENABLED_CONTINUOUS, + BootSourceOverrideTargetRedfishAllowableValues: []redfishClient.BootSource{ + redfishClient.BOOTSOURCE_CD, + redfishClient.BOOTSOURCE_FLOPPY, + redfishClient.BOOTSOURCE_HDD, + redfishClient.BOOTSOURCE_PXE, + }, + }, + } +) + +func getDefaultRedfishRemoteDirectObj(t *testing.T, api redfishAPI.RedfishAPI) RedfishRemoteDirect { + + t.Helper() + + rDCfg, err := NewRedfishRemoteDirectClient( + ctx, + defaultURL, + computerSystemID, + "/tmp/test.iso", + ) + + assert.NoError(t, err) + + rDCfg.Api = api + + return rDCfg +} + +func TestRedfishRemoteDirectNormal(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + systemID := computerSystemID + m.On("GetSystem", ctx, systemID). + Return(SampleSystem, nil, nil) + m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything). + Return(redfishClient.RedfishError{}, nil, nil) + + systemReq := redfishClient.ComputerSystem{ + Boot: redfishClient.Boot{ + BootSourceOverrideTarget: redfishClient.BOOTSOURCE_CD, + }, + } + m.On("SetSystem", ctx, systemID, systemReq). + Times(1). + Return(redfishClient.ComputerSystem{}, nil, nil) + + resetReq := redfishClient.ResetRequestBody{} + resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, nil, nil) + + resetReq.ResetType = redfishClient.RESETTYPE_ON + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, nil, nil) + + rDCfg := getDefaultRedfishRemoteDirectObj(t, m) + + err := rDCfg.DoRemoteDirect() + + assert.NoError(t, err) +} + +func TestRedfishRemoteDirectInvalidSystemId(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + systemID := "invalid-server" + localRDCfg := getDefaultRedfishRemoteDirectObj(t, m) + + localRDCfg.EphemeralNodeId = systemID + + realErr := fmt.Errorf("%s system do not exist", systemID) + m.On("GetSystem", ctx, systemID). + Times(1). + Return(redfishClient.ComputerSystem{}, nil, realErr) + + err := localRDCfg.DoRemoteDirect() + + _, ok := err.(*RedfishClientError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectGetSystemNetworkError(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + systemID := computerSystemID + realErr := fmt.Errorf("Server request timeout") + httpResp := &http.Response{ + StatusCode: 408, + } + m.On("GetSystem", ctx, systemID). + Times(1). + Return(redfishClient.ComputerSystem{}, httpResp, realErr) + + rDCfg := getDefaultRedfishRemoteDirectObj(t, m) + + err := rDCfg.DoRemoteDirect() + + _, ok := err.(*RedfishClientError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectInvalidIsoPath(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + systemID := computerSystemID + rDCfg := getDefaultRedfishRemoteDirectObj(t, m) + localRDCfg := rDCfg + localRDCfg.IsoPath = "bogus/path/to.iso" + + errStr := "Invalid remote boot path" + realErr := fmt.Errorf(errStr) + httpResp := &http.Response{ + StatusCode: 500, + } + m.On("GetSystem", ctx, systemID). + Times(1). + Return(SampleSystem, nil, nil) + + m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything). + Return(redfishClient.RedfishError{}, httpResp, realErr) + + err := localRDCfg.DoRemoteDirect() + + _, ok := err.(*RedfishClientError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectCdDvdNotAvailableInBootSources(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + systemID := computerSystemID + invalidSystem := SampleSystem + invalidSystem.Boot.BootSourceOverrideTargetRedfishAllowableValues = []redfishClient.BootSource{ + redfishClient.BOOTSOURCE_HDD, + redfishClient.BOOTSOURCE_PXE, + } + + m.On("GetSystem", ctx, systemID). + Return(invalidSystem, nil, nil) + + m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything). + Return(redfishClient.RedfishError{}, nil, nil) + + rDCfg := getDefaultRedfishRemoteDirectObj(t, m) + + err := rDCfg.DoRemoteDirect() + + _, ok := err.(*RedfishClientError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectSetSystemBootSourceFailed(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + systemID := computerSystemID + m.On("GetSystem", ctx, systemID). + Return(SampleSystem, nil, nil) + + m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything). + Return(redfishClient.RedfishError{}, nil, nil) + + realErr := fmt.Errorf("Unauthorized") + httpResp := &http.Response{ + StatusCode: 401, + } + m.On("SetSystem", ctx, systemID, mock.Anything). + Times(1). + Return(redfishClient.ComputerSystem{}, httpResp, realErr) + + rDCfg := getDefaultRedfishRemoteDirectObj(t, m) + + err := rDCfg.DoRemoteDirect() + + _, ok := err.(*RedfishClientError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectSystemRebootFailed(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + systemID := computerSystemID + + m.On("GetSystem", ctx, systemID). + Return(SampleSystem, nil, nil) + + m.On("InsertVirtualMedia", ctx, mock.Anything, mock.Anything, mock.Anything). + Return(redfishClient.RedfishError{}, nil, nil) + + m.On("SetSystem", ctx, systemID, mock.Anything). + Times(1). + Return(redfishClient.ComputerSystem{}, nil, nil) + + realErr := fmt.Errorf("Unauthorized") + httpResp := &http.Response{ + StatusCode: 401, + } + resetReq := redfishClient.ResetRequestBody{} + resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, httpResp, realErr) + + rDCfg := getDefaultRedfishRemoteDirectObj(t, m) + + err := rDCfg.DoRemoteDirect() + + _, ok := err.(*RedfishClientError) + assert.True(t, ok) +} diff --git a/pkg/remote/redfish/utils.go b/pkg/remote/redfish/utils.go new file mode 100644 index 000000000..73d04bfb5 --- /dev/null +++ b/pkg/remote/redfish/utils.go @@ -0,0 +1,118 @@ +package redfish + +import ( + "context" + "net/url" + "strings" + "time" + + redfishApi "github.com/Nordix/go-redfish/api" + redfishClient "github.com/Nordix/go-redfish/client" +) + +const ( + SystemRebootDelay = 2 * time.Second +) + +// Redfish Id ref is a URI which contains resource Id +// as the last part. This function extracts resource +// ID from ID ref +func GetResourceIDFromURL(refURL string) (string, error) { + + u, err := url.Parse(refURL) + + if err != nil { + return "", err + } + url := strings.TrimSuffix(u.Path, "/") + elems := strings.Split(url, "/") + + id := elems[len(elems)-1] + return id, nil +} + +// Checks whether an ID exists in Redfish IDref collection +func IsIDInList(idRefList []redfishClient.IdRef, id string) bool { + for _, r := range idRefList { + rId, _ := GetResourceIDFromURL(r.OdataId) + if rId == id { + return true + } + } + return false +} + +// This function walks through the list of manager's virtual media resources +// and gets the ID of virtualmedia which has type "CD" or "DVD" +func GetVirtualMediaId(ctx context.Context, + api redfishApi.RedfishAPI, + managerId string, +) (string, string, error) { + + // TODO: Sushy emulator has a bug which sends 'virtualMedia.inserted' field as + // string instead of a boolean which causes the redfish client to fail + // while unmarshalling this field. + // Insert logic here after the bug is fixed in sushy-emulator. + return "Cd", "CD", nil +} + +// This function walks through the bootsources of a system and sets the bootsource +// which is compatible with the given media type. +func SetSystemBootSourceForMediaType(ctx context.Context, + api redfishApi.RedfishAPI, + systemId string, + mediaType string) error { + + /* Check available boot sources for system */ + system, _, err := api.GetSystem(ctx, systemId) + if err != nil { + return NewRedfishClientErrorf("Get System[%s] failed with err: %s", systemId, err.Error()) + } + + allowableValues := system.Boot.BootSourceOverrideTargetRedfishAllowableValues + for _, bootSource := range allowableValues { + if strings.EqualFold(string(bootSource), mediaType) { + /* set boot source */ + systemReq := redfishClient.ComputerSystem{} + systemReq.Boot.BootSourceOverrideTarget = bootSource + _, httpResp, err := api.SetSystem(ctx, systemId, systemReq) + return ScreenRedfishError(httpResp, err) + } + } + + return NewRedfishClientErrorf("failed to set system[%s] boot source", systemId) +} + +// Reboots a system by force shutoff and turning on. +func RebootSystem(ctx context.Context, api redfishApi.RedfishAPI, systemId string) error { + + resetReq := redfishClient.ResetRequestBody{} + resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF + _, httpResp, err := api.ResetSystem(ctx, systemId, resetReq) + if err != nil { + return ScreenRedfishError(httpResp, err) + } + + time.Sleep(SystemRebootDelay) + + resetReq.ResetType = redfishClient.RESETTYPE_ON + _, httpResp, err = api.ResetSystem(ctx, systemId, resetReq) + + return ScreenRedfishError(httpResp, err) +} + +// Insert the remote virtual media to the given virtual media id. +// This assumes that isoPath is accessible to the redfish server and +// virtualMedia device is either of type CD or DVD. +func SetVirtualMedia(ctx context.Context, + api redfishApi.RedfishAPI, + managerId string, + vMediaId string, + isoPath string) error { + + vMediaReq := redfishClient.InsertMediaRequestBody{} + vMediaReq.Image = isoPath + vMediaReq.Inserted = true + _, httpResp, err := api.InsertVirtualMedia(ctx, managerId, vMediaId, vMediaReq) + return ScreenRedfishError(httpResp, err) +} diff --git a/pkg/remote/redfish/utils_test.go b/pkg/remote/redfish/utils_test.go new file mode 100644 index 000000000..715134fe6 --- /dev/null +++ b/pkg/remote/redfish/utils_test.go @@ -0,0 +1,194 @@ +package redfish + +import ( + "context" + "fmt" + "net/http" + "testing" + + redfishMocks "github.com/Nordix/go-redfish/api/mocks" + redfishClient "github.com/Nordix/go-redfish/client" + "github.com/stretchr/testify/assert" +) + +const ( + systemID = "123" +) + +func TestRedfishErrorNoError(t *testing.T) { + + err := ScreenRedfishError(nil, nil) + assert.NoError(t, err) +} + +func TestRedfishErrorNonNilErrorWithoutHttpResp(t *testing.T) { + + realErr := fmt.Errorf("Sample error") + err := ScreenRedfishError(nil, realErr) + assert.Error(t, err) + _, ok := err.(*RedfishClientError) + assert.True(t, ok) +} + +func TestRedfishErrorNonNilErrorWithHttpRespError(t *testing.T) { + + realErr := fmt.Errorf("Sample error") + + httpResp := &http.Response{StatusCode: 408} + err := ScreenRedfishError(httpResp, realErr) + assert.Equal(t, err, NewRedfishClientErrorf(realErr.Error())) + + httpResp.StatusCode = 400 + err = ScreenRedfishError(httpResp, realErr) + assert.Equal(t, err, NewRedfishClientErrorf(realErr.Error())) + + httpResp.StatusCode = 199 + err = ScreenRedfishError(httpResp, realErr) + assert.Equal(t, err, NewRedfishClientErrorf(realErr.Error())) +} + +func TestRedfishErrorNonNilErrorWithHttpRespOK(t *testing.T) { + + realErr := fmt.Errorf("Sample error") + + httpResp := &http.Response{StatusCode: 204} + err := ScreenRedfishError(httpResp, realErr) + assert.NoError(t, err) + + httpResp.StatusCode = 200 + err = ScreenRedfishError(httpResp, realErr) + assert.NoError(t, err) + + httpResp.StatusCode = 399 + err = ScreenRedfishError(httpResp, realErr) + assert.NoError(t, err) +} + +func TestRedfishUtilGetResIDFromURL(t *testing.T) { + + // simple case + url := "api/user/123" + id, err := GetResourceIDFromURL(url) + assert.NoError(t, err) + assert.Equal(t, id, "123") + + // FQDN + url = "http://www.abc.com/api/user/123" + id, err = GetResourceIDFromURL(url) + assert.NoError(t, err) + assert.Equal(t, id, "123") + + //Trailing slash + url = "api/user/123/" + id, err = GetResourceIDFromURL(url) + assert.NoError(t, err) + assert.Equal(t, id, "123") +} + +func TestRedfishUtilIsIdInList(t *testing.T) { + + idList := []redfishClient.IdRef{ + {OdataId: "/path/to/id/1"}, + {OdataId: "/path/to/id/2"}, + {OdataId: "/path/to/id/3"}, + {OdataId: "/path/to/id/4"}, + } + emptyList := []redfishClient.IdRef{} + + res := IsIDInList(idList, "1") + assert.True(t, res) + + res = IsIDInList(idList, "100") + assert.False(t, res) + + res = IsIDInList(emptyList, "1") + assert.False(t, res) +} + +func TestRedfishUtilRebootSystemOK(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + ctx := context.Background() + resetReq := redfishClient.ResetRequestBody{} + resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, nil, nil) + + resetReq.ResetType = redfishClient.RESETTYPE_ON + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, nil, nil) + + err := RebootSystem(ctx, m, systemID) + assert.NoError(t, err) +} + +func TestRedfishUtilRebootSystemForceOffError2(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + ctx := context.Background() + realErr := fmt.Errorf("Unauthorized") + httpResp := &http.Response{ + StatusCode: 401, + } + resetReq := redfishClient.ResetRequestBody{} + resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF + + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, httpResp, realErr) + + err := RebootSystem(ctx, m, systemID) + assert.Error(t, err) +} + +func TestRedfishUtilRebootSystemForceOffError(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + ctx := context.Background() + realErr := fmt.Errorf("Unauthorized") + httpResp := &http.Response{ + StatusCode: 401, + } + resetReq := redfishClient.ResetRequestBody{} + resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, httpResp, realErr) + + err := RebootSystem(ctx, m, systemID) + assert.Error(t, err) +} + +func TestRedfishUtilRebootSystemTurningOnError(t *testing.T) { + + m := &redfishMocks.RedfishAPI{} + defer m.AssertExpectations(t) + + ctx := context.Background() + resetReq := redfishClient.ResetRequestBody{} + resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF + m.On("ResetSystem", ctx, systemID, resetReq). + Times(1). + Return(redfishClient.RedfishError{}, nil, nil) + + realErr := fmt.Errorf("Unauthorized") + httpResp := &http.Response{ + StatusCode: 401, + } + resetOnReq := redfishClient.ResetRequestBody{} + resetOnReq.ResetType = redfishClient.RESETTYPE_ON + m.On("ResetSystem", ctx, systemID, resetOnReq). + Times(1). + Return(redfishClient.RedfishError{}, httpResp, realErr) + + err := RebootSystem(ctx, m, systemID) + assert.Error(t, err) +} diff --git a/pkg/remote/remote_direct.go b/pkg/remote/remote_direct.go new file mode 100644 index 000000000..cd8779e3d --- /dev/null +++ b/pkg/remote/remote_direct.go @@ -0,0 +1,86 @@ +package remote + +import ( + "context" + + alog "opendev.org/airship/airshipctl/pkg/log" + redfish "opendev.org/airship/airshipctl/pkg/remote/redfish" +) + +const ( + AirshipRemoteTypeRedfish string = "redfish" + AirshipRemoteTypeSmash string = "smash" +) + +// This structure defines the common remote direct config +// for all remote types. +type RemoteDirectConfig struct { + // remote type + RemoteType string + + // remote URL + RemoteURL string + + // ephemeral Host ID + EphemeralNodeId string + + // ISO URL + IsoPath string + + // TODO: Ephemeral Node IP + + // TODO: kubeconfig (in object form or raw yaml?) for ephemeral node validation. + + // TODO: More fields can be added on need basis +} + +// Interface to be implemented by remoteDirect implementation +type RemoteDirectClient interface { + DoRemoteDirect() error +} + +// Get remotedirect client based on config +func getRemoteDirectClient(remoteConfig RemoteDirectConfig) (RemoteDirectClient, error) { + + var client RemoteDirectClient + var err error + + switch remoteConfig.RemoteType { + case AirshipRemoteTypeRedfish: + alog.Debug("Remote type redfish") + + client, err = redfish.NewRedfishRemoteDirectClient( + context.Background(), + remoteConfig.RemoteURL, + remoteConfig.EphemeralNodeId, + remoteConfig.IsoPath) + if err != nil { + alog.Debugf("redfish remotedirect client creation failed") + return nil, err + } + + default: + return nil, NewRemoteDirectErrorf("invalid remote type") + } + + return client, nil +} + +// Top level function to execute remote direct based on remote type +func DoRemoteDirect(remoteConfig RemoteDirectConfig) error { + + client, err := getRemoteDirectClient(remoteConfig) + if err != nil { + return err + } + + err = client.DoRemoteDirect() + if err != nil { + alog.Debugf("remote direct failed: %s", err) + return err + } + + alog.Print("Remote direct successfully completed") + + return nil +} diff --git a/pkg/remote/remote_direct_test.go b/pkg/remote/remote_direct_test.go new file mode 100644 index 000000000..d51f36f66 --- /dev/null +++ b/pkg/remote/remote_direct_test.go @@ -0,0 +1,84 @@ +package remote + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + redfish "opendev.org/airship/airshipctl/pkg/remote/redfish" +) + +func TestUnknownRemoteType(t *testing.T) { + + rdCfg := RemoteDirectConfig{ + RemoteType: "new-remote", + RemoteURL: "http://localhost:8000", + EphemeralNodeId: "test-node", + IsoPath: "/test.iso", + } + + err := DoRemoteDirect(rdCfg) + + _, ok := err.(*RemoteDirectError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectWithBogusConfig(t *testing.T) { + + rdCfg := RemoteDirectConfig{ + RemoteType: "redfish", + RemoteURL: "http://nolocalhost:8888", + EphemeralNodeId: "test-node", + IsoPath: "/test.iso", + } + + err := DoRemoteDirect(rdCfg) + + _, ok := err.(*redfish.RedfishClientError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectWithEmptyURL(t *testing.T) { + + rdCfg := RemoteDirectConfig{ + RemoteType: "redfish", + RemoteURL: "", + EphemeralNodeId: "test-node", + IsoPath: "/test.iso", + } + + err := DoRemoteDirect(rdCfg) + + _, ok := err.(*redfish.RedfishConfigError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectWithEmptyNodeID(t *testing.T) { + + rdCfg := RemoteDirectConfig{ + RemoteType: "redfish", + RemoteURL: "http://nolocalhost:8888", + EphemeralNodeId: "", + IsoPath: "/test.iso", + } + + err := DoRemoteDirect(rdCfg) + + _, ok := err.(*redfish.RedfishConfigError) + assert.True(t, ok) +} + +func TestRedfishRemoteDirectWithEmptyIsoPath(t *testing.T) { + + rdCfg := RemoteDirectConfig{ + RemoteType: "redfish", + RemoteURL: "http://nolocalhost:8888", + EphemeralNodeId: "123", + IsoPath: "", + } + + err := DoRemoteDirect(rdCfg) + + _, ok := err.(*redfish.RedfishConfigError) + assert.True(t, ok) +}