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

     1  /*
     2  Copyright (c) 2017 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"
    22  	"os"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/vmware/govmomi/simulator/esx"
    27  	"github.com/vmware/govmomi/vim25/methods"
    28  	"github.com/vmware/govmomi/vim25/mo"
    29  	"github.com/vmware/govmomi/vim25/soap"
    30  	"github.com/vmware/govmomi/vim25/types"
    31  )
    32  
    33  var (
    34  	hostPortUnique = os.Getenv("VCSIM_HOST_PORT_UNIQUE") == "true"
    35  
    36  	globalLock sync.Mutex
    37  	// globalHostCount is used to construct unique hostnames. Should be consumed under globalLock.
    38  	globalHostCount = 0
    39  )
    40  
    41  type HostSystem struct {
    42  	mo.HostSystem
    43  
    44  	sh *simHost
    45  }
    46  
    47  func asHostSystemMO(obj mo.Reference) (*mo.HostSystem, bool) {
    48  	h, ok := getManagedObject(obj).Addr().Interface().(*mo.HostSystem)
    49  	return h, ok
    50  }
    51  
    52  func NewHostSystem(host mo.HostSystem) *HostSystem {
    53  	if hostPortUnique { // configure unique port for each host
    54  		port := &esx.HostSystem.Summary.Config.Port
    55  		*port++
    56  		host.Summary.Config.Port = *port
    57  	}
    58  
    59  	now := time.Now()
    60  
    61  	hs := &HostSystem{
    62  		HostSystem: host,
    63  	}
    64  
    65  	hs.Name = hs.Summary.Config.Name
    66  	hs.Summary.Runtime = &hs.Runtime
    67  	hs.Summary.Runtime.BootTime = &now
    68  
    69  	// shallow copy Summary.Hardware, as each host will be assigned its own .Uuid
    70  	hardware := *host.Summary.Hardware
    71  	hs.Summary.Hardware = &hardware
    72  
    73  	if hs.Hardware == nil {
    74  		// shallow copy Hardware, as each host will be assigned its own .Uuid
    75  		info := *esx.HostHardwareInfo
    76  		hs.Hardware = &info
    77  	}
    78  
    79  	cfg := new(types.HostConfigInfo)
    80  	deepCopy(hs.Config, cfg)
    81  	hs.Config = cfg
    82  
    83  	// copy over the reference advanced options so each host can have it's own, allowing hosts to be configured for
    84  	// container backing individually
    85  	deepCopy(esx.AdvancedOptions, &cfg.Option)
    86  
    87  	// add a supported option to the AdvancedOption manager
    88  	simOption := types.OptionDef{ElementDescription: types.ElementDescription{Key: advOptContainerBackingImage}}
    89  	// TODO: how do we enter patterns here? Or should we stick to a list in the value?
    90  	// patterns become necessary if we want to enforce correctness on options for RUN.underlay.<pnic> or allow RUN.port.xxx
    91  	hs.Config.OptionDef = append(hs.Config.OptionDef, simOption)
    92  
    93  	config := []struct {
    94  		ref **types.ManagedObjectReference
    95  		obj mo.Reference
    96  	}{
    97  		{&hs.ConfigManager.DatastoreSystem, &HostDatastoreSystem{Host: &hs.HostSystem}},
    98  		{&hs.ConfigManager.NetworkSystem, NewHostNetworkSystem(&hs.HostSystem)},
    99  		{&hs.ConfigManager.AdvancedOption, NewOptionManager(nil, nil, &hs.Config.Option)},
   100  		{&hs.ConfigManager.FirewallSystem, NewHostFirewallSystem(&hs.HostSystem)},
   101  		{&hs.ConfigManager.StorageSystem, NewHostStorageSystem(&hs.HostSystem)},
   102  	}
   103  
   104  	for _, c := range config {
   105  		ref := Map.Put(c.obj).Reference()
   106  
   107  		*c.ref = &ref
   108  	}
   109  
   110  	return hs
   111  }
   112  
   113  func (h *HostSystem) configure(ctx *Context, spec types.HostConnectSpec, connected bool) {
   114  	h.Runtime.ConnectionState = types.HostSystemConnectionStateDisconnected
   115  	if connected {
   116  		h.Runtime.ConnectionState = types.HostSystemConnectionStateConnected
   117  	}
   118  
   119  	// lets us construct non-conflicting hostname automatically if omitted
   120  	// does not use the unique port instead to avoid constraints on port, such as >1024
   121  
   122  	globalLock.Lock()
   123  	instanceID := globalHostCount
   124  	globalHostCount++
   125  	globalLock.Unlock()
   126  
   127  	if spec.HostName == "" {
   128  		spec.HostName = fmt.Sprintf("esx-%d", instanceID)
   129  	} else if net.ParseIP(spec.HostName) != nil {
   130  		h.Config.Network.Vnic[0].Spec.Ip.IpAddress = spec.HostName
   131  	}
   132  
   133  	h.Summary.Config.Name = spec.HostName
   134  	h.Name = h.Summary.Config.Name
   135  	id := newUUID(h.Name)
   136  	h.Summary.Hardware.Uuid = id
   137  	h.Hardware.SystemInfo.Uuid = id
   138  
   139  	var err error
   140  	h.sh, err = createSimulationHost(ctx, h)
   141  	if err != nil {
   142  		panic("failed to create simulation host and no path to return error: " + err.Error())
   143  	}
   144  }
   145  
   146  // configureContainerBacking sets up _this_ host for simulation using a container backing.
   147  // Args:
   148  //
   149  //		image - the container image with which to simulate the host
   150  //		mounts - array of mount info that should be translated into /vmfs/volumes/... mounts backed by container volumes
   151  //	 	networks - names of bridges to use for underlays. Will create a pNIC for each. The first will be treated as the management network.
   152  //
   153  // Restrictions adopted from createSimulationHost:
   154  // * no mock of VLAN connectivity
   155  // * only a single vmknic, used for "the management IP"
   156  // * pNIC connectivity does not directly impact VMs/vmks using it as uplink
   157  //
   158  // The pnics will be named using standard pattern, ie. vmnic0, vmnic1, ...
   159  // This will sanity check the NetConfig for "management" nicType to ensure that it maps through PortGroup->vSwitch->pNIC to vmnic0.
   160  func (h *HostSystem) configureContainerBacking(ctx *Context, image string, mounts []types.HostFileSystemMountInfo, networks ...string) error {
   161  	option := &types.OptionValue{
   162  		Key:   advOptContainerBackingImage,
   163  		Value: image,
   164  	}
   165  
   166  	advOpts := ctx.Map.Get(h.ConfigManager.AdvancedOption.Reference()).(*OptionManager)
   167  	fault := advOpts.UpdateOptions(&types.UpdateOptions{ChangedValue: []types.BaseOptionValue{option}}).Fault()
   168  	if fault != nil {
   169  		panic(fault)
   170  	}
   171  
   172  	h.Config.FileSystemVolume = nil
   173  	if mounts != nil {
   174  		h.Config.FileSystemVolume = &types.HostFileSystemVolumeInfo{
   175  			VolumeTypeList: []string{"VMFS", "OTHER"},
   176  			MountInfo:      mounts,
   177  		}
   178  	}
   179  
   180  	// force at least a management network
   181  	if len(networks) == 0 {
   182  		networks = []string{defaultUnderlayBridgeName}
   183  	}
   184  
   185  	// purge pNICs from the template - it makes no sense to keep them for a sim host
   186  	h.Config.Network.Pnic = make([]types.PhysicalNic, len(networks))
   187  
   188  	// purge any IPs and MACs associated with existing NetConfigs for the host
   189  	for cfgIdx := range h.Config.VirtualNicManagerInfo.NetConfig {
   190  		config := &h.Config.VirtualNicManagerInfo.NetConfig[cfgIdx]
   191  		for candidateIdx := range config.CandidateVnic {
   192  			candidate := &config.CandidateVnic[candidateIdx]
   193  			candidate.Spec.Ip.IpAddress = "0.0.0.0"
   194  			candidate.Spec.Ip.SubnetMask = "0.0.0.0"
   195  			candidate.Spec.Mac = "00:00:00:00:00:00"
   196  		}
   197  	}
   198  
   199  	// The presence of a pNIC is used to indicate connectivity to a specific underlay. We construct an empty pNIC entry and specify the underly via
   200  	// host.ConfigManager.AdvancedOptions. The pNIC will be populated with the MAC (accurate) and IP (divergence - we need to stash it somewhere) for the veth.
   201  	// We create a NetConfig "management" entry for the first pNIC - this will be populated with the IP of the "host" container.
   202  
   203  	// create a pNIC for each underlay
   204  	for i, net := range networks {
   205  		name := fmt.Sprintf("vmnic%d", i)
   206  
   207  		// we don't have a natural field for annotating which pNIC is connected to which network, so stash it in an adv option.
   208  		option := &types.OptionValue{
   209  			Key:   advOptPrefixPnicToUnderlayPrefix + name,
   210  			Value: net,
   211  		}
   212  		fault = advOpts.UpdateOptions(&types.UpdateOptions{ChangedValue: []types.BaseOptionValue{option}}).Fault()
   213  		if fault != nil {
   214  			panic(fault)
   215  		}
   216  
   217  		h.Config.Network.Pnic[i] = types.PhysicalNic{
   218  			Key:             "key-vim.host.PhysicalNic-" + name,
   219  			Device:          name,
   220  			Pci:             fmt.Sprintf("0000:%2d:00.0", i+1),
   221  			Driver:          "vcsim-bridge",
   222  			DriverVersion:   "1.2.10.0",
   223  			FirmwareVersion: "1.57, 0x80000185",
   224  			LinkSpeed: &types.PhysicalNicLinkInfo{
   225  				SpeedMb: 10000,
   226  				Duplex:  true,
   227  			},
   228  			ValidLinkSpecification: []types.PhysicalNicLinkInfo{
   229  				{
   230  					SpeedMb: 10000,
   231  					Duplex:  true,
   232  				},
   233  			},
   234  			Spec: types.PhysicalNicSpec{
   235  				Ip:                            &types.HostIpConfig{},
   236  				LinkSpeed:                     (*types.PhysicalNicLinkInfo)(nil),
   237  				EnableEnhancedNetworkingStack: types.NewBool(false),
   238  				EnsInterruptEnabled:           types.NewBool(false),
   239  			},
   240  			WakeOnLanSupported: false,
   241  			Mac:                "00:00:00:00:00:00",
   242  			FcoeConfiguration: &types.FcoeConfig{
   243  				PriorityClass: 3,
   244  				SourceMac:     "00:00:00:00:00:00",
   245  				VlanRange: []types.FcoeConfigVlanRange{
   246  					{},
   247  				},
   248  				Capabilities: types.FcoeConfigFcoeCapabilities{},
   249  				FcoeActive:   false,
   250  			},
   251  			VmDirectPathGen2Supported:             types.NewBool(false),
   252  			VmDirectPathGen2SupportedMode:         "",
   253  			ResourcePoolSchedulerAllowed:          types.NewBool(false),
   254  			ResourcePoolSchedulerDisallowedReason: nil,
   255  			AutoNegotiateSupported:                types.NewBool(true),
   256  			EnhancedNetworkingStackSupported:      types.NewBool(false),
   257  			EnsInterruptSupported:                 types.NewBool(false),
   258  			RdmaDevice:                            "",
   259  			DpuId:                                 "",
   260  		}
   261  	}
   262  
   263  	// sanity check that everything's hung together sufficiently well
   264  	details, err := h.getNetConfigInterface(ctx, "management")
   265  	if err != nil {
   266  		return err
   267  	}
   268  
   269  	if details.uplink == nil || details.uplink.Device != "vmnic0" {
   270  		return fmt.Errorf("Config provided for host %s does not result in a consistent 'management' NetConfig that's bound to 'vmnic0'", h.Name)
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  // netConfigDetails is used to packaged up all the related network entities associated with a NetConfig binding
   277  type netConfigDetails struct {
   278  	nicType   string
   279  	netconfig *types.VirtualNicManagerNetConfig
   280  	vmk       *types.HostVirtualNic
   281  	netstack  *types.HostNetStackInstance
   282  	portgroup *types.HostPortGroup
   283  	vswitch   *types.HostVirtualSwitch
   284  	uplink    *types.PhysicalNic
   285  }
   286  
   287  // getNetConfigInterface returns the set of constructs active for a given nicType (eg. "management", "vmotion")
   288  // This method is provided because the Config structure held by HostSystem is heavily interconnected but serialized and not cross-linked with pointers.
   289  // As such there's a _lot_ of cross-referencing that needs to be done to navigate.
   290  // The pNIC returned is the uplink associated with the vSwitch for the netconfig
   291  func (h *HostSystem) getNetConfigInterface(ctx *Context, nicType string) (*netConfigDetails, error) {
   292  	details := &netConfigDetails{
   293  		nicType: nicType,
   294  	}
   295  
   296  	for i := range h.Config.VirtualNicManagerInfo.NetConfig {
   297  		if h.Config.VirtualNicManagerInfo.NetConfig[i].NicType == nicType {
   298  			details.netconfig = &h.Config.VirtualNicManagerInfo.NetConfig[i]
   299  			break
   300  		}
   301  	}
   302  	if details.netconfig == nil {
   303  		return nil, fmt.Errorf("no matching NetConfig for NicType=%s", nicType)
   304  	}
   305  
   306  	if details.netconfig.SelectedVnic == nil {
   307  		return details, nil
   308  	}
   309  
   310  	vnicKey := details.netconfig.SelectedVnic[0]
   311  	for i := range details.netconfig.CandidateVnic {
   312  		if details.netconfig.CandidateVnic[i].Key == vnicKey {
   313  			details.vmk = &details.netconfig.CandidateVnic[i]
   314  			break
   315  		}
   316  	}
   317  	if details.vmk == nil {
   318  		panic(fmt.Sprintf("NetConfig for host %s references non-existant vNIC key %s for %s nicType", h.Name, vnicKey, nicType))
   319  	}
   320  
   321  	portgroupName := details.vmk.Portgroup
   322  	netstackKey := details.vmk.Spec.NetStackInstanceKey
   323  
   324  	for i := range h.Config.Network.NetStackInstance {
   325  		if h.Config.Network.NetStackInstance[i].Key == netstackKey {
   326  			details.netstack = &h.Config.Network.NetStackInstance[i]
   327  			break
   328  		}
   329  	}
   330  	if details.netstack == nil {
   331  		panic(fmt.Sprintf("NetConfig for host %s references non-existant NetStack key %s for %s nicType", h.Name, netstackKey, nicType))
   332  	}
   333  
   334  	for i := range h.Config.Network.Portgroup {
   335  		// TODO: confirm correctness of this - seems weird it references the Spec.Name instead of the key like everything else.
   336  		if h.Config.Network.Portgroup[i].Spec.Name == portgroupName {
   337  			details.portgroup = &h.Config.Network.Portgroup[i]
   338  			break
   339  		}
   340  	}
   341  	if details.portgroup == nil {
   342  		panic(fmt.Sprintf("NetConfig for host %s references non-existant PortGroup name %s for %s nicType", h.Name, portgroupName, nicType))
   343  	}
   344  
   345  	vswitchKey := details.portgroup.Vswitch
   346  	for i := range h.Config.Network.Vswitch {
   347  		if h.Config.Network.Vswitch[i].Key == vswitchKey {
   348  			details.vswitch = &h.Config.Network.Vswitch[i]
   349  			break
   350  		}
   351  	}
   352  	if details.vswitch == nil {
   353  		panic(fmt.Sprintf("NetConfig for host %s references non-existant vSwitch key %s for %s nicType", h.Name, vswitchKey, nicType))
   354  	}
   355  
   356  	if len(details.vswitch.Pnic) != 1 {
   357  		// to change this, look at the Active NIC in the NicTeamingPolicy, but for now not worth it
   358  		panic(fmt.Sprintf("vSwitch %s for host %s has multiple pNICs associated which is not supported.", vswitchKey, h.Name))
   359  	}
   360  
   361  	pnicKey := details.vswitch.Pnic[0]
   362  	for i := range h.Config.Network.Pnic {
   363  		if h.Config.Network.Pnic[i].Key == pnicKey {
   364  			details.uplink = &h.Config.Network.Pnic[i]
   365  			break
   366  		}
   367  	}
   368  	if details.uplink == nil {
   369  		panic(fmt.Sprintf("NetConfig for host %s references non-existant pNIC key %s for %s nicType", h.Name, pnicKey, nicType))
   370  	}
   371  
   372  	return details, nil
   373  }
   374  
   375  func (h *HostSystem) event() types.HostEvent {
   376  	return types.HostEvent{
   377  		Event: types.Event{
   378  			Datacenter:      datacenterEventArgument(h),
   379  			ComputeResource: h.eventArgumentParent(),
   380  			Host:            h.eventArgument(),
   381  		},
   382  	}
   383  }
   384  
   385  func (h *HostSystem) eventArgument() *types.HostEventArgument {
   386  	return &types.HostEventArgument{
   387  		Host:                h.Self,
   388  		EntityEventArgument: types.EntityEventArgument{Name: h.Name},
   389  	}
   390  }
   391  
   392  func (h *HostSystem) eventArgumentParent() *types.ComputeResourceEventArgument {
   393  	parent := hostParent(&h.HostSystem)
   394  
   395  	return &types.ComputeResourceEventArgument{
   396  		ComputeResource:     parent.Self,
   397  		EntityEventArgument: types.EntityEventArgument{Name: parent.Name},
   398  	}
   399  }
   400  
   401  func hostParent(host *mo.HostSystem) *mo.ComputeResource {
   402  	switch parent := Map.Get(*host.Parent).(type) {
   403  	case *mo.ComputeResource:
   404  		return parent
   405  	case *ClusterComputeResource:
   406  		return &parent.ComputeResource
   407  	default:
   408  		return nil
   409  	}
   410  }
   411  
   412  func addComputeResource(s *types.ComputeResourceSummary, h *HostSystem) {
   413  	s.TotalCpu += h.Summary.Hardware.CpuMhz
   414  	s.TotalMemory += h.Summary.Hardware.MemorySize
   415  	s.NumCpuCores += h.Summary.Hardware.NumCpuCores
   416  	s.NumCpuThreads += h.Summary.Hardware.NumCpuThreads
   417  	s.EffectiveCpu += h.Summary.Hardware.CpuMhz
   418  	s.EffectiveMemory += h.Summary.Hardware.MemorySize
   419  	s.NumHosts++
   420  	s.NumEffectiveHosts++
   421  	s.OverallStatus = types.ManagedEntityStatusGreen
   422  }
   423  
   424  // CreateDefaultESX creates a standalone ESX
   425  // Adds objects of type: Datacenter, Network, ComputeResource, ResourcePool and HostSystem
   426  func CreateDefaultESX(ctx *Context, f *Folder) {
   427  	dc := NewDatacenter(ctx, &f.Folder)
   428  
   429  	host := NewHostSystem(esx.HostSystem)
   430  
   431  	summary := new(types.ComputeResourceSummary)
   432  	addComputeResource(summary, host)
   433  
   434  	cr := &mo.ComputeResource{
   435  		Summary: summary,
   436  		Network: esx.Datacenter.Network,
   437  	}
   438  	cr.Self = *host.Parent
   439  	cr.Name = host.Name
   440  	cr.Host = append(cr.Host, host.Reference())
   441  	host.Network = cr.Network
   442  	ctx.Map.PutEntity(cr, host)
   443  	cr.EnvironmentBrowser = newEnvironmentBrowser(ctx, host.Reference())
   444  
   445  	pool := NewResourcePool()
   446  	cr.ResourcePool = &pool.Self
   447  	ctx.Map.PutEntity(cr, pool)
   448  	pool.Owner = cr.Self
   449  
   450  	folderPutChild(ctx, &ctx.Map.Get(dc.HostFolder).(*Folder).Folder, cr)
   451  }
   452  
   453  // CreateStandaloneHost uses esx.HostSystem as a template, applying the given spec
   454  // and creating the ComputeResource parent and ResourcePool sibling.
   455  func CreateStandaloneHost(ctx *Context, f *Folder, spec types.HostConnectSpec) (*HostSystem, types.BaseMethodFault) {
   456  	if spec.HostName == "" {
   457  		return nil, &types.NoHost{}
   458  	}
   459  
   460  	template := esx.HostSystem
   461  	network := ctx.Map.getEntityDatacenter(f).defaultNetwork()
   462  
   463  	if p := ctx.Map.FindByName(spec.UserName, f.ChildEntity); p != nil {
   464  		cr := p.(*mo.ComputeResource)
   465  		h := ctx.Map.Get(cr.Host[0])
   466  		// "clone" an existing host from the inventory
   467  		template = h.(*HostSystem).HostSystem
   468  		template.Vm = nil
   469  		network = cr.Network
   470  	}
   471  
   472  	pool := NewResourcePool()
   473  	host := NewHostSystem(template)
   474  	host.configure(ctx, spec, false)
   475  
   476  	summary := new(types.ComputeResourceSummary)
   477  	addComputeResource(summary, host)
   478  
   479  	cr := &mo.ComputeResource{
   480  		ConfigurationEx: &types.ComputeResourceConfigInfo{
   481  			VmSwapPlacement: string(types.VirtualMachineConfigInfoSwapPlacementTypeVmDirectory),
   482  		},
   483  		Summary: summary,
   484  	}
   485  
   486  	ctx.Map.PutEntity(cr, ctx.Map.NewEntity(host))
   487  	cr.EnvironmentBrowser = newEnvironmentBrowser(ctx, host.Reference())
   488  
   489  	host.Summary.Host = &host.Self
   490  	host.Config.Host = host.Self
   491  
   492  	ctx.Map.PutEntity(cr, ctx.Map.NewEntity(pool))
   493  
   494  	cr.Name = host.Name
   495  	cr.Network = network
   496  	cr.Host = append(cr.Host, host.Reference())
   497  	cr.ResourcePool = &pool.Self
   498  
   499  	folderPutChild(ctx, &f.Folder, cr)
   500  	pool.Owner = cr.Self
   501  	host.Network = cr.Network
   502  
   503  	return host, nil
   504  }
   505  
   506  func (h *HostSystem) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
   507  	task := CreateTask(h, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
   508  		if len(h.Vm) > 0 {
   509  			return nil, &types.ResourceInUse{}
   510  		}
   511  
   512  		ctx.postEvent(&types.HostRemovedEvent{HostEvent: h.event()})
   513  
   514  		f := ctx.Map.getEntityParent(h, "Folder").(*Folder)
   515  		folderRemoveChild(ctx, &f.Folder, h.Reference())
   516  		err := h.sh.remove(ctx)
   517  
   518  		if err != nil {
   519  			return nil, &types.RuntimeFault{
   520  				MethodFault: types.MethodFault{
   521  					FaultCause: &types.LocalizedMethodFault{
   522  						Fault:            &types.SystemErrorFault{Reason: err.Error()},
   523  						LocalizedMessage: err.Error()}}}
   524  		}
   525  
   526  		// TODO: should there be events on lifecycle operations as with VMs?
   527  
   528  		return nil, nil
   529  	})
   530  
   531  	return &methods.Destroy_TaskBody{
   532  		Res: &types.Destroy_TaskResponse{
   533  			Returnval: task.Run(ctx),
   534  		},
   535  	}
   536  }
   537  
   538  func (h *HostSystem) EnterMaintenanceModeTask(ctx *Context, spec *types.EnterMaintenanceMode_Task) soap.HasFault {
   539  	task := CreateTask(h, "enterMaintenanceMode", func(t *Task) (types.AnyType, types.BaseMethodFault) {
   540  		h.Runtime.InMaintenanceMode = true
   541  		return nil, nil
   542  	})
   543  
   544  	return &methods.EnterMaintenanceMode_TaskBody{
   545  		Res: &types.EnterMaintenanceMode_TaskResponse{
   546  			Returnval: task.Run(ctx),
   547  		},
   548  	}
   549  }
   550  
   551  func (h *HostSystem) ExitMaintenanceModeTask(ctx *Context, spec *types.ExitMaintenanceMode_Task) soap.HasFault {
   552  	task := CreateTask(h, "exitMaintenanceMode", func(t *Task) (types.AnyType, types.BaseMethodFault) {
   553  		h.Runtime.InMaintenanceMode = false
   554  		return nil, nil
   555  	})
   556  
   557  	return &methods.ExitMaintenanceMode_TaskBody{
   558  		Res: &types.ExitMaintenanceMode_TaskResponse{
   559  			Returnval: task.Run(ctx),
   560  		},
   561  	}
   562  }
   563  
   564  func (h *HostSystem) DisconnectHostTask(ctx *Context, spec *types.DisconnectHost_Task) soap.HasFault {
   565  	task := CreateTask(h, "disconnectHost", func(t *Task) (types.AnyType, types.BaseMethodFault) {
   566  		h.Runtime.ConnectionState = types.HostSystemConnectionStateDisconnected
   567  		return nil, nil
   568  	})
   569  
   570  	return &methods.DisconnectHost_TaskBody{
   571  		Res: &types.DisconnectHost_TaskResponse{
   572  			Returnval: task.Run(ctx),
   573  		},
   574  	}
   575  }
   576  
   577  func (h *HostSystem) ReconnectHostTask(ctx *Context, spec *types.ReconnectHost_Task) soap.HasFault {
   578  	task := CreateTask(h, "reconnectHost", func(t *Task) (types.AnyType, types.BaseMethodFault) {
   579  		h.Runtime.ConnectionState = types.HostSystemConnectionStateConnected
   580  		return nil, nil
   581  	})
   582  
   583  	return &methods.ReconnectHost_TaskBody{
   584  		Res: &types.ReconnectHost_TaskResponse{
   585  			Returnval: task.Run(ctx),
   586  		},
   587  	}
   588  }