github.com/vmware/govmomi@v0.37.2/vapi/namespace/simulator/simulator.go (about)

     1  /*
     2  Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package simulator
    18  
    19  import (
    20  	"archive/tar"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"path"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/google/uuid"
    31  
    32  	"github.com/vmware/govmomi"
    33  	"github.com/vmware/govmomi/property"
    34  	"github.com/vmware/govmomi/simulator"
    35  	"github.com/vmware/govmomi/vapi/namespace"
    36  	vapi "github.com/vmware/govmomi/vapi/simulator"
    37  	"github.com/vmware/govmomi/view"
    38  	"github.com/vmware/govmomi/vim25/types"
    39  
    40  	"github.com/vmware/govmomi/vapi/namespace/internal"
    41  )
    42  
    43  func init() {
    44  	simulator.RegisterEndpoint(func(s *simulator.Service, r *simulator.Registry) {
    45  		New(s.Listen).Register(s, r)
    46  	})
    47  }
    48  
    49  // Handler implements the Cluster Modules API simulator
    50  type Handler struct {
    51  	URL *url.URL
    52  }
    53  
    54  // New creates a Handler instance
    55  func New(u *url.URL) *Handler {
    56  	return &Handler{
    57  		URL: u,
    58  	}
    59  }
    60  
    61  // Register Namespace Management API paths with the vapi simulator's http.ServeMux
    62  func (h *Handler) Register(s *simulator.Service, r *simulator.Registry) {
    63  	if r.IsVPX() {
    64  		s.HandleFunc(internal.NamespacesPath, h.namespaces)
    65  		s.HandleFunc(internal.NamespacesPath+"/", h.namespaces)
    66  		s.HandleFunc(internal.NamespaceClusterPath, h.clusters)
    67  		s.HandleFunc(internal.NamespaceClusterPath+"/", h.clustersID)
    68  		s.HandleFunc(internal.NamespaceDistributedSwitchCompatibility+"/", h.listCompatibleDistributedSwitches)
    69  		s.HandleFunc(internal.NamespaceEdgeClusterCompatibility+"/", h.listCompatibleEdgeClusters)
    70  
    71  		s.HandleFunc(internal.SupervisorServicesPath, h.listServices)
    72  		s.HandleFunc(internal.SupervisorServicesPath+"/", h.getService)
    73  
    74  		s.HandleFunc(internal.VmClassesPath, h.vmClasses)
    75  		s.HandleFunc(internal.VmClassesPath+"/", h.vmClasses)
    76  	}
    77  }
    78  
    79  // enabledClusters returns refs for cluster names with a "WCP-" prefix.
    80  // Using the name as a simple hack until we add support for enabling via the API.
    81  func enabledClusters(c *govmomi.Client) ([]types.ManagedObjectReference, error) {
    82  	ctx := context.Background()
    83  	kind := []string{"ClusterComputeResource"}
    84  
    85  	m := view.NewManager(c.Client)
    86  	v, err := m.CreateContainerView(ctx, c.ServiceContent.RootFolder, kind, true)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	defer func() { _ = v.Destroy(ctx) }()
    91  
    92  	return v.Find(ctx, kind, property.Match{"name": "WCP-*"})
    93  }
    94  
    95  func (h *Handler) clusters(w http.ResponseWriter, r *http.Request) {
    96  	c, err := govmomi.NewClient(context.Background(), h.URL, true)
    97  	if err != nil {
    98  		panic(err)
    99  	}
   100  
   101  	switch r.Method {
   102  	case http.MethodGet:
   103  		refs, err := enabledClusters(c)
   104  		if err != nil {
   105  			panic(err)
   106  		}
   107  
   108  		clusters := make([]namespace.ClusterSummary, len(refs))
   109  		for i, ref := range refs {
   110  			clusters[i] = namespace.ClusterSummary{
   111  				ID:               ref.Value,
   112  				ConfigStatus:     &namespace.RunningConfigStatus,
   113  				KubernetesStatus: &namespace.ReadyKubernetesStatus,
   114  			}
   115  		}
   116  		vapi.StatusOK(w, clusters)
   117  	}
   118  }
   119  
   120  func (h *Handler) clustersSupportBundle(w http.ResponseWriter, r *http.Request) {
   121  	var token internal.SupportBundleToken
   122  	_ = json.NewDecoder(r.Body).Decode(&token)
   123  	_ = r.Body.Close()
   124  
   125  	if token.Value == "" {
   126  		u := *h.URL
   127  		u.Path = r.URL.Path
   128  		// Create support bundle request
   129  		location := namespace.SupportBundleLocation{
   130  			Token: namespace.SupportBundleToken{
   131  				Token: uuid.New().String(),
   132  			},
   133  			URL: u.String(),
   134  		}
   135  
   136  		vapi.StatusOK(w, &location)
   137  		return
   138  	}
   139  
   140  	// Get support bundle
   141  	id := path.Base(path.Dir(r.URL.Path))
   142  	name := fmt.Sprintf("wcp-support-bundle-%s-%s--00-00.tar", id, time.Now().Format("2006Jan02"))
   143  
   144  	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name))
   145  	w.Header().Set("Content-Type", "application/octet-stream")
   146  
   147  	readme := "vcsim generated support bundle"
   148  	tw := tar.NewWriter(w)
   149  	_ = tw.WriteHeader(&tar.Header{
   150  		Name:    "README",
   151  		Size:    int64(len(readme) + 1),
   152  		Mode:    0444,
   153  		ModTime: time.Now(),
   154  	})
   155  	_, _ = fmt.Fprintln(tw, readme)
   156  	_ = tw.Close()
   157  }
   158  
   159  func (h *Handler) clustersID(w http.ResponseWriter, r *http.Request) {
   160  	id := path.Base(r.URL.Path)
   161  	route := map[string]func(http.ResponseWriter, *http.Request){
   162  		"support-bundle": h.clustersSupportBundle,
   163  	}[id]
   164  
   165  	if route != nil {
   166  		route(w, r)
   167  		return
   168  	}
   169  
   170  	// TODO:
   171  	// https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.vcenter.namespace_management.clusters
   172  }
   173  
   174  func (h *Handler) listCompatibleDistributedSwitches(w http.ResponseWriter, r *http.Request) {
   175  	switch r.Method {
   176  	case http.MethodGet:
   177  
   178  		// normally expect to get exactly one result back
   179  		switches := []namespace.DistributedSwitchCompatibilitySummary{
   180  			{
   181  				Compatible:        true,
   182  				DistributedSwitch: "Compatible-DVS-1",
   183  			},
   184  		}
   185  		vapi.StatusOK(w, switches)
   186  	}
   187  }
   188  
   189  func (h *Handler) listCompatibleEdgeClusters(w http.ResponseWriter, r *http.Request) {
   190  	switch r.Method {
   191  	case http.MethodGet:
   192  
   193  		// CLI is able to filter in case we get multiple results
   194  		switches := []namespace.EdgeClusterCompatibilitySummary{
   195  			{
   196  				Compatible:  true,
   197  				EdgeCluster: "Compat-Edge-ID1",
   198  				DisplayName: "Edge-Cluster-1",
   199  			},
   200  			{
   201  				Compatible:  true,
   202  				EdgeCluster: "Compat-Edge-ID2",
   203  				DisplayName: "Edge-Cluster-2",
   204  			},
   205  		}
   206  		vapi.StatusOK(w, switches)
   207  	}
   208  }
   209  
   210  var supervisorServices []namespace.SupervisorServiceSummary = []namespace.SupervisorServiceSummary{
   211  	{
   212  		ID:    "service1",
   213  		Name:  "mock-service-1",
   214  		State: "ACTIVATED",
   215  	},
   216  	{
   217  		ID:    "service2",
   218  		Name:  "mock-service-2",
   219  		State: "DE-ACTIVATED",
   220  	},
   221  }
   222  
   223  func (h *Handler) listServices(w http.ResponseWriter, r *http.Request) {
   224  	switch r.Method {
   225  	case http.MethodGet:
   226  		vapi.StatusOK(w, supervisorServices)
   227  	}
   228  }
   229  
   230  func (h *Handler) getService(w http.ResponseWriter, r *http.Request) {
   231  	id := path.Base(r.URL.Path)
   232  	switch r.Method {
   233  	case http.MethodGet:
   234  		for _, svc := range supervisorServices {
   235  			if svc.ID == id {
   236  				svcInfo := namespace.SupervisorServiceInfo{
   237  					Name:        svc.Name,
   238  					State:       svc.State,
   239  					Description: fmt.Sprintf("Description of %s", svc.ID),
   240  				}
   241  				vapi.StatusOK(w, svcInfo)
   242  				return
   243  			}
   244  		}
   245  		w.WriteHeader(http.StatusNotFound)
   246  	}
   247  }
   248  
   249  var namespacesMap = make(map[string]*namespace.NamespacesInstanceInfo)
   250  
   251  func (h *Handler) namespaces(w http.ResponseWriter, r *http.Request) {
   252  	subpath := r.URL.Path[len(internal.NamespacesPath):]
   253  	subpath = strings.Replace(subpath, "/", "", -1)
   254  
   255  	switch r.Method {
   256  	case http.MethodGet:
   257  		if len(subpath) > 0 {
   258  			if result, contains := namespacesMap[subpath]; contains {
   259  				vapi.StatusOK(w, result)
   260  			} else {
   261  				vapi.ApiErrorNotFound(w)
   262  			}
   263  			return
   264  		} else {
   265  			result := make([]namespace.NamespacesInstanceSummary, 0, len(namespacesMap))
   266  
   267  			for k, v := range namespacesMap {
   268  				entry := namespace.NamespacesInstanceSummary{
   269  					ClusterId:    v.ClusterId,
   270  					Namespace:    k,
   271  					ConfigStatus: v.ConfigStatus,
   272  					Description:  v.Description,
   273  					Stats:        v.Stats,
   274  				}
   275  				result = append(result, entry)
   276  			}
   277  
   278  			vapi.StatusOK(w, result)
   279  		}
   280  	case http.MethodPatch:
   281  		if len(subpath) > 0 {
   282  			if entry, contains := namespacesMap[subpath]; contains {
   283  				var spec namespace.NamespacesInstanceUpdateSpec
   284  				if vapi.Decode(r, w, &spec) {
   285  					entry.VmServiceSpec = spec.VmServiceSpec
   286  					vapi.StatusOK(w)
   287  				}
   288  			}
   289  		}
   290  
   291  		vapi.ApiErrorNotFound(w)
   292  	case http.MethodPost:
   293  		var spec namespace.NamespacesInstanceCreateSpec
   294  		if !vapi.Decode(r, w, &spec) {
   295  			return
   296  		}
   297  
   298  		newNamespace := namespace.NamespacesInstanceInfo{
   299  			ClusterId:     spec.Cluster,
   300  			ConfigStatus:  namespace.RunningConfigStatus.String(),
   301  			VmServiceSpec: spec.VmServiceSpec,
   302  		}
   303  
   304  		namespacesMap[spec.Namespace] = &newNamespace
   305  
   306  		vapi.StatusOK(w)
   307  	case http.MethodDelete:
   308  		if len(subpath) > 0 {
   309  			if _, contains := namespacesMap[subpath]; contains {
   310  				delete(namespacesMap, subpath)
   311  				vapi.StatusOK(w)
   312  				return
   313  			}
   314  		}
   315  		vapi.ApiErrorNotFound(w)
   316  	}
   317  }
   318  
   319  var vmClassesMap = make(map[string]*namespace.VirtualMachineClassInfo)
   320  
   321  func (h *Handler) vmClasses(w http.ResponseWriter, r *http.Request) {
   322  	subpath := r.URL.Path[len(internal.VmClassesPath):]
   323  	subpath = strings.Replace(subpath, "/", "", -1)
   324  
   325  	switch r.Method {
   326  	case http.MethodGet:
   327  		if len(subpath) > 0 {
   328  			if result, contains := vmClassesMap[subpath]; contains {
   329  				vapi.StatusOK(w, result)
   330  			} else {
   331  				vapi.ApiErrorNotFound(w)
   332  			}
   333  			return
   334  		} else {
   335  			result := make([]*namespace.VirtualMachineClassInfo, 0, len(vmClassesMap))
   336  
   337  			for _, v := range vmClassesMap {
   338  				result = append(result, v)
   339  			}
   340  
   341  			vapi.StatusOK(w, result)
   342  		}
   343  	case http.MethodPatch:
   344  		if len(subpath) > 0 {
   345  			if entry, contains := vmClassesMap[subpath]; contains {
   346  				var spec namespace.VirtualMachineClassUpdateSpec
   347  				if !vapi.Decode(r, w, &spec) {
   348  					return
   349  				}
   350  
   351  				entry.CpuCount = spec.CpuCount
   352  				entry.MemoryMb = spec.MemoryMb
   353  				entry.CpuReservation = spec.CpuReservation
   354  				entry.MemoryReservation = spec.MemoryReservation
   355  				entry.Devices = spec.Devices
   356  
   357  				vapi.StatusOK(w)
   358  				return
   359  			}
   360  		}
   361  
   362  		vapi.ApiErrorNotFound(w)
   363  	case http.MethodPost:
   364  		var spec namespace.VirtualMachineClassCreateSpec
   365  		if !vapi.Decode(r, w, &spec) {
   366  			return
   367  		}
   368  
   369  		newClass := namespace.VirtualMachineClassInfo{
   370  			Id:                spec.Id,
   371  			CpuCount:          spec.CpuCount,
   372  			MemoryMb:          spec.MemoryMb,
   373  			MemoryReservation: spec.MemoryReservation,
   374  			CpuReservation:    spec.CpuReservation,
   375  			Devices:           spec.Devices,
   376  		}
   377  
   378  		vmClassesMap[spec.Id] = &newClass
   379  
   380  		vapi.StatusOK(w)
   381  	case http.MethodDelete:
   382  		if len(subpath) > 0 {
   383  			if _, contains := vmClassesMap[subpath]; contains {
   384  				delete(vmClassesMap, subpath)
   385  				vapi.StatusOK(w)
   386  				return
   387  			}
   388  		}
   389  		vapi.ApiErrorNotFound(w)
   390  	}
   391  }