Сбор перечня виртуальных машин
Данная операция необходима для проверки доступности гипервизора, а так же для получения общих данных при заведении оборудования.
Обзор операции
Данная операция реализуется следующим RPC:
// Сервис HypervisorManager реализует логику взаимодействия и сбора информации с гипервизоров
service HypervisorManager {
// Получение данных для заведения гипервизора
rpc CollectHypervisorInitialContent(CollectHypervisorInitialContentRequest) returns (CollectHypervisorInitialContentResponse);
}
/**
* Контракт запроса для rpc CollectHypervisorInitialContent
*/
message CollectHypervisorInitialContentRequest {
// Входные данные для сбора информации
DeviceInitialInput device_input = 1;
}
/**
* Контракт ответа для rpc CollectHypervisorInitialContent
*/
message CollectHypervisorInitialContentResponse {
// Инфомрация, необходимая при заведении
DeviceInitialContent content = 2;
}
С полной структурой данных вы можете ознакомиться в прото-файлах.
Пример реализации
Реализация операции будет производиться на ESXI версии 7.0 запущенной на оборудовании Gagar>n oracul gen. 1.
Дополняем уже имеющийся шаблон реализацией RPC CollectHypervisorInitialContent.
В ходе обработки запроса на языке golang для гипервизора ESXI мы воспользуемся библиотекой govmomi для взаимодействия с VMware vSphere APIs и сбора информации с гипервизора.
Данный подход основан на примере.
Для начала необходимо получить данные для подключения к гипервизору по протоколу Esxi:
creds, err := findCreds(req.DeviceInput.Credentials, pb.InitialProtocol_INITIAL_PROTOCOL_ESXI)
if err != nil {
return nil, fmt.Errorf("find creds: %s", err)
}
Функция для получения данных подключения:
func findCreds(in []*pb.InitialCredential, protocol pb.InitialProtocol) (*pb.InitialCredential, error) {
if len(in) == 0 {
return nil, fmt.Errorf("device creds list is empty")
}
for _, creds := range in {
if creds.Protocol == protocol {
if creds.Login == "" || creds.Password == "" {
return nil, fmt.Errorf("login or password can not be empty")
}
return creds, nil
}
}
return nil, fmt.Errorf("creds not found")
}
Попробуем создать клиента к гипервизору для проверки подключения, используем переменную ошибки далее в конечном парсинге ответа:
func (r *microservice) CollectVirtualMachinesList(ctx context.Context, req *pb.CollectHypervisorInitialContentRequest) (*pb.CollectHypervisorInitialContentResponse, error) {
// ...
_, err = createClient(ctx, req.DeviceInput.Address, creds.Login, creds.Password)
// ...
}
func createClient(ctx context.Context, address, login, password string) (*govmomi.Client, error) {
if strings.TrimSpace(address) == "" {
return nil, fmt.Errorf("hypervisor address can not be empty")
}
u, err := soap.ParseURL(address)
if err != nil {
return nil, fmt.Errorf("parse URL [%s]: %s", address, err)
}
// TODO UserPassword() method UNRECOMMENDED
u.User = url.UserPassword(login, password)
return govmomi.NewClient(ctx, u, true)
}
Далее попробуем запросить данные с гипервизора с помощью функции collectData:
func (r *microservice) CollectVirtualMachinesList(ctx context.Context, req *pb.CollectHypervisorInitialContentRequest) (*pb.CollectHypervisorInitialContentResponse, error) {
// ...
collectData(ctx, req.DeviceInput.Address, creds.Login, creds.Password, &hostSystems, "HostSystem", "hardware", "summary", "config")
// ...
}
func collectData(
ctx context.Context,
addr, login, passw string,
dst any,
mainPath string,
subPaths ...string,
) error {
c, err := createClient(ctx, addr, login, passw)
if err != nil {
return fmt.Errorf("connect: %s", err)
}
m := view.NewManager(c.Client)
v, err := m.CreateContainerView(ctx, c.ServiceContent.RootFolder, []string{mainPath}, true)
if err != nil {
return fmt.Errorf("create container view: %s", err)
}
defer v.Destroy(ctx) //nolint:all
if err = v.Retrieve(ctx, []string{mainPath}, subPaths, dst); err != nil {
return fmt.Errorf("retrieve: %s", err)
}
return nil
}
В завершении парсим данные из полученного ответа, формируем структуру для ответа по RPC (на данный момент обрабатываем только первую хостовую систему в массиве):
func parseInitialContent(hs []mo.HostSystem, checkErr error) *pb.CollectHypervisorInitialContentResponse {
out := pb.CollectHypervisorInitialContentResponse{
Content: &pb.DeviceInitialContent{},
}
if checkErr != nil {
out.Content.CheckResult = false
out.Content.Output = checkErr.Error()
return &out
}
out.Content.CheckResult = true
h := hs[0]
if h.Hardware != nil {
sn := strings.TrimSpace(h.Hardware.SystemInfo.SerialNumber)
if sn != "" {
out.Content.SerialNumbers = append(out.Content.SerialNumbers, sn)
}
model := strings.TrimSpace(h.Hardware.SystemInfo.Model)
if model != "" {
out.Content.Models = append(out.Content.Models, model)
}
vendor := strings.TrimSpace(h.Hardware.SystemInfo.Vendor)
if model != "" {
out.Content.Vendors = append(out.Content.Vendors, vendor)
}
}
if h.Config != nil {
operationSystemVersion := h.Config.Product.Version
operationSystem := h.Config.Product.FullName
operationSystemArchitecture := h.Config.Product.OsType
hostname := ""
if h.Summary.Host != nil {
hostname = h.Summary.Host.Value
}
out.Content.OperationSystems = append(out.Content.OperationSystems, &pb.InitialOperationSystem{
OperationSystem: operationSystem,
OperationSystemVersion: operationSystemVersion,
OperationSystemArchitecture: operationSystemArchitecture,
Hostname: hostname,
})
}
return &out
}
В завершении отправляем полученный результат как ответ по RPC.
Пример готового проекта расположен в папке project