bmclib v2

repository·main·Indexed 42 days ago

https://github.com/bmc-toolbox/bmclib

A Go library that abstracts interactions with Baseboard Management Controllers (BMCs), providing a unified interface for managing hardware, firmware, and power states across various protocols and vendor-specific APIs including Redfish, IPMI, and SSH.

Tokens
8.5K
Snippets
31
Records
42
Agent score
53%

What's inside bmclib

  1. How BMC drivers and connections work

    main

    bmclib uses multiple drivers to interact with BMCs. Drivers represent different services exposed by a BMC, such as redfish, IPMI, SSH, or a vendor API.

    The bmclib.Client automatically determines which driver to use for an action (e.g., Power cycle or Create user) based on driver availability or through a compatibility test. You can manually control driver selection by modifying the client.Registry.Drivers list.

    // Query just using the redfish endpoint
    cl := bmclib.NewClient("192.168.1.1", "admin", "hunter2")
    cl.Registry.Drivers = cl.Registry.Using("redfish")
    
    // Query using redfish and fall back to IPMI
    client := bmclib.NewClient("192.168.1.1", "admin", "hunter2")
    drivers := append(registrar.Drivers{}, bmcClient.Registry.Using("redfish")...)
    drivers = append(drivers, bmcClient.Registry.Using("ipmi")...)
    client.Registry.Drivers = drivers
    
    // Filter drivers based on compatibility
    client.Registry.Drivers = cl.Registry.FilterForCompatible(ctx)
  2. Monitor BMC Update and Verify Tasks

    main

    After initiating an update or verification, monitor the progress by querying the Redfish TaskService.

    1. Check Task Status: GET the specific task URI (e.g., /redfish/v1/TaskService/Tasks/1).
    2. Identify Task Type: Look for the Name field in the response to distinguish between BMC Verify and BMC Update tasks.
    3. Track Progress: Use TaskState (e.g., Running, Completed) and PercentComplete to monitor the lifecycle.
    4. Verify Success: A successful task will have TaskStatus: "OK" and TaskState: "Completed".
    # Query a specific task status
    curl 'https://<BMC_IP>/redfish/v1/TaskService/Tasks/<TASK_ID>' \
      -H 'X-Auth-Token: <YOUR_TOKEN>' \
      --insecure
  3. Perform a BMC Update on Supermicro X12

    main

    To update the BMC on a Supermicro X12 system, upload the firmware file to the Redfish UpdateService/upload endpoint using a multipart/form-data request. The request must include UpdateParameters as a JSON payload and the firmware file as UpdateFile.

    curl 'https://<BMC_IP>/redfish/v1/UpdateService/upload' \
      -H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytykIB3S8fDkno3cP' \
      -H 'X-Auth-Token: <YOUR_TOKEN>' \
      --data-raw $'------WebKitFormBoundarytykIB3S8fDkno3cP\r\nContent-Disposition: form-data; name="UpdateParameters"\r\n\r\n{"Targets":["/redfish/v1/Managers/1"],"@Redfish.OperationApplyTime":"OnStartUpdateRequest","Oem":{"Supermicro":{"BMC":{"PreserveCfg":true,"PreserveSdr":true,"PreserveSsl":true}}}}\r\n------WebKitFormBoundarytykIB3S8fDkno3cP\r\nContent-Disposition: form-data; name="UpdateFile"; filename="BMC_X12AST2600-F201MS_20220627_1.13.04_STDsp.bin"\r\nContent-Type: application/macbinary\r\n\r\n<BINARY_DATA>\r\n------WebKitFormBoundarytykIB3S8fDkno3cP--'
  4. Flash ASRock Rack BMC Firmware

    main

    Flashing ASRock Rack BMC firmware is a multi-step process involving setting the flash mode, uploading the image, verifying the payload, and triggering the upgrade.

    Workflow Steps:

    1. Enter Flash Mode: Call PUT /api/maintenance/flash with no payload. This takes approximately one minute to return a 200 OK.
    2. Upload Firmware: Call POST /api/maintenance/firmware using multipart/form-data. The payload must include a field named fwimage containing the firmware file (e.g., application/octet-stream). A successful upload returns {"cc": 0}.
    3. Verify Firmware: Call GET /api/maintenance/firmware/verification.
      • If it returns 500, the firmware payload is bad; you must invoke a reset.
      • If it returns 200, check the verification_status in the response array.
    4. Handle Reset: If verification fails OR the firmware update progress reaches 100% done, you must invoke a reset using GET /api/maintenance/reset.
    5. Trigger Upgrade: Call PUT /api/maintenance/firmware/upgrade with a JSON payload to define preservation settings:
      • preserve_config: (integer)
      • preserve_network: (integer)
      • preserve_user: (integer)
      • flash_status: (integer)
    6. Monitor Progress: Poll GET /api/maintenance/firmware/flash-progress to track the progress string and state.
    # 1. Enter flash mode
    PUT /api/maintenance/flash
    
    # 2. Upload firmware
    POST /api/maintenance/firmware
    Content-Type: multipart/form-data
    
    --WebKitFormBoundaryESKCgdjyLnqUPHBK
    Content-Disposition: form-data; name="fwimage"; filename="firmware.ima"
    Content-Type: application/octet-stream
    
    <binary_data>
    --WebKitFormBoundaryESKCgdjyLnqUPHBK--
    
    # 3. Verify
    GET /api/maintenance/firmware/verification
    
    # 5. Upgrade
    PUT /api/maintenance/firmware/upgrade
    {"preserve_config":1,"preserve_network":0,"preserve_user":0,"flash_status":1}
    
    # 6. Monitor
    GET /api/maintenance/firmware/flash-progress
  5. Enable Tracing for BMC interactions

    main

    To collect trace telemetry (spans) for each client method, use the bmclib.WithTracerProvider() option when initializing the client with an OpenTelemetry tracer provider.

    cl := bmclib.NewClient(
              host,
              user,
              pass,
              bmclib.WithLogger(log),
              bmclib.WithTracerProvider(otel.GetTracerProvider()),
          )
  6. Flash ASRock Rack BIOS Firmware

    main

    Updating the BIOS on ASRock Rack hardware follows a specific sequence of POST requests to the api/asrr/maintenance/BIOS/ endpoints.

    Workflow Steps:

    1. Upload BIOS Image: Call POST api/asrr/maintenance/BIOS/firmware using multipart/form-data. The file must be provided in a field named fwimage.
    2. Configure BIOS: Call POST api/asrr/maintenance/BIOS/configuration with a JSON payload {"action":"2"}. A successful response is {"response": 1}.
    3. Execute Upgrade: Call POST api/asrr/maintenance/BIOS/upgrade with a JSON payload {"action": 3}.
    4. Monitor Progress: Poll GET api/asrr/maintenance/BIOS/flash-progress to track the update status.
    # 1. Upload BIOS
    POST api/asrr/maintenance/BIOS/firmware
    Content-Type: multipart/form-data
    
    --WebKitFormBoundaryBet48KCtZK4gBlQz
    Content-Disposition: form-data; name="fwimage"; filename="BIOS_FILE"
    Content-Type: application/octet-stream
    
    <binary_data>
    --WebKitFormBoundaryBet48KCtZK4gBlQz--
    
    # 2. Configure
    POST api/asrr/maintenance/BIOS/configuration
    {"action":"2"}
    
    # 3. Upgrade
    POST api/asrr/maintenance/BIOS/upgrade
    {"action": 3}
    
    # 4. Monitor
    GET api/asrr/maintenance/BIOS/flash-progress
  7. Configure timeouts for BMC interactions

    main

    You can manage timeouts using either the context passed to method calls or via client options.

    • Total max timeout: Controlled by passing a context.WithTimeout or context.WithDeadline to method calls. The per-provider timeout is calculated as the total timeout divided by the number of providers.
    • Per-provider timeout: Set using bmclib.WithPerProviderTimeout(duration) in the NewClient constructor. This defines the maximum time a specific provider has to complete an interaction.
    • Default timeout: If no timeout is specified, the default is 30 seconds per provider/interaction.
    // Example: Total max timeout (60s) and per-provider timeout (15s)
    cl := bmclib.NewClient(host, user, pass, bmclib.WithLogger(log), bmclib.WithPerProviderTimeout(15*time.Second))
    
    ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
    defer cancel()
    
    if err = cl.Open(ctx); err != nil {
      return(err)
    }
    defer cl.Close(ctx)
    
    state, err := cl.GetPowerState(ctx)
  8. Configure BMC Update Parameters

    main

    When performing a BMC update, the UpdateParameters JSON object defines the target and behavior of the update.

    Key fields:

    • Targets: An array of Redfish URIs (e.g., ["/redfish/v1/Managers/1"]).
    • @Redfish.OperationApplyTime: Determines when the update is applied. Use OnStartUpdateRequest to apply immediately.
    • Oem.Supermicro.BMC: Contains Supermicro-specific settings:
      • PreserveCfg: Boolean to preserve configuration.
      • PreserveSdr: Boolean to preserve Sensor Data Repository.
      • PreserveSsl: Boolean to preserve SSL certificates.
    {
      "Targets": ["/redfish/v1/Managers/1"],
      "@Redfish.OperationApplyTime": "OnStartUpdateRequest",
      "Oem": {
        "Supermicro": {
          "BMC": {
            "PreserveCfg": true,
            "PreserveSdr": true,
            "PreserveSsl": true
          }
        }
      }
    }
  9. Basic usage: Connect to a BMC and retrieve inventory

    main

    This example demonstrates how to initialize a client with a logger, open a session, and retrieve hardware/firmware inventory data.

    import (
      bmclib "github.com/bmc-toolbox/bmclib/v2"
    )
    
        // setup logger
        l := logrus.New()
        l.Level = logrus.DebugLevel
        logger := logrusr.New(l)
    
        clientOpts := []bmclib.Option{bmclib.WithLogger(logger)}
    
        // init client
        client := bmclib.NewClient(*host, "admin", "hunter2", clientOpts...)
    
        // open BMC session
        err := client.Open(ctx)
        if err != nil {
            log.Fatal(err, "bmc login failed")
        }
    
        defer client.Close(ctx)
    
        // retrieve inventory data
        inventory, err := client.Inventory(ctx)
        if err != nil {
            l.Error(err)
        }
    
        b, err := json.MarshalIndent(inventory, "", "  ")
        if err != nil {
            l.Error(err)
        }
    
        fmt.Println(string(b))
  10. Implement powerCycle using SMC XML API

    main

    To perform a cold powercycle on a Supermicro x11 device, send a POST request to cgi/ipmi.cgi with the payload op=POWER_INFO.XML&r=(1,3)&_=. The request must include the header Content-type: application/x-www-form-urlencoded; charset=UTF-8.

    // powerCycle using SMC XML API
    func (c *x11) powerCycle(ctx context.Context) (bool, error) {
    	payload := []byte(`op=POWER_INFO.XML&r=(1,3)&_=`)
    
    	headers := map[string]string{
    		"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
    	}
    
    	body, status, err := c.serviceClient.query(ctx, "cgi/ipmi.cgi", http.MethodPost, bytes.NewBuffer(payload), headers, 0)
    	if err != nil {
    		return false, err
    	}
    
    	if status != http.StatusOK {
    		return false, unexpectedResponseErr(payload, body, status)
    	}
    
    	return true, nil
    }