Repository: vmware/govmomi
Pull request #2668 (2021-11-24 12:57:53)

govc: add support for supervisor services deploy
Signed-off-by: Ricardo Katz <rkatz@vmware.com>

## Description

Initial work on supporting vSphere Namespaces supervisor services.

Right now it's possible to:
* List
* Enable
* Disable
* Remove
* Create


TODO: Write VCSIM things

## Type of change

Please mark options that are relevant:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to
  not work as expected)
- [ ] This change requires a documentation update
- [ ] Build related change

## How Has This Been Tested?

Tested with a real cluster. Still will check to write some unit tests

## Checklist:

- [ ] My code follows the CONTRIBUTION [guidelines](./../CONTRIBUTING.md) of
  this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged

rikatz: @dougm thanks for the review :) and sorry for the delay, a lot of other stuff ongoing here :/

Hopefully this is right now and all the tests will pass as well :)
embano1: Thx @rikatz ! Once the review is done, can you please squash your commits and follow our contribution guidelines in terms of title and message body (reference issue) so your work renders nicely in the CHANGELOG?

Patch:
diff --git a/govc/USAGE.md b/govc/USAGE.md
index c3f001f1..81898b3e 100644
--- a/govc/USAGE.md
+++ b/govc/USAGE.md
@@ -231,6 +231,12 @@ but appear via `govc $cmd -h`:
  - [namespace.cluster.enable](#namespaceclusterenable)
  - [namespace.cluster.ls](#namespaceclusterls)
  - [namespace.logs.download](#namespacelogsdownload)
+ - [namespace.service.activate](#namespaceserviceactivate)
+ - [namespace.service.create](#namespaceservicecreate)
+ - [namespace.service.deactivate](#namespaceservicedeactivate)
+ - [namespace.service.info](#namespaceserviceinfo)
+ - [namespace.service.ls](#namespaceservicels)
+ - [namespace.service.rm](#namespaceservicerm)
  - [object.collect](#objectcollect)
  - [object.destroy](#objectdestroy)
  - [object.method](#objectmethod)
@@ -3788,6 +3794,88 @@ Options:
   -cluster=              Cluster [GOVC_CLUSTER]
 ```
 
+## namespace.service.activate
+
+```
+Usage: govc namespace.service.activate [OPTIONS] NAME...
+
+Activates a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.activate my-supervisor-service other-supervisor-service
+
+Options:
+```
+
+## namespace.service.create
+
+```
+Usage: govc namespace.service.create [OPTIONS] MANIFEST
+
+Creates a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.create manifest.yaml
+
+Options:
+```
+
+## namespace.service.deactivate
+
+```
+Usage: govc namespace.service.deactivate [OPTIONS] NAME...
+
+Deactivates a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.deactivate my-supervisor-service other-supervisor-service
+
+Options:
+```
+
+## namespace.service.info
+
+```
+Usage: govc namespace.service.info [OPTIONS] NAME
+
+Gets information of a specific supervisor service.
+
+Examples:
+  govc namespace.service.info my-supervisor-service
+  govc namespace.service.info -json my-supervisor-service | jq .
+
+Options:
+```
+
+## namespace.service.ls
+
+```
+Usage: govc namespace.service.ls [OPTIONS]
+
+List namepace registered supervisor services.
+
+Examples:
+  govc namespace.service.ls
+  govc namespace.service.ls -l
+  govc namespace.service.ls -json | jq .
+
+Options:
+  -l=false               Long listing format
+```
+
+## namespace.service.rm
+
+```
+Usage: govc namespace.service.rm [OPTIONS] NAME...
+
+Removes a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.rm my-supervisor-service other-supervisor-service
+
+Options:
+```
+
 ## object.collect
 
 ```
diff --git a/govc/main.go b/govc/main.go
index a78a6856..3a9e0436 100644
--- a/govc/main.go
+++ b/govc/main.go
@@ -72,6 +72,7 @@ import (
 	_ "github.com/vmware/govmomi/govc/metric"
 	_ "github.com/vmware/govmomi/govc/metric/interval"
 	_ "github.com/vmware/govmomi/govc/namespace/cluster"
+	_ "github.com/vmware/govmomi/govc/namespace/service"
 	_ "github.com/vmware/govmomi/govc/object"
 	_ "github.com/vmware/govmomi/govc/option"
 	_ "github.com/vmware/govmomi/govc/permissions"
diff --git a/govc/namespace/service/activate.go b/govc/namespace/service/activate.go
new file mode 100644
index 00000000..5de9e114
--- /dev/null
+++ b/govc/namespace/service/activate.go
@@ -0,0 +1,71 @@
+/*
+Copyright (c) 2021 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 service
+
+import (
+	"context"
+	"flag"
+
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vapi/namespace"
+)
+
+type activate struct {
+	*flags.ClientFlag
+}
+
+func init() {
+	cli.Register("namespace.service.activate", &activate{})
+}
+
+func (cmd *activate) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+}
+
+func (cmd *activate) Description() string {
+	return `Activates a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.activate my-supervisor-service other-supervisor-service`
+}
+
+func (cmd *activate) Usage() string {
+	return "NAME..."
+}
+
+func (cmd *activate) Run(ctx context.Context, f *flag.FlagSet) error {
+	services := f.Args()
+	if len(services) < 1 {
+		return flag.ErrHelp
+	}
+
+	c, err := cmd.RestClient()
+	if err != nil {
+		return err
+	}
+
+	m := namespace.NewManager(c)
+	for _, svc := range services {
+		if err := m.ActivateSupervisorServices(ctx, svc); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/govc/namespace/service/create.go b/govc/namespace/service/create.go
new file mode 100644
index 00000000..beae49e0
--- /dev/null
+++ b/govc/namespace/service/create.go
@@ -0,0 +1,98 @@
+/*
+Copyright (c) 2021 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 service
+
+import (
+	"context"
+	"encoding/base64"
+	"flag"
+	"fmt"
+	"io/ioutil"
+
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vapi/namespace"
+)
+
+type create struct {
+	*flags.ClientFlag
+
+	specType        string
+	trustedProvider bool
+	acceptEULA      bool
+}
+
+func init() {
+	cli.Register("namespace.service.create", &create{})
+}
+
+func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+
+	flag.StringVar(&cmd.specType, "spec-type", "vsphere", "Type of Spec: only vsphere is supported right now")
+	flag.BoolVar(&cmd.trustedProvider, "trusted", false, "Define if this is a trusted provider")
+	flag.BoolVar(&cmd.acceptEULA, "accept-eula", false, "Auto accept EULA")
+
+}
+
+func (cmd *create) Description() string {
+	return `Creates a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.create manifest.yaml`
+}
+
+func (cmd *create) Usage() string {
+	return "MANIFEST"
+}
+
+func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error {
+	manifest := f.Args()
+	if len(manifest) != 1 {
+		return flag.ErrHelp
+	}
+
+	if cmd.specType != "vsphere" {
+		return fmt.Errorf("only vsphere specs are accepted right now")
+	}
+
+	manifestFile, err := ioutil.ReadFile(manifest[0])
+	if err != nil {
+		return fmt.Errorf("failed to read manifest file: %s", err)
+	}
+
+	content := base64.StdEncoding.EncodeToString(manifestFile)
+	service := namespace.SupervisorService{
+		VsphereService: namespace.SupervisorServicesVSphereSpec{
+			VersionSpec: namespace.SupervisorServicesVSphereVersionCreateSpec{
+				Content:         content,
+				AcceptEula:      cmd.acceptEULA,
+				TrustedProvider: cmd.trustedProvider,
+			},
+		},
+	}
+
+	c, err := cmd.RestClient()
+	if err != nil {
+		return err
+	}
+
+	m := namespace.NewManager(c)
+	return m.CreateSupervisorService(ctx, &service)
+
+}
diff --git a/govc/namespace/service/deactivate.go b/govc/namespace/service/deactivate.go
new file mode 100644
index 00000000..5e460139
--- /dev/null
+++ b/govc/namespace/service/deactivate.go
@@ -0,0 +1,72 @@
+/*
+Copyright (c) 2021 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 service
+
+import (
+	"context"
+	"flag"
+
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vapi/namespace"
+)
+
+type deactivate struct {
+	*flags.ClientFlag
+}
+
+func init() {
+	cli.Register("namespace.service.deactivate", &deactivate{})
+}
+
+func (cmd *deactivate) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+}
+
+func (cmd *deactivate) Description() string {
+	return `Deactivates a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.deactivate my-supervisor-service other-supervisor-service`
+}
+
+func (cmd *deactivate) Usage() string {
+	return "NAME..."
+}
+
+func (cmd *deactivate) Run(ctx context.Context, f *flag.FlagSet) error {
+
+	services := f.Args()
+	if len(services) < 1 {
+		return flag.ErrHelp
+	}
+
+	c, err := cmd.RestClient()
+	if err != nil {
+		return err
+	}
+
+	m := namespace.NewManager(c)
+	for _, svc := range services {
+		if err := m.DeactivateSupervisorServices(ctx, svc); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/govc/namespace/service/info.go b/govc/namespace/service/info.go
new file mode 100644
index 00000000..d48266ea
--- /dev/null
+++ b/govc/namespace/service/info.go
@@ -0,0 +1,112 @@
+/*
+Copyright (c) 2021 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 service
+
+import (
+	"context"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"text/tabwriter"
+
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vapi/namespace"
+)
+
+type info struct {
+	*flags.ClientFlag
+	*flags.OutputFlag
+}
+
+func init() {
+	cli.Register("namespace.service.info", &info{})
+}
+
+func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+	cmd.OutputFlag.Register(ctx, f)
+
+}
+
+func (cmd *info) Process(ctx context.Context) error {
+	if err := cmd.ClientFlag.Process(ctx); err != nil {
+		return err
+	}
+	return cmd.OutputFlag.Process(ctx)
+}
+
+func (cmd *info) Description() string {
+	return `Gets information of a specific supervisor service.
+
+Examples:
+  govc namespace.service.info my-supervisor-service
+  govc namespace.service.info -json my-supervisor-service | jq .`
+}
+
+type infoWriter struct {
+	cmd     *info
+	Service namespace.SupervisorServiceInfo
+}
+
+func (r *infoWriter) Write(w io.Writer) error {
+	tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
+
+	fmt.Fprintf(tw, "%s", r.Service.Name)
+	fmt.Fprintf(tw, "\t%s", r.Service.State)
+	fmt.Fprintf(tw, "\t%s", r.Service.Description)
+
+	fmt.Fprintf(tw, "\n")
+
+	return tw.Flush()
+}
+
+func (r *infoWriter) MarshalJSON() ([]byte, error) {
+	return json.Marshal(r.Service)
+}
+
+func (r *infoWriter) Dump() interface{} {
+	return r.Service
+}
+
+func (cmd *info) Usage() string {
+	return "NAME"
+}
+
+func (cmd *info) Run(ctx context.Context, f *flag.FlagSet) error {
+	service := f.Args()
+	if len(service) != 1 {
+		return flag.ErrHelp
+	}
+
+	c, err := cmd.RestClient()
+	if err != nil {
+		return err
+	}
+
+	m := namespace.NewManager(c)
+	supervisorservice, err := m.GetSupervisorService(ctx, service[0])
+	if err != nil {
+		return err
+	}
+
+	return cmd.WriteResult(&infoWriter{cmd, supervisorservice})
+}
diff --git a/govc/namespace/service/ls.go b/govc/namespace/service/ls.go
new file mode 100644
index 00000000..a0c3cf74
--- /dev/null
+++ b/govc/namespace/service/ls.go
@@ -0,0 +1,109 @@
+/*
+Copyright (c) 2021 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 service
+
+import (
+	"context"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"text/tabwriter"
+
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vapi/namespace"
+)
+
+type ls struct {
+	*flags.ClientFlag
+	*flags.OutputFlag
+
+	long bool
+}
+
+func init() {
+	cli.Register("namespace.service.ls", &ls{})
+}
+
+func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+	cmd.OutputFlag.Register(ctx, f)
+
+	f.BoolVar(&cmd.long, "l", false, "Long listing format")
+}
+
+func (cmd *ls) Process(ctx context.Context) error {
+	if err := cmd.ClientFlag.Process(ctx); err != nil {
+		return err
+	}
+	return cmd.OutputFlag.Process(ctx)
+}
+
+func (cmd *ls) Description() string {
+	return `List namepace registered supervisor services.
+
+Examples:
+  govc namespace.service.ls
+  govc namespace.service.ls -l
+  govc namespace.service.ls -json | jq .`
+}
+
+type lsWriter struct {
+	cmd     *ls
+	Service []namespace.SupervisorServiceSummary
+}
+
+func (r *lsWriter) Write(w io.Writer) error {
+	tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
+
+	for _, svc := range r.Service {
+		fmt.Fprintf(tw, "%s", svc.ID)
+		if r.cmd.long {
+			fmt.Fprintf(tw, "\t%s", svc.State)
+			fmt.Fprintf(tw, "\t%s", svc.Name)
+		}
+		fmt.Fprintf(tw, "\n")
+	}
+	return tw.Flush()
+}
+
+func (r *lsWriter) MarshalJSON() ([]byte, error) {
+	return json.Marshal(r.Service)
+}
+
+func (r *lsWriter) Dump() interface{} {
+	return r.Service
+}
+
+func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {
+	c, err := cmd.RestClient()
+	if err != nil {
+		return err
+	}
+
+	m := namespace.NewManager(c)
+	supervisorservices, err := m.ListSupervisorServices(ctx)
+	if err != nil {
+		return err
+	}
+
+	return cmd.WriteResult(&lsWriter{cmd, supervisorservices})
+}
diff --git a/govc/namespace/service/rm.go b/govc/namespace/service/rm.go
new file mode 100644
index 00000000..66aa0aa3
--- /dev/null
+++ b/govc/namespace/service/rm.go
@@ -0,0 +1,73 @@
+/*
+Copyright (c) 2021 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 service
+
+import (
+	"context"
+	"flag"
+	"fmt"
+
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vapi/namespace"
+)
+
+type rm struct {
+	*flags.ClientFlag
+}
+
+func init() {
+	cli.Register("namespace.service.rm", &rm{})
+}
+
+func (cmd *rm) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+}
+
+func (cmd *rm) Description() string {
+	return `Removes a vSphere Namespace Supervisor Service.
+
+Examples:
+  govc namespace.service.rm my-supervisor-service other-supervisor-service`
+}
+
+func (cmd *rm) Usage() string {
+	return "NAME..."
+}
+
+func (cmd *rm) Run(ctx context.Context, f *flag.FlagSet) error {
+
+	services := f.Args()
+	if len(services) < 1 {
+		return fmt.Errorf("at least one service must be passed as argument")
+	}
+
+	c, err := cmd.RestClient()
+	if err != nil {
+		return err
+	}
+
+	m := namespace.NewManager(c)
+	for _, svc := range services {
+
+		if err := m.RemoveSupervisorService(ctx, svc); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/vapi/namespace/internal/internal.go b/vapi/namespace/internal/internal.go
index 32d436e9..b84e192c 100644
--- a/vapi/namespace/internal/internal.go
+++ b/vapi/namespace/internal/internal.go
@@ -21,6 +21,7 @@ const (
 	NamespaceClusterPath                    = "/api/vcenter/namespace-management/clusters"
 	NamespaceDistributedSwitchCompatibility = "/api/vcenter/namespace-management/distributed-switch-compatibility"
 	NamespaceEdgeClusterCompatibility       = "/api/vcenter/namespace-management/edge-cluster-compatibility"
+	SupervisorServicesPath                  = "/api/vcenter/namespace-management/supervisor-services"
 )
 
 type SupportBundleToken struct {
diff --git a/vapi/namespace/simulator/simulator.go b/vapi/namespace/simulator/simulator.go
index fa24fd27..fc773928 100644
--- a/vapi/namespace/simulator/simulator.go
+++ b/vapi/namespace/simulator/simulator.go
@@ -64,6 +64,10 @@ func (h *Handler) Register(s *simulator.Service, r *simulator.Registry) {
 		s.HandleFunc(internal.NamespaceClusterPath+"/", h.clustersID)
 		s.HandleFunc(internal.NamespaceDistributedSwitchCompatibility+"/", h.listCompatibleDistributedSwitches)
 		s.HandleFunc(internal.NamespaceEdgeClusterCompatibility+"/", h.listCompatibleEdgeClusters)
+
+		s.HandleFunc(internal.SupervisorServicesPath, h.listServices)
+		s.HandleFunc(internal.SupervisorServicesPath+"/", h.getService)
+
 	}
 }
 
@@ -197,3 +201,42 @@ func (h *Handler) listCompatibleEdgeClusters(w http.ResponseWriter, r *http.Requ
 		vapi.StatusOK(w, switches)
 	}
 }
+
+var supervisorServices []namespace.SupervisorServiceSummary = []namespace.SupervisorServiceSummary{
+	{
+		ID:    "service1",
+		Name:  "mock-service-1",
+		State: "ACTIVATED",
+	},
+	{
+		ID:    "service2",
+		Name:  "mock-service-2",
+		State: "DE-ACTIVATED",
+	},
+}
+
+func (h *Handler) listServices(w http.ResponseWriter, r *http.Request) {
+	switch r.Method {
+	case http.MethodGet:
+		vapi.StatusOK(w, supervisorServices)
+	}
+}
+
+func (h *Handler) getService(w http.ResponseWriter, r *http.Request) {
+	id := path.Base(r.URL.Path)
+	switch r.Method {
+	case http.MethodGet:
+		for _, svc := range supervisorServices {
+			if svc.ID == id {
+				svcInfo := namespace.SupervisorServiceInfo{
+					Name:        svc.Name,
+					State:       svc.State,
+					Description: fmt.Sprintf("Description of %s", svc.ID),
+				}
+				vapi.StatusOK(w, svcInfo)
+				return
+			}
+		}
+		vapi.Status(w, http.StatusNotFound)
+	}
+}
diff --git a/vapi/namespace/supervisorsvc.go b/vapi/namespace/supervisorsvc.go
new file mode 100644
index 00000000..2292647a
--- /dev/null
+++ b/vapi/namespace/supervisorsvc.go
@@ -0,0 +1,97 @@
+/*
+Copyright (c) 2021 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 namespace
+
+import (
+	"context"
+	"net/http"
+	"path"
+
+	"github.com/vmware/govmomi/vapi/namespace/internal"
+)
+
+// SupervisorServiceSummary for a supervisor service existent in vSphere.
+type SupervisorServiceSummary struct {
+	ID    string `json:"supervisor_service"`
+	Name  string `json:"display_name"`
+	State string `json:"state"`
+}
+
+// SupervisorServiceInfo for a supervisor service existent in vSphere.
+type SupervisorServiceInfo struct {
+	Name        string `json:"display_name"`
+	State       string `json:"state"`
+	Description string `json:"description"`
+}
+
+// SupervisorService defines a new SupervisorService specification
+type SupervisorService struct {
+	VsphereService SupervisorServicesVSphereSpec `json:"vsphere_spec,omitempty"`
+}
+
+// SupervisorServicesVSphereSpec defines a new SupervisorService specification of vSphere type
+type SupervisorServicesVSphereSpec struct {
+	VersionSpec SupervisorServicesVSphereVersionCreateSpec `json:"version_spec"`
+}
+
+// SupervisorServicesVSphereVersionCreateSpec defines a new SupervisorService specification for vSphere
+type SupervisorServicesVSphereVersionCreateSpec struct {
+	Content         string `json:"content"`
+	TrustedProvider bool   `json:"trusted_provider,omitempty"`
+	AcceptEula      bool   `json:"accept_EULA,omitempty"`
+}
+
+// CreateSupervisorService creates a new Supervisor Service on vSphere Namespaces endpoint.
+func (c *Manager) CreateSupervisorService(ctx context.Context, service *SupervisorService) error {
+	url := c.Resource(internal.SupervisorServicesPath)
+	return c.Do(ctx, url.Request(http.MethodPost, service), nil)
+}
+
+// ListSupervisorServices returns a summary of all clusters with vSphere Namespaces enabled.
+func (c *Manager) ListSupervisorServices(ctx context.Context) ([]SupervisorServiceSummary, error) {
+	var res []SupervisorServiceSummary
+	url := c.Resource(internal.SupervisorServicesPath)
+	return res, c.Do(ctx, url.Request(http.MethodGet), &res)
+}
+
+// GetSupervisorService gets the information of a specific supervisor service.
+func (c *Manager) GetSupervisorService(ctx context.Context, id string) (SupervisorServiceInfo, error) {
+	var res SupervisorServiceInfo
+	url := c.Resource(path.Join(internal.SupervisorServicesPath, id))
+	return res, c.Do(ctx, url.Request(http.MethodGet, nil), &res)
+}
+
+// ActivateSupervisorServices activates a previously registered Supervisor Service.
+func (c *Manager) ActivateSupervisorServices(ctx context.Context, id string) error {
+	url := c.Resource(path.Join(internal.SupervisorServicesPath, id)).WithParam("action", "activate")
+	err := c.Do(ctx, url.Request(http.MethodPatch, nil), nil)
+	return err
+}
+
+// DeactivateSupervisorServices deactivates a previously registered Supervisor Service.
+func (c *Manager) DeactivateSupervisorServices(ctx context.Context, id string) error {
+	url := c.Resource(path.Join(internal.SupervisorServicesPath, id)).WithParam("action", "deactivate")
+	err := c.Do(ctx, url.Request(http.MethodPatch, nil), nil)
+	return err
+}
+
+// RemoveSupervisorService removes a previously deactivated supervisor service.
+func (c *Manager) RemoveSupervisorService(ctx context.Context, id string) error {
+	url := c.Resource(path.Join(internal.SupervisorServicesPath, id))
+	err := c.Do(ctx, url.Request(http.MethodDelete, nil), nil)
+	return err
+}
diff --git a/vapi/simulator/simulator.go b/vapi/simulator/simulator.go
index f9a535ee..07e72dee 100644
--- a/vapi/simulator/simulator.go
+++ b/vapi/simulator/simulator.go
@@ -291,10 +291,10 @@ func (s *handler) DetachTag(id vim.ManagedObjectReference, tag vim.VslmTagEntry)
 	return nil
 }
 
-// StatusOK responds with http.StatusOK and json encoded val if given.
+// Status responds with custom HTTP status and json encoded val if given.
 // For use with "/api" endpoints.
-func StatusOK(w http.ResponseWriter, val ...interface{}) {
-	w.WriteHeader(http.StatusOK)
+func Status(w http.ResponseWriter, httpStatus int, val ...interface{}) {
+	w.WriteHeader(httpStatus)
 	if len(val) == 0 {
 		return
 	}
@@ -306,6 +306,12 @@ func StatusOK(w http.ResponseWriter, val ...interface{}) {
 	}
 }
 
+// StatusOK responds with http.StatusOK and json encoded val if given.
+// For use with "/api" endpoints.
+func StatusOK(w http.ResponseWriter, val ...interface{}) {
+	Status(w, http.StatusOK, val...)
+}
+
 // OK responds with http.StatusOK and json encoded val if given.
 // For use with "/rest" endpoints where the response is a "value" wrapped structure.
 func OK(w http.ResponseWriter, val ...interface{}) {


Test patch:
diff --git a/govc/test/namespace.bats b/govc/test/namespace.bats
index 219ba59d..a2187456 100755
--- a/govc/test/namespace.bats
+++ b/govc/test/namespace.bats
@@ -86,3 +86,31 @@ load test_helper
 
   govc namespace.logs.download -cluster DC0_C0 - | tar -xvOf-
 }
+
+@test "namespace.service.ls" {
+    vcsim_env
+
+    run govc namespace.service.ls
+    assert_success
+    assert_matches service1
+    assert_matches service2
+
+    run govc namespace.service.ls -l
+    assert_success
+    assert_matches ACTIVATED
+    assert_matches mock-service-1
+}
+
+@test "namespace.service.info" {
+    vcsim_env
+
+    run govc namespace.service.info service1
+    assert_success
+    assert_matches mock-service-1
+    assert_matches ACTIVATED
+    assert_matches "Description of service1"
+
+    run govc namespace.service.info -json service2
+    assert_matches DE-ACTIVATED
+
+}
\ No newline at end of file