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

     1  /*
     2  Copyright (c) 2024-2024 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  	"fmt"
    21  	"net/http"
    22  	"net/url"
    23  	"strings"
    24  
    25  	"github.com/vmware/govmomi/simulator"
    26  	"github.com/vmware/govmomi/vapi/esx/settings/clusters"
    27  	"github.com/vmware/govmomi/vapi/esx/settings/depots"
    28  	vapi "github.com/vmware/govmomi/vapi/simulator"
    29  )
    30  
    31  func init() {
    32  	simulator.RegisterEndpoint(func(s *simulator.Service, r *simulator.Registry) {
    33  		New(s.Listen).Register(s, r)
    34  	})
    35  }
    36  
    37  type Handler struct {
    38  	URL *url.URL
    39  
    40  	Depots          map[string]depots.SettingsDepotsOfflineInfo
    41  	DepotComponents map[string]depots.SettingsDepotsOfflineContentInfo
    42  	BaseImages      []depots.BaseImagesSummary
    43  
    44  	SoftwareDrafts     map[string]clusters.SettingsClustersSoftwareDraftsMetadata
    45  	SoftwareComponents map[string]clusters.SettingsComponentInfo
    46  	ClusterImage       *clusters.SettingsBaseImageInfo
    47  
    48  	depotCounter int
    49  	draftCounter int
    50  
    51  	vlcmEnabled bool
    52  }
    53  
    54  // New creates a Handler instance
    55  func New(u *url.URL) *Handler {
    56  	return &Handler{
    57  		URL:                u,
    58  		Depots:             make(map[string]depots.SettingsDepotsOfflineInfo),
    59  		DepotComponents:    make(map[string]depots.SettingsDepotsOfflineContentInfo),
    60  		BaseImages:         createMockBaseImages(),
    61  		SoftwareDrafts:     make(map[string]clusters.SettingsClustersSoftwareDraftsMetadata),
    62  		SoftwareComponents: make(map[string]clusters.SettingsComponentInfo),
    63  		depotCounter:       0,
    64  		vlcmEnabled:        false,
    65  	}
    66  }
    67  
    68  func (h *Handler) Register(s *simulator.Service, r *simulator.Registry) {
    69  	if r.IsVPX() {
    70  		s.HandleFunc(depots.DepotsOfflinePath, h.depotsOffline)
    71  		s.HandleFunc(depots.DepotsOfflinePath+"/", h.depotsOffline)
    72  		s.HandleFunc(depots.BaseImagesPath, h.baseImages)
    73  		s.HandleFunc("/api/esx/settings/clusters/", h.clusters)
    74  	}
    75  }
    76  
    77  func (h *Handler) depotsOffline(w http.ResponseWriter, r *http.Request) {
    78  	subpath := r.URL.Path[len(depots.DepotsOfflinePath):]
    79  	segments := strings.Split(subpath, "/")
    80  
    81  	switch r.Method {
    82  	case http.MethodGet:
    83  		if len(segments) > 1 {
    84  			if res, contains := h.DepotComponents[segments[1]]; !contains {
    85  				vapi.ApiErrorNotFound(w)
    86  			} else if len(segments) > 2 && segments[2] == "content" {
    87  				vapi.StatusOK(w, res)
    88  			} else {
    89  				vapi.ApiErrorUnsupported(w)
    90  			}
    91  		}
    92  		vapi.StatusOK(w, h.Depots)
    93  	case http.MethodDelete:
    94  		if _, contains := h.Depots[segments[1]]; !contains {
    95  			vapi.ApiErrorNotFound(w)
    96  		} else {
    97  			delete(h.Depots, segments[1])
    98  			delete(h.DepotComponents, segments[1])
    99  			vapi.StatusOK(w, "")
   100  		}
   101  	case http.MethodPost:
   102  		var spec depots.SettingsDepotsOfflineCreateSpec
   103  		if vapi.Decode(r, w, &spec) {
   104  			// Create depot
   105  			depot := depots.SettingsDepotsOfflineInfo{}
   106  			depot.SourceType = spec.SourceType
   107  			depot.FileId = spec.FileId
   108  			depot.Location = spec.Location
   109  			depot.OwnerData = spec.OwnerData
   110  			depot.Description = spec.Description
   111  
   112  			h.depotCounter += 1
   113  			depotId := fmt.Sprintf("depot-%d", h.depotCounter)
   114  			h.Depots[depotId] = depot
   115  
   116  			// Generate content
   117  			content := depots.SettingsDepotsOfflineContentInfo{}
   118  			content.MetadataBundles = make(map[string][]depots.SettingsDepotsMetadataInfo)
   119  			content.MetadataBundles["dummy-content"] = make([]depots.SettingsDepotsMetadataInfo, 1)
   120  			bundle := depots.SettingsDepotsMetadataInfo{}
   121  			bundle.IndependentComponents = make(map[string]depots.SettingsDepotsComponentSummary)
   122  			independentComp := depots.SettingsDepotsComponentSummary{}
   123  			independentComp.Versions = make([]depots.ComponentVersion, 1)
   124  			independentComp.Versions[0].Version = "1.0.0"
   125  			independentComp.Versions[0].DisplayVersion = "1.0.0"
   126  			independentComp.DisplayName = "DummyComponent"
   127  			bundle.IndependentComponents["dummy-component"] = independentComp
   128  			content.MetadataBundles["dummy-content"][0] = bundle
   129  			h.DepotComponents[depotId] = content
   130  
   131  			vapi.StatusOK(w, fmt.Sprintf("depot-task-%d", h.depotCounter))
   132  		}
   133  	}
   134  }
   135  
   136  func (h *Handler) baseImages(w http.ResponseWriter, r *http.Request) {
   137  	switch r.Method {
   138  	case http.MethodGet:
   139  		vapi.StatusOK(w, h.BaseImages)
   140  	}
   141  }
   142  
   143  func (h *Handler) clusters(w http.ResponseWriter, r *http.Request) {
   144  	subpath := r.URL.Path[len("/api/esx/settings/clusters"):]
   145  	segments := strings.Split(subpath, "/")
   146  
   147  	if len(segments) > 3 && segments[2] == "software" && segments[3] == "drafts" {
   148  		segments = segments[4:]
   149  		if len(segments) > 2 && segments[1] == "software" && segments[2] == "components" {
   150  			h.clustersSoftwareDraftsComponents(w, r, segments)
   151  			return
   152  		} else if len(segments) > 2 && segments[1] == "software" && segments[2] == "base-image" {
   153  			h.clustersSoftwareDraftsBaseImage(w, r)
   154  			return
   155  		} else {
   156  			h.clustersSoftwareDrafts(w, r, segments)
   157  			return
   158  		}
   159  	} else if len(segments) > 3 && segments[2] == "enablement" && segments[3] == "software" {
   160  		h.clustersSoftwareEnablement(w, r)
   161  		return
   162  	}
   163  
   164  	vapi.ApiErrorUnsupported(w)
   165  }
   166  
   167  func (h *Handler) clustersSoftwareDrafts(w http.ResponseWriter, r *http.Request, subpath []string) {
   168  	var draftId *string
   169  	if len(subpath) > 0 {
   170  		draftId = &subpath[0]
   171  	}
   172  
   173  	switch r.Method {
   174  	case http.MethodGet:
   175  		if draftId != nil {
   176  			if draft, contains := h.SoftwareDrafts[*draftId]; !contains {
   177  				vapi.ApiErrorNotFound(w)
   178  				return
   179  			} else {
   180  				vapi.StatusOK(w, draft)
   181  			}
   182  		} else {
   183  			vapi.StatusOK(w, h.SoftwareDrafts)
   184  		}
   185  	case http.MethodDelete:
   186  		if draftId != nil {
   187  			if _, contains := h.SoftwareDrafts[*draftId]; !contains {
   188  				vapi.ApiErrorNotFound(w)
   189  				return
   190  			} else {
   191  				delete(h.SoftwareDrafts, *draftId)
   192  				vapi.StatusOK(w)
   193  			}
   194  		}
   195  	case http.MethodPost:
   196  		if strings.Contains(r.URL.RawQuery, "action=commit") {
   197  			if draftId != nil {
   198  				if _, contains := h.SoftwareDrafts[*draftId]; !contains {
   199  					vapi.ApiErrorNotFound(w)
   200  					return
   201  				} else {
   202  					delete(h.SoftwareDrafts, *draftId)
   203  					vapi.StatusOK(w)
   204  				}
   205  			}
   206  		}
   207  		// Only one active draft is permitted
   208  		if len(h.SoftwareDrafts) > 0 {
   209  			vapi.ApiErrorNotAllowedInCurrentState(w)
   210  			return
   211  		}
   212  
   213  		h.draftCounter += 1
   214  		draft := clusters.SettingsClustersSoftwareDraftsMetadata{}
   215  		newDraftId := fmt.Sprintf("%d", h.draftCounter)
   216  		h.SoftwareDrafts[newDraftId] = draft
   217  		vapi.StatusOK(w, newDraftId)
   218  	}
   219  }
   220  
   221  func (h *Handler) clustersSoftwareDraftsComponents(w http.ResponseWriter, r *http.Request, subpath []string) {
   222  	switch r.Method {
   223  	case http.MethodGet:
   224  		if len(subpath) > 3 {
   225  			if comp, contains := h.SoftwareComponents[subpath[3]]; contains {
   226  				vapi.StatusOK(w, comp)
   227  			} else {
   228  				vapi.ApiErrorNotFound(w)
   229  			}
   230  		} else {
   231  			vapi.StatusOK(w, h.SoftwareComponents)
   232  		}
   233  	case http.MethodDelete:
   234  		if len(subpath) > 3 {
   235  			compId := subpath[3]
   236  			if comp, contains := h.SoftwareComponents[compId]; contains {
   237  				delete(h.SoftwareComponents, compId)
   238  				vapi.StatusOK(w, comp)
   239  			} else {
   240  				vapi.ApiErrorNotFound(w)
   241  			}
   242  		}
   243  	case http.MethodPatch:
   244  		var spec clusters.SoftwareComponentsUpdateSpec
   245  		if vapi.Decode(r, w, &spec) {
   246  			for k, v := range spec.ComponentsToSet {
   247  				h.SoftwareComponents[k] = clusters.SettingsComponentInfo{
   248  					Version: v,
   249  					Details: clusters.SettingsComponentDetails{DisplayName: "DummyComponent"},
   250  				}
   251  			}
   252  		}
   253  	}
   254  }
   255  
   256  func (h *Handler) clustersSoftwareDraftsBaseImage(w http.ResponseWriter, r *http.Request) {
   257  	switch r.Method {
   258  	case http.MethodGet:
   259  		vapi.StatusOK(w, h.ClusterImage)
   260  	case http.MethodPut:
   261  		var spec clusters.SettingsBaseImageSpec
   262  		if vapi.Decode(r, w, &spec) {
   263  			h.ClusterImage = &clusters.SettingsBaseImageInfo{Version: spec.Version}
   264  			vapi.StatusOK(w)
   265  		} else {
   266  			vapi.ApiErrorInvalidArgument(w)
   267  		}
   268  	}
   269  }
   270  
   271  func (h *Handler) clustersSoftwareEnablement(w http.ResponseWriter, r *http.Request) {
   272  	switch r.Method {
   273  	case http.MethodGet:
   274  		vapi.StatusOK(w, clusters.SoftwareManagementInfo{Enabled: h.vlcmEnabled})
   275  	case http.MethodPut:
   276  		h.vlcmEnabled = true
   277  		vapi.StatusOK(w)
   278  	}
   279  }
   280  
   281  func createMockBaseImages() []depots.BaseImagesSummary {
   282  	baseImage := depots.BaseImagesSummary{
   283  		DisplayName:    "DummyImage",
   284  		DisplayVersion: "0.0.1",
   285  		Version:        "0.0.1",
   286  	}
   287  	return []depots.BaseImagesSummary{baseImage}
   288  }