-
Notifications
You must be signed in to change notification settings - Fork 2
/
identify.go
240 lines (219 loc) · 8.63 KB
/
identify.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright (C) 2015-2020 the Gprovision Authors. All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// SPDX-License-Identifier: BSD-3-Clause
//
// +build !light
package appliance
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/purecloudlabs/gprovision/pkg/hw/block"
"github.com/purecloudlabs/gprovision/pkg/hw/dmi"
"github.com/purecloudlabs/gprovision/pkg/log"
)
// cause bindata.go to be generated from files in the data dir
//go:generate ../../bin/go-bindata -tags !light -prefix=../../proprietary/data/appliance -pkg=$GOPACKAGE ../../proprietary/data/appliance
func init() {
if strings.Contains(os.Args[0], ".test") {
//compiled/executed by 'go test', so don't load json
return
}
//get either the generic 'aj_default' or the go-bindata version
j := getJson()
err := loadJson(j)
if err != nil {
log.Logf("loading default json: %s", err)
log.Fatalf("json error")
}
}
//TODO move to per-variant match function?
//TODO add bool to Variant to indicate whether to use baseboard-* or system-*
func Identify() *Variant {
if identifiedVariant != nil {
return identifiedVariant
}
// mfg/sysMfg and prod/sysProd are set by the oem and typically aren't very specific
// sku is more specific and at times is set by us
mfg := dmi.String("baseboard-manufacturer")
prod := dmi.String("baseboard-product-name")
sku := dmi.Field(1, "SKU Number:") //this is the default field for SKU, may be overridden for paricular models
for _, v := range variants {
if mfg == v.DmiMbMfg && prod == v.DmiProdName {
overrideSku, success := v.checkModelRe(mfg, prod, sku)
if !success {
continue
}
if !v.processorMatch() {
continue
}
identifiedVariant = &Variant{
i: v,
mfg: mfg,
prod: prod,
sku: overrideSku,
serial: dmi.String(v.SerNumField),
}
return identifiedVariant
}
}
log.Logf("attempting alternate identification method...")
sysMfg := dmi.String("system-manufacturer")
sysProd := dmi.String("system-product-name")
for _, v := range variants {
if sysMfg == v.DmiMbMfg && sysProd == v.DmiProdName {
overrideSku, success := v.checkModelRe(sysMfg, sysProd, sku)
if !success {
continue
}
if !v.processorMatch() {
continue
}
identifiedVariant = &Variant{
i: v,
mfg: sysMfg,
prod: sysProd,
sku: overrideSku,
serial: dmi.String(v.SerNumField),
}
return identifiedVariant
}
}
log.Logf("no match for mfg|prod %s | %s (or %s | %s ), CPU %q\nknown platforms:\n", mfg, prod, sysMfg, sysProd, dmi.String("processor-version"))
for _, v := range variants {
log.Log(v.DiagSummary())
}
return nil
}
// Check that the unit's cpus match what the system reports.
func (v *Variant_) processorMatch() bool {
if len(v.CPU) == 0 {
return true
}
//system with 1 populated socket, 1 empty:
//Cpu 1\nNot Specified
//not sure if "Not Specified" comes from dmidecode or is something the vendor chooses
current := strings.TrimSpace(dmi.String("processor-version"))
//not sure the cpus will always be in the same order, so loop over them
vcpus := strings.Split(v.CPU, "\n")
ccpus := strings.Split(current, "\n")
for _, vc := range vcpus {
for i := range ccpus {
cc := strings.TrimSpace(ccpus[i])
if cc == vc {
ccpus = append(ccpus[:i], ccpus[i+1:]...)
break
}
}
}
//if it matches, ccpus will be empty
return len(ccpus) == 0
}
func ReIdentify() *Variant {
identifiedVariant = nil
return Identify()
}
func (v *Variant_) checkModelRe(mfg, prod, sku string) (overrideSku string, match bool) {
re, err := regexp.Compile(v.DmiProdModelRegex)
if err != nil {
log.Logf("skipping potential match %s:%s, as regex %s failed to compile - err=%s",
mfg, prod, v.DmiProdModelRegex, err)
return "", false
}
if v.DmiPMOverrideFld != "" {
sku = dmi.Field(v.DmiPMOverrideTyp, v.DmiPMOverrideFld)
}
if !re.Match([]byte(sku)) {
log.Logf("skipping potential match %s:%s, as regex %s failed to match %s",
mfg, prod, v.DmiProdModelRegex, sku)
return "", false
}
log.Logf("+++ device matches %s - %s:%s:%s +++", v.DevCodeName, mfg, prod, sku)
return sku, true
}
//one-line summary
func (v *Variant_) DiagSummary() string {
s := fmt.Sprintf(" %s | %s | SKU %s (%s)", v.DmiMbMfg, v.DmiProdName, v.DmiProdModelRegex, v.DevCodeName)
if v.CPU != "" {
s += fmt.Sprintf(", CPU=%q", v.CPU)
}
return s
}
//returns names of block devices which could be the recovery device
//excludes virtual devices and ones with incorrect size
func (v *Variant) RecoveryCandidates(RecoverySize uint64) (devList []string) {
for _, dev := range block.Devices() {
if dev.Size == RecoverySize {
log.Logf("recovery candidate: %s (%d)", dev.Name, dev.Size)
devList = append(devList, dev.Name)
} else {
log.Logf("rejected recovery candidate: %s (%d != %d)", dev.Name, dev.Size, RecoverySize)
}
}
return
}
// Call Identify(); if that fails, use fallback() to get the stored code name.
// Use appliance.json section matching that codename. The fallback() func
// probably gets the codename from the file /platform on the recovery volume
// (and ultimately from the windows disktag).
func IdentifyWithFallback(fallback func() (string, error)) (*Variant, error) {
Identify()
if identifiedVariant != nil {
return identifiedVariant, nil
}
ident, err := fallback()
if err != nil {
return nil, err
}
usedFallback = true
for _, v := range variants {
var sku string
if v.DevCodeName == ident {
if v.DmiPMOverrideFld != "" {
sku = dmi.Field(v.DmiPMOverrideTyp, v.DmiPMOverrideFld)
} else {
sku = dmi.Field(1, "SKU Number:")
}
identifiedVariant = &Variant{
i: v,
mfg: v.DmiMbMfg,
prod: v.DmiProdName,
sku: sku,
serial: dmi.String(v.SerNumField),
}
break
}
}
return identifiedVariant, nil
}
var usedFallback bool
func IdentifiedViaFallback() bool {
return usedFallback
}
func Get(codename string) *Variant {
for _, v := range variants {
if v.DevCodeName == codename {
return &Variant{i: v}
}
}
return nil
}
var aj_default = `{"Variants":[
{"Familyname":"qemu","DmiMbMfg":"GPROV_QEMU","DmiProdName":"9p2k_dev","DmiProdModelRegex":".*","SerNumField":"system-serial-number","NumDataDisks":1,"Disksize":5368709120,"DiskIsSSD":true,"SwRaidlevel":-1,"Virttype":2,"NICInfo":{"SharedDiagPorts":[],"WANIndex":0,"DefaultNamesNoDiag":["Port 1 (WAN)"]},"RecoveryMedia":{"LocateRDMethod":"9p","ValidateRDMethod":"9p","FsType":"9p","FsOptsAdditional":"trans=virtio,version=9p2000.L"},"Lcd":"none","DevCodeName":"QEMU","Prototype":true},
{"Familyname":"qemu","DmiMbMfg":"GPROV_QEMU","DmiProdName":"mfg_test","DmiProdModelRegex":".*","SerNumField":"system-serial-number","NumDataDisks":1,"Disksize":21474836480,"DiskIsSSD":true,"SwRaidlevel":-1,"Virttype":2,"NICInfo":{"SharedDiagPorts":[],"WANIndex":0,"DefaultNamesNoDiag":["Port 1 (WAN)"]},"RecoveryMedia":{"LocateRDMethod":"byLabel","ValidateRDMethod":"usb","FsType":"ext3","SSD":true},"Lcd":"none","DevCodeName":"QEMU-mfg-test","Prototype":true},
{"_comment":"identical to the above, except for prod name & ipmi being enabled",
"Familyname":"qemu","DmiMbMfg":"GPROV_QEMU","DmiProdName":"mfg_test_ipmi","DmiProdModelRegex":".*","SerNumField":"system-serial-number","NumDataDisks":1,"Disksize":21474836480,"DiskIsSSD":true,"SwRaidlevel":-1,"Virttype":2,"NICInfo":{"SharedDiagPorts":[],"WANIndex":0,"DefaultNamesNoDiag":["Port 1 (WAN)"]},"IPMI":true,"RecoveryMedia":{"LocateRDMethod":"byLabel","ValidateRDMethod":"usb","FsType":"ext3","SSD":true},"Lcd":"none","DevCodeName":"QEMU-mfg-test","Prototype":true},
{"Familyname":"cputest","DmiMbMfg":"cputest","DmiProdName":"cputest","DmiProdModelRegex":".*","CPU":"someVersion","SerNumField":"system-serial-number","NumDataDisks":1,"Disksize":21474836480,"DiskIsSSD":true,"SwRaidlevel":-1,"Virttype":2,"NICInfo":{"SharedDiagPorts":[],"WANIndex":0,"DefaultNamesNoDiag":["Port 1 (WAN)"]},"RecoveryMedia":{"LocateRDMethod":"byLabel","ValidateRDMethod":"usb","FsType":"ext3","SSD":true},"Lcd":"none","DevCodeName":"cputest1","Prototype":true},
{"Familyname":"cputest","DmiMbMfg":"cputest","DmiProdName":"cputest","DmiProdModelRegex":".*","CPU":"someOtherVersion","SerNumField":"system-serial-number","NumDataDisks":1,"Disksize":21474836480,"DiskIsSSD":true,"SwRaidlevel":-1,"Virttype":2,"NICInfo":{"SharedDiagPorts":[],"WANIndex":0,"DefaultNamesNoDiag":["Port 1 (WAN)"]},"RecoveryMedia":{"LocateRDMethod":"byLabel","ValidateRDMethod":"usb","FsType":"ext3","SSD":true},"Lcd":"none","DevCodeName":"cputest2","Prototype":true}]}`
//load embedded data
func getJson() []byte {
j, err := Asset("appliance.json")
if err == nil {
return j
}
log.Log("no embedded appliance.json, using default")
return []byte(aj_default)
}