package service import ( "context" "fmt" "strings" "esxlib/api/cloud" "esxlib/esx" "git.twelvetwelve.org/library/core/log" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func splitVmID(id string) (string, string) { vals := strings.SplitN(id, ":", 2) if len(vals) == 1 { return "", "" } // zone, id return vals[0], vals[1] } func vmToInfo(vm *esx.VirtualMachine, zoneId string) *cloud.VMInfo { info := &cloud.VMInfo{ Id: fmt.Sprintf("%s:%s", zoneId, vm.Id()), Name: vm.Name(), Zone: zoneId, } switch vm.State() { case esx.PowerStateOff: info.State = cloud.PowerState_OFF case esx.PowerStateOn: info.State = cloud.PowerState_ON case esx.PowerStateSuspended: info.State = cloud.PowerState_SUSPENDED case esx.PowerStateUndefined: info.State = cloud.PowerState_UNKNOWN } for _, vmNet := range vm.GetNetworkAddressV4() { info.Network = append(info.Network, &cloud.VMNetworkInfo{ Address: vmNet, }) } return info } func (cs *CloudServer) VMList(ctx context.Context, req *cloud.VMListRequest) (*cloud.VMListResponse, error) { resp := &cloud.VMListResponse{} for zoneId := range cs.esx { fmt.Printf("Loading zone: %s\n", zoneId) zone := cs.esx[zoneId] ls, err := zone.VirtualMachines.List(ctx) if err != nil { log.Errorf("VM from zone: %s failed err:%s\n", zoneId, err.Error()) return nil, err } for idx := range ls { resp.Vms = append(resp.Vms, vmToInfo(ls[idx], zoneId)) } } return resp, nil } func (cs *CloudServer) VMGet(ctx context.Context, req *cloud.VMGetRequest) (*cloud.VMGetResponse, error) { switch search := req.Search.(type) { case *cloud.VMGetRequest_Id: zoneId, vmId := splitVmID(search.Id) zone, ok := cs.esx[zoneId] if !ok { return nil, status.Errorf(codes.InvalidArgument, "invalid vm id") } vm, err := zone.VirtualMachines.GetById(ctx, vmId) if err != nil { return nil, err } resp := &cloud.VMGetResponse{ Vm: vmToInfo(vm, zoneId), } return resp, err } return nil, status.Errorf(codes.Unimplemented, "method VMGet not implemented") } func (cs *CloudServer) VMPower(ctx context.Context, req *cloud.VMPowerRequest) (*cloud.VMPowerResponse, error) { zoneId, vmId := splitVmID(req.Id) zone, ok := cs.esx[zoneId] if !ok { return nil, status.Errorf(codes.InvalidArgument, "invalid vm id") } vm, err := zone.VirtualMachines.GetById(ctx, vmId) if err != nil { return nil, err } switch req.State { case cloud.PowerState_OFF: err = vm.PowerOff(ctx) case cloud.PowerState_ON: err = vm.PowerOn(ctx) case cloud.PowerState_SUSPENDED: err = vm.Suspend(ctx) default: return nil, fmt.Errorf("unsupported") } if err != nil { return nil, err } return &cloud.VMPowerResponse{}, nil } func (cs *CloudServer) VMCreate(ctx context.Context, req *cloud.VMCreateRequest) (*cloud.VMCreateResponse, error) { zone, ok := cs.esx[req.ZoneId] if !ok { return nil, status.Errorf(codes.InvalidArgument, "invalid vm id") } var hosts string for idx := range req.AuthorizedHosts { hosts = hosts + req.AuthorizedHosts[idx] + "\n" } vm, err := zone.VirtualMachines.Create(ctx, req.Slug, req.Name, hosts, []byte(req.InitScript)) if err != nil { return nil, err } err = vm.PowerOn(ctx) if err != nil { log.Warnf("VM: %s:%s (%s) created but not powered on.", req.ZoneId, vm.Id(), vm.Name()) } _ = vm.Update() return &cloud.VMCreateResponse{ Vm: vmToInfo(vm, req.ZoneId), }, nil } func (cs *CloudServer) VMDestroy(ctx context.Context, req *cloud.VMDestroyRequest) (*cloud.VMDestroyResponse, error) { zoneId, vmId := splitVmID(req.Id) zone, ok := cs.esx[zoneId] if !ok { return nil, status.Errorf(codes.InvalidArgument, "invalid vm id") } log.Eventf("VM Destroy: %s\n", req.Id) err := zone.VirtualMachines.Destroy(ctx, vmId) if err != nil { return nil, err } log.Eventf("VM Destroy: %s OK\n", req.Id) return &cloud.VMDestroyResponse{}, nil } func (cs *CloudServer) VMListSlugs(ctx context.Context, req *cloud.VMListSlugsRequest) (*cloud.VMListSlugsResponse, error) { zone, ok := cs.config.Zones[req.ZoneId] if !ok { return nil, status.Errorf(codes.InvalidArgument, "invalid zone id") } resp := &cloud.VMListSlugsResponse{} for slugId, slug := range zone.HostDefaults.Slugs { resp.Slugs = append(resp.Slugs, &cloud.Slug{ Id: slugId, Name: slug.Image, Description: slug.Description, CpuCount: uint32(slug.CpuCount), MemoryMb: uint32(slug.MemoryMB), DiskGb: uint32(slug.DiskSizeGB), Cost: 0, }) } return resp, nil } func (cs *CloudServer) ZonesList(ctx context.Context, req *cloud.ZonesListRequest) (*cloud.ZonesListResponse, error) { resp := &cloud.ZonesListResponse{} for zoneId, zone := range cs.config.Zones { resp.Zones = append(resp.Zones, &cloud.Zone{ Id: zoneId, Name: zone.Name, Description: zone.Description, }) } return resp, nil } func (cs *CloudServer) NetworksList(ctx context.Context, req *cloud.NetworksListRequest) (*cloud.NetworksListResponse, error) { zone, ok := cs.esx[req.ZoneId] if !ok { return nil, status.Errorf(codes.InvalidArgument, "invalid zone id") } networks, err := zone.Networks.List(ctx) if err != nil { return nil, err } resp := &cloud.NetworksListResponse{} for _, net := range networks { resp.Networks = append(resp.Networks, &cloud.Network{ Id: net.Id, Name: net.Name, VLAN: net.VLAN, }) } return resp, err }