From 58019ca950d018539d40a6f9cfa51093a1fcbee2 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 19 Apr 2017 15:28:38 -0700 Subject: [PATCH 1/2] Add generated types and methods for PBM Reworked the generator so we generate packages for API extension endpoints. This works with other endpoints too, such as sms and sps. Note that in the case of pbm, there is no need to generate a mo/ package, as pbm managed objects do not have any properties, only methods. --- gen/gen.sh | 55 +- gen/gen_from_vmodl.rb | 11 +- gen/gen_from_wsdl.rb | 22 +- gen/vim_wsdl.rb | 116 ++- pbm/methods/methods.go | 664 ++++++++++++++++ pbm/types/enum.go | 211 +++++ pbm/types/if.go | 139 ++++ pbm/types/types.go | 1712 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 2870 insertions(+), 60 deletions(-) create mode 100644 pbm/methods/methods.go create mode 100644 pbm/types/enum.go create mode 100644 pbm/types/if.go create mode 100644 pbm/types/types.go diff --git a/gen/gen.sh b/gen/gen.sh index 8373fcf82..4a5a0557e 100755 --- a/gen/gen.sh +++ b/gen/gen.sh @@ -16,23 +16,38 @@ set -e -if [ ! -f rbvmomi/vmodl.db ]; then - git clone https://github.com/vmware/rbvmomi -fi - -dst=../vim25 - -pkgs=$(echo $dst/{types,methods,mo}) -mkdir -p $pkgs - -bundle exec ruby gen_from_wsdl.rb $dst -bundle exec ruby gen_from_vmodl.rb $dst - -for p in $pkgs -do - echo $p - cd $p - goimports -w *.go - go install - cd - -done +generate() { + dst="$1" + wsdl="$2" + modl="$3" + + pkgs=(types methods) + if [ -n "$modl" ] ; then + pkgs+=(mo) + fi + + for p in "${pkgs[@]}" + do + mkdir -p "$dst/$p" + done + + echo "generating $dst/..." + + bundle exec ruby gen_from_wsdl.rb "$dst" "$wsdl" + if [ -n "$modl" ] ; then + bundle exec ruby gen_from_vmodl.rb "$dst" "$wsdl" "$modl" + fi + + for p in "${pkgs[@]}" + do + pushd "$dst/$p" >/dev/null + goimports -w ./*.go + go install + popd >/dev/null + done +} + +# ./sdk/ contains the contents of wsdl.zip from vimbase build 5037323 + +generate "../vim25" "vim" "./rbvmomi/vmodl.db" # from github.com/vmware/rbvmomi@f6907e6 +generate "../pbm" "pbm" diff --git a/gen/gen_from_vmodl.rb b/gen/gen_from_vmodl.rb index 33f72624c..7cd89ce0a 100644 --- a/gen/gen_from_vmodl.rb +++ b/gen/gen_from_vmodl.rb @@ -18,10 +18,8 @@ require "test/unit" -PATH = File.expand_path("../rbvmomi", __FILE__) - def read(file) - File.open(File.join(PATH, file)) + File.open(file) end class Prop @@ -198,7 +196,6 @@ def managed @data.map do |k,v| next if !v.is_a?(Hash) next if v["kind"] != "managed" - next if k =~ /^pbm/i Managed.new(self, k, v) end.compact @@ -209,15 +206,15 @@ def managed raise "first argument not a directory" end -wsdl = WSDL.new(WSDL.read "vim.wsdl") +wsdl = WSDL.new(WSDL.read ARGV[1]+".wsdl") wsdl.validate_assumptions! wsdl.peek() +vmodl = Vmodl.new(read ARGV[2] || "./rbvmomi/vmodl.db") + File.open(File.join(ARGV.first, "mo/mo.go"), "w") do |io| io.print WSDL.header("mo") - vmodl = Vmodl.new(read "vmodl.db") - vmodl. managed. sort_by { |m| m.name }. diff --git a/gen/gen_from_wsdl.rb b/gen/gen_from_wsdl.rb index 5004cfd0f..69c42a2de 100644 --- a/gen/gen_from_wsdl.rb +++ b/gen/gen_from_wsdl.rb @@ -20,7 +20,8 @@ raise "first argument not a directory" end -wsdl = WSDL.new(WSDL.read "vim.wsdl") +target = ARGV[1] +wsdl = WSDL.new(WSDL.read target+".wsdl") wsdl.validate_assumptions! wsdl.peek() @@ -41,6 +42,14 @@ File.open(File.join(ARGV.first, "types/types.go"), "w") do |io| io.print WSDL.header("types") + if target != "vim" + io.print < Date: Wed, 19 Apr 2017 17:24:04 -0700 Subject: [PATCH 2/2] Add PBM client and wrapper methods There were changes required to marshal the SOAP header used for authentication. This can also be used with API endpoints other than pbm. --- pbm/client.go | 96 ++++++++++++++++++++++++ pbm/client_test.go | 158 ++++++++++++++++++++++++++++++++++++++++ vim25/soap/client.go | 34 +++++++++ vim25/soap/soap.go | 8 +- vim25/soap/soap_test.go | 18 +++-- vim25/types/registry.go | 11 ++- 6 files changed, 311 insertions(+), 14 deletions(-) create mode 100644 pbm/client.go create mode 100644 pbm/client_test.go diff --git a/pbm/client.go b/pbm/client.go new file mode 100644 index 000000000..cf09d2668 --- /dev/null +++ b/pbm/client.go @@ -0,0 +1,96 @@ +/* +Copyright (c) 2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pbm + +import ( + "context" + + "github.com/vmware/govmomi/pbm/methods" + "github.com/vmware/govmomi/pbm/types" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/soap" + vim "github.com/vmware/govmomi/vim25/types" +) + +type Client struct { + *soap.Client + + ServiceContent types.PbmServiceInstanceContent +} + +func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) { + sc := c.Client.NewServiceClient("/pbm/sdk", "urn:pbm") + + req := types.PbmRetrieveServiceContent{ + This: vim.ManagedObjectReference{ + Type: "PbmServiceInstance", + Value: "ServiceInstance", + }, + } + + res, err := methods.PbmRetrieveServiceContent(ctx, sc, &req) + if err != nil { + return nil, err + } + + return &Client{sc, res.Returnval}, nil +} + +func (c *Client) QueryProfile(ctx context.Context, rtype types.PbmProfileResourceType, category string) ([]types.PbmProfileId, error) { + req := types.PbmQueryProfile{ + This: c.ServiceContent.ProfileManager, + ResourceType: rtype, + ProfileCategory: category, + } + + res, err := methods.PbmQueryProfile(ctx, c, &req) + if err != nil { + return nil, err + } + + return res.Returnval, nil +} + +func (c *Client) RetrieveContent(ctx context.Context, ids []types.PbmProfileId) ([]types.BasePbmProfile, error) { + req := types.PbmRetrieveContent{ + This: c.ServiceContent.ProfileManager, + ProfileIds: ids, + } + + res, err := methods.PbmRetrieveContent(ctx, c, &req) + if err != nil { + return nil, err + } + + return res.Returnval, nil +} + +func (c *Client) CheckRequirements(ctx context.Context, hubs []types.PbmPlacementHub, ref *types.PbmServerObjectRef, preq []types.BasePbmPlacementRequirement) ([]types.PbmPlacementCompatibilityResult, error) { + req := types.PbmCheckRequirements{ + This: c.ServiceContent.PlacementSolver, + HubsToSearch: hubs, + PlacementSubjectRef: ref, + PlacementSubjectRequirement: preq, + } + + res, err := methods.PbmCheckRequirements(ctx, c, &req) + if err != nil { + return nil, err + } + + return res.Returnval, nil +} diff --git a/pbm/client_test.go b/pbm/client_test.go new file mode 100644 index 000000000..aedadb41c --- /dev/null +++ b/pbm/client_test.go @@ -0,0 +1,158 @@ +/* +Copyright (c) 2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pbm + +import ( + "context" + "os" + "reflect" + "sort" + "testing" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/pbm/types" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/view" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" + vim "github.com/vmware/govmomi/vim25/types" +) + +func TestClient(t *testing.T) { + url := os.Getenv("GOVMOMI_PBM_URL") + if url == "" { + t.SkipNow() + } + + clusterName := os.Getenv("GOVMOMI_PBM_CLUSTER") + + u, err := soap.ParseURL(url) + if err != nil { + t.Fatal(err) + } + + ctx := context.Background() + + c, err := govmomi.NewClient(ctx, u, true) + if err != nil { + t.Fatal(err) + } + + pc, err := NewClient(ctx, c.Client) + if err != nil { + t.Fatal(err) + } + + t.Logf("PBM version=%s", pc.ServiceContent.AboutInfo.Version) + + rtype := types.PbmProfileResourceType{ + ResourceType: string(types.PbmProfileResourceTypeEnumSTORAGE), + } + + category := types.PbmProfileCategoryEnumREQUIREMENT + + ids, err := pc.QueryProfile(ctx, rtype, string(category)) + if err != nil { + t.Fatal(err) + } + + var qids []string + + for _, id := range ids { + qids = append(qids, id.UniqueId) + } + + var cids []string + + policies, err := pc.RetrieveContent(ctx, ids) + if err != nil { + t.Fatal(err) + } + + for i := range policies { + profile := policies[i].GetPbmProfile() + cids = append(cids, profile.ProfileId.UniqueId) + } + + sort.Strings(qids) + sort.Strings(cids) + + if !reflect.DeepEqual(qids, cids) { + t.Error("ids mismatch") + } + + root := c.ServiceContent.RootFolder + var datastores []vim.ManagedObjectReference + var kind []string + + if clusterName == "" { + kind = []string{"Datastore"} + } else { + kind = []string{"ClusterComputeResource"} + } + + m := view.NewManager(c.Client) + + v, err := m.CreateContainerView(ctx, root, kind, true) + if err != nil { + t.Fatal(err) + } + + if clusterName == "" { + datastores, err = v.Find(ctx, kind, nil) + if err != nil { + t.Fatal(err) + } + } else { + var cluster mo.ClusterComputeResource + + err = v.RetrieveWithFilter(ctx, kind, []string{"datastore"}, &cluster, property.Filter{"name": clusterName}) + if err != nil { + t.Fatal(err) + } + + datastores = cluster.Datastore + } + + _ = v.Destroy(ctx) + + t.Logf("checking %d datatores", len(datastores)) + + var hubs []types.PbmPlacementHub + + for _, ds := range datastores { + hubs = append(hubs, types.PbmPlacementHub{ + HubType: ds.Type, + HubId: ds.Value, + }) + } + + var req []types.BasePbmPlacementRequirement + + for _, id := range ids { + req = append(req, &types.PbmPlacementCapabilityProfileRequirement{ + ProfileId: id, + }) + } + + res, err := pc.CheckRequirements(ctx, hubs, nil, req) + if err != nil { + t.Fatal(err) + } + + t.Logf("%d results", len(res)) +} diff --git a/vim25/soap/client.go b/vim25/soap/client.go index e89ffc810..9ac3cc4d6 100644 --- a/vim25/soap/client.go +++ b/vim25/soap/client.go @@ -58,6 +58,10 @@ const ( DefaultMinVimVersion = "5.5" ) +type header struct { + Cookie string `xml:"vcSessionCookie,omitempty"` +} + type Client struct { http.Client @@ -73,6 +77,8 @@ type Client struct { Namespace string // Vim namespace Version string // Vim version UserAgent string + + header *header } var schemeMatch = regexp.MustCompile(`^\w+://`) @@ -147,6 +153,32 @@ func NewClient(u *url.URL, insecure bool) *Client { return &c } +// NewServiceClient creates a NewClient with the given URL.Path and namespace. +func (c *Client) NewServiceClient(path string, namespace string) *Client { + u := c.URL() + u.Path = path + + client := NewClient(u, c.k) + + client.Namespace = namespace + + // Copy the cookies + client.Client.Jar.SetCookies(u, c.Client.Jar.Cookies(u)) + + // Set SOAP Header cookie + for _, cookie := range client.Jar.Cookies(u) { + if cookie.Name == "vmware_soap_session" { + client.header = &header{ + Cookie: cookie.Value, + } + + break + } + } + + return client +} + // SetRootCAs defines the set of root certificate authorities // that clients use when verifying server certificates. // By default TLS uses the host's root CA set. @@ -401,6 +433,8 @@ func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error reqEnv := Envelope{Body: reqBody} resEnv := Envelope{Body: resBody} + reqEnv.Header = c.header + // Create debugging context for this round trip d := c.d.newRoundTrip() if d.enabled() { diff --git a/vim25/soap/soap.go b/vim25/soap/soap.go index ea35e77ad..a8baa0122 100644 --- a/vim25/soap/soap.go +++ b/vim25/soap/soap.go @@ -22,15 +22,11 @@ import ( ) type Envelope struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` - Header *Header `xml:",omitempty"` + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` + Header interface{} `xml:",omitempty"` Body interface{} } -type Header struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"` -} - type Fault struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"` Code string `xml:"faultcode"` diff --git a/vim25/soap/soap_test.go b/vim25/soap/soap_test.go index d90a17d19..44fa657bd 100644 --- a/vim25/soap/soap_test.go +++ b/vim25/soap/soap_test.go @@ -38,18 +38,22 @@ func TestEmptyEnvelope(t *testing.T) { } } -func TestEmptyHeader(t *testing.T) { - h := Header{} +func TestNonEmptyHeader(t *testing.T) { + env := Envelope{ + Header: struct { + Foo string + }{"bar"}, + } - b, err := xml.Marshal(h) + b, err := xml.Marshal(env) if err != nil { t.Errorf("error: %s", err) return } - expected := `
` - actual := string(b) - if expected != actual { - t.Fatalf("expected: %s, actual: %s", expected, actual) + env = Envelope{} + err = xml.Unmarshal(b, &env) + if err != nil { + t.Errorf("error: %s", err) } } diff --git a/vim25/types/registry.go b/vim25/types/registry.go index 8885dfbf6..ff7c302d3 100644 --- a/vim25/types/registry.go +++ b/vim25/types/registry.go @@ -16,7 +16,10 @@ limitations under the License. package types -import "reflect" +import ( + "reflect" + "strings" +) var t = map[string]reflect.Type{} @@ -29,6 +32,12 @@ type Func func(string) (reflect.Type, bool) func TypeFunc() Func { return func(name string) (reflect.Type, bool) { typ, ok := t[name] + if !ok { + // The /sdk endpoint does not prefix types with the namespace, + // but extension endpoints, such as /pbm/sdk do. + name = strings.TrimPrefix(name, "vim25:") + typ, ok = t[name] + } return typ, ok } }