Huawei Cloud Provider implementation (#241)

* add huawei CCI provider

* add readme

* add vender

* add huawei provider mock test
This commit is contained in:
Fei Xu
2018-06-30 01:21:15 +08:00
committed by Robbie Zhang
parent df6a8750bb
commit a30303035f
34 changed files with 3884 additions and 0 deletions

View File

@@ -8,3 +8,4 @@ Ria Bhatia <ria.bhatia@microsoft.com>
Rita Zhang <rita.z.zhang@gmail.com>
Robbie Zhang <junjiez@microsoft.com>
Ben Corrie <bcorrie@vmware.com>
Fei Xu <xufei40@huawei.com>

View File

@@ -0,0 +1,85 @@
# Huawei CCI
Huawei CCI [(Cloud Container Instance)](https://www.huaweicloud.com/product/cci.html) service provides serverless container management,
and does not require users to manage the cluster and the server.
Only through simple configuration, users can enjoy the agility and high performance of the container.
CCI supports stateless workloads (Deployment) and stateful workload (StatefulSet).
On the basis of Kubernetes, we have made a series of important enhancements such as secure container,
elastic load balancing, elastic scalability, Blue Green Deployment and so on.
## Huawei CCI Virtual Kubelet Provider
Huawei CCI virtual kubelet provider configures a CCI project as node in any of your Kubernetes cluster,
such as Huawei CCE [(Cloud Container Engine)](https://www.huaweicloud.com/en-us/product/cce.html).
CCE supports native Kubernetes applications and tools as private cluster, allowing you to easily set up a container runtime environment.
Pod which is scheduled to the virtual kubelet provider will run in the CCI, that will makes good use of the high performance of CCI.
The diagram below illustrates how Huawei CCI virtual kubelet provider works.
![diagram](cci-provider.svg)
**NOTE:** The Huawei CCI virtual-kubelet provider is in the early stages of development,
and don't use it in a production environment.
## Prerequisites
You must install the provider in a Kubernetes cluster and connect to the CCI, and also need create an account for CCI.
Once you've created your account, then need to record the aksk, region for the configuration in next step.
## Configuration
Before run CCI Virtual Kubelet Provider, you must do as the following steps.
1. Create a configuration profile.
You need to provide the fields you specify like in the [example fils](cci.toml).
2. Copy your AKSK and save them in environment variable:
```console
export APP_KEY="<AppKey>"
export APP_SECRET="<AppSecret>"
```
## Connect to CCI from your cluster via Virtual Kubelet
On the Kubernetes work node, starting a virtual-kubelet process as follows.
```console
virtual-kubelet --provider huawei --provider-config cci.toml
```
Then run ``kubectl get nodes`` in your cluster to validate the provider has been running as a node.
```console
kubectl get nodes
NAME STATUS AGE
virtual-kubelet Ready 5m
cce-192.168.0.178 Ready 10d
cce-192.168.0.233 Ready 10d
```
If want to stop the virtual kubelet, just stop the virtual kubelet process.
## Schedule pod to CCI via Virtual Kubelet
```console
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
app: myapp
spec:
nodeName: virtual-kubelet
containers:
- name: nginx
image: 1and1internet/ubuntu-16-nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
tolerations:
- key: huawei.com/cci
effect: NoSchedule
```
Replace the nodeName to the virtual-kubelet nodename and save the configuration to a file ``virtual-kubelet-pod.yaml``.
Then run ``kubectl create -f virtual-kubelet-pod.yaml`` to create the pod. Run ``kubectl get pods -owide`` to get pods.
```console
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
myapp-7c7877989-vbffm 1/1 Running 0 39s 172.17.0.3 virtual-kubelet
```

View File

@@ -0,0 +1,309 @@
package auth
// HWS API Gateway Signature
// Analog to AWS Signature Version 4, with some HWS specific parameters
// Please refer to: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"
)
// BasicDateFormat and BasicDateFormatShort define aws-date format
const (
BasicDateFormat = "20060102T150405Z"
BasicDateFormatShort = "20060102"
TerminationString = "sdk_request"
Algorithm = "SDK-HMAC-SHA256"
PreSKString = "SDK"
HeaderXDate = "x-sdk-date"
HeaderDate = "date"
HeaderHost = "host"
HeaderAuthorization = "Authorization"
HeaderContentSha256 = "x-sdk-content-sha256"
// todo: use the region and service.
DefaultRegion = "default"
DefaultService = "apigateway"
)
func shouldEscape(c byte) bool {
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '~' || c == '.' {
return false
}
return true
}
func escape(s string) string {
hexCount := 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c) {
hexCount++
}
}
if hexCount == 0 {
return s
}
t := make([]byte, len(s)+2*hexCount)
j := 0
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case shouldEscape(c):
t[j] = '%'
t[j+1] = "0123456789ABCDEF"[c>>4]
t[j+2] = "0123456789ABCDEF"[c&15]
j += 3
default:
t[j] = s[i]
j++
}
}
return string(t)
}
func hmacsha256(key []byte, data string) ([]byte, error) {
h := hmac.New(sha256.New, []byte(key))
if _, err := h.Write([]byte(data)); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// Build a CanonicalRequest from a regular request string
//
// See http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
// CanonicalRequest =
// HTTPRequestMethod + '\n' +
// CanonicalURI + '\n' +
// CanonicalQueryString + '\n' +
// CanonicalHeaders + '\n' +
// SignedHeaders + '\n' +
// HexEncode(Hash(RequestPayload))
func CanonicalRequest(r *http.Request) (string, error) {
var hexencode string
var err error
if hex := r.Header.Get(HeaderContentSha256); hex != "" {
hexencode = hex
} else {
data, err := RequestPayload(r)
if err != nil {
return "", err
}
hexencode, err = HexEncodeSHA256Hash(data)
}
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", r.Method, CanonicalURI(r), CanonicalQueryString(r), CanonicalHeaders(r), SignedHeaders(r), hexencode), err
}
// CanonicalURI returns request uri
func CanonicalURI(r *http.Request) string {
pattens := strings.Split(r.URL.Path, "/")
var uri []string
for _, v := range pattens {
switch v {
case "":
continue
case ".":
continue
case "..":
if len(uri) > 0 {
uri = uri[:len(uri)-1]
}
default:
uri = append(uri, escape(v))
}
}
urlpath := "/"
if len(uri) > 0 {
urlpath = urlpath + strings.Join(uri, "/") + "/"
}
return urlpath
}
// CanonicalQueryString
func CanonicalQueryString(r *http.Request) string {
var a []string
for key, value := range r.URL.Query() {
k := escape(key)
for _, v := range value {
var kv string
if v == "" {
kv = k
} else {
kv = fmt.Sprintf("%s=%s", k, escape(v))
}
a = append(a, kv)
}
}
sort.Strings(a)
query := strings.Join(a, "&")
r.URL.RawQuery = query
return query
}
// CanonicalHeaders
func CanonicalHeaders(r *http.Request) string {
var a []string
for key, value := range r.Header {
sort.Strings(value)
var q []string
for _, v := range value {
q = append(q, trimString(v))
}
a = append(a, strings.ToLower(key)+":"+strings.Join(q, ","))
}
a = append(a, HeaderHost+":"+r.Host)
sort.Strings(a)
return fmt.Sprintf("%s\n", strings.Join(a, "\n"))
}
// SignedHeaders
func SignedHeaders(r *http.Request) string {
var a []string
for key := range r.Header {
a = append(a, strings.ToLower(key))
}
a = append(a, HeaderHost)
sort.Strings(a)
return fmt.Sprintf("%s", strings.Join(a, ";"))
}
// RequestPayload
func RequestPayload(r *http.Request) ([]byte, error) {
if r.Body == nil {
return []byte(""), nil
}
b, err := ioutil.ReadAll(r.Body)
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
return b, err
}
// Return the Credential Scope. See http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
func CredentialScope(t time.Time, regionName, serviceName string) string {
return fmt.Sprintf("%s/%s/%s/%s", t.UTC().Format(BasicDateFormatShort), regionName, serviceName, TerminationString)
}
// Create a "String to Sign". See http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
func StringToSign(canonicalRequest, credentialScope string, t time.Time) string {
hash := sha256.New()
hash.Write([]byte(canonicalRequest))
return fmt.Sprintf("%s\n%s\n%s\n%x",
Algorithm, t.UTC().Format(BasicDateFormat), credentialScope, hash.Sum(nil))
}
// Generate a "signing key" to sign the "String To Sign". See http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
func GenerateSigningKey(secretKey, regionName, serviceName string, t time.Time) ([]byte, error) {
key := []byte(PreSKString + secretKey)
var err error
dateStamp := t.UTC().Format(BasicDateFormatShort)
data := []string{dateStamp, regionName, serviceName, TerminationString}
for _, d := range data {
key, err = hmacsha256(key, d)
if err != nil {
return nil, err
}
}
return key, nil
}
// Create the HWS Signature. See http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
func SignStringToSign(stringToSign string, signingKey []byte) (string, error) {
hm, err := hmacsha256(signingKey, stringToSign)
return fmt.Sprintf("%x", hm), err
}
// HexEncodeSHA256Hash returns hexcode of sha256
func HexEncodeSHA256Hash(body []byte) (string, error) {
hash := sha256.New()
if body == nil {
body = []byte("")
}
_, err := hash.Write(body)
return fmt.Sprintf("%x", hash.Sum(nil)), err
}
// Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign
func AuthHeaderValue(signature, accessKey, credentialScope, signedHeaders string) string {
return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, credentialScope, signedHeaders, signature)
}
func trimString(s string) string {
var trimedString []byte
inQuote := false
var lastChar byte
s = strings.TrimSpace(s)
for _, v := range []byte(s) {
if byte(v) == byte('"') {
inQuote = !inQuote
}
if lastChar == byte(' ') && byte(v) == byte(' ') && !inQuote {
continue
}
trimedString = append(trimedString, v)
lastChar = v
}
return string(trimedString)
}
type Signer interface {
Sign(*http.Request) error
}
// Signature HWS meta
type SignerHws struct {
AppKey string
AppSecret string
Region string
Service string
}
// SignRequest set Authorization header
func (s *SignerHws) Sign(r *http.Request) error {
var t time.Time
var err error
var dt string
if dt = r.Header.Get(HeaderXDate); dt != "" {
t, err = time.Parse(BasicDateFormat, dt)
} else if dt = r.Header.Get(HeaderDate); dt != "" {
t, err = time.Parse(time.RFC1123, dt)
}
if err != nil || dt == "" {
r.Header.Del(HeaderDate)
t = time.Now()
r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat))
}
canonicalRequest, err := CanonicalRequest(r)
if err != nil {
return err
}
Region := DefaultRegion
Service := DefaultService
if s.Region != "" {
Region = s.Region
}
if s.Service != "" {
Service = s.Service
}
credentialScope := CredentialScope(t, Region, Service)
stringToSign := StringToSign(canonicalRequest, credentialScope, t)
key, err := GenerateSigningKey(s.AppSecret, Region, Service, t)
if err != nil {
return err
}
signature, err := SignStringToSign(stringToSign, key)
if err != nil {
return err
}
signedHeaders := SignedHeaders(r)
authValue := AuthHeaderValue(signature, s.AppKey, credentialScope, signedHeaders)
r.Header.Set(HeaderAuthorization, authValue)
return nil
}

View File

@@ -0,0 +1,784 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- 由 Microsoft Visio, SVG Export 生成 cci-provider.svg Page-1 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="8.02082in" height="4.62889in"
viewBox="0 0 577.499 333.28" xml:space="preserve" color-interpolation-filters="sRGB" class="st19">
<v:documentProperties v:langID="2052" v:metric="true" v:viewMarkup="false">
<v:userDefs>
<v:ud v:nameU="msvSubprocessMaster" v:prompt="" v:val="VT4(Rectangle)"/>
<v:ud v:nameU="msvNoAutoConnect" v:val="VT0(1):26"/>
</v:userDefs>
</v:documentProperties>
<style type="text/css">
<![CDATA[
.st1 {fill:#ffffff;stroke:#41719c;stroke-width:0.75}
.st2 {visibility:visible}
.st3 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
.st4 {fill:#ffffff;stroke:#5b9bd5;stroke-width:0.25}
.st5 {fill:#e2efd9;stroke:#61973d;stroke-dasharray:5.25,3.75;stroke-width:0.75}
.st6 {fill:none;stroke:none;stroke-width:0.25}
.st7 {fill:#000000;font-family:Calibri;font-size:1.33333em}
.st8 {fill:#000000;font-family:Calibri;font-size:1.00001em}
.st9 {fill:#c5e0b3;stroke:#c7c8c8;stroke-width:0.25}
.st10 {fill:#000000;font-family:Calibri;font-size:1.00001em;font-weight:bold}
.st11 {marker-end:url(#mrkr1-32);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
.st12 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.28409090909091}
.st13 {fill:#9cc3e5;stroke:#c7c8c8;stroke-width:0.25}
.st14 {fill:#000000;font-family:Calibri;font-size:1.16666em;font-weight:bold}
.st15 {fill:#000000;font-family:Calibri;font-size:1.33333em;font-weight:bold}
.st16 {fill:#ffffff;stroke:#41719c;stroke-dasharray:3.5,2.5;stroke-width:0.5}
.st17 {fill:#e2efd9;stroke:#61973d;stroke-width:0.75}
.st18 {fill:#000000;font-family:Calibri;font-size:1.16666em}
.st19 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
]]>
</style>
<defs id="Markers">
<g id="lend1">
<path d="M 1 -1 L 0 0 L 1 1 " style="stroke-linecap:round;stroke-linejoin:round;fill:none"/>
</g>
<marker id="mrkr1-32" class="st12" v:arrowType="1" v:arrowSize="2" orient="auto" markerUnits="strokeWidth"
overflow="visible">
<use xlink:href="#lend1" transform="scale(-3.52,-3.52) "/>
</marker>
</defs>
<defs id="Filters">
<filter id="filter_2">
<feGaussianBlur stdDeviation="2"/>
</filter>
</defs>
<g v:mID="0" v:index="1" v:groupContext="foregroundPage">
<v:userDefs>
<v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
</v:userDefs>
<title>页-1</title>
<v:pageProperties v:drawingScale="0.0393701" v:pageScale="0.0393701" v:drawingUnits="24" v:shadowOffsetX="8.50394"
v:shadowOffsetY="-8.50394"/>
<g id="shape17-1" v:mID="17" v:groupContext="shape" transform="translate(18.7496,-157.931)">
<title>圆角的矩形.17</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
</v:userDefs>
<path d="M24.66 333.28 L221.95 333.28 A24.661 24.661 -180 0 0 246.61 308.62 L246.61 206.51 A24.661 24.661 -180 0 0 221.95
181.85 L24.66 181.85 A24.661 24.661 -180 0 0 -0 206.51 L0 308.62 A24.661 24.661 -180 0 0 24.66 333.28 Z"
class="st1"/>
</g>
<g id="shape85-3" v:mID="85" v:groupContext="shape" transform="translate(33.1466,-173.969)">
<title>圆角的矩形</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<g id="shadow85-4" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M11.34 333.28 L102.05 333.28 A11.3384 11.3384 -180 0 0 113.39 321.94 L113.39 287.93 A11.3384 11.3384 -180
0 0 102.05 276.59 L11.34 276.59 A11.3384 11.3384 -180 0 0 0 287.93 L0 321.94 A11.3384 11.3384 -180 0
0 11.34 333.28 Z" class="st3"/>
</g>
<path d="M11.34 333.28 L102.05 333.28 A11.3384 11.3384 -180 0 0 113.39 321.94 L113.39 287.93 A11.3384 11.3384 -180 0
0 102.05 276.59 L11.34 276.59 A11.3384 11.3384 -180 0 0 0 287.93 L0 321.94 A11.3384 11.3384 -180 0 0 11.34
333.28 Z" class="st4"/>
</g>
<g id="shape5-8" v:mID="5" v:groupContext="shape" transform="translate(89.6157,-242.747)">
<title>圆角的矩形.5</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
</v:userDefs>
<path d="M8.5 333.28 L76.54 333.28 A8.5038 8.5038 -180 0 0 85.04 324.78 L85.04 295.01 A8.5038 8.5038 -180 0 0 76.54 286.51
L8.5 286.51 A8.5038 8.5038 -180 0 0 -0 295.01 L0 324.78 A8.5038 8.5038 -180 0 0 8.5 333.28 Z" class="st5"/>
</g>
<g id="shape7-10" v:mID="7" v:groupContext="shape" transform="translate(140.639,-281.014)">
<title>工作表.7</title>
<desc>CCE-Cluster1</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="62.3622" cy="316.272" width="124.73" height="34.0157"/>
<rect x="0" y="299.264" width="124.724" height="34.0157" class="st6"/>
<text x="20.54" y="321.07" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CCE-Cluster1</text> </g>
<g id="shape8-13" v:mID="8" v:groupContext="shape" transform="translate(100.954,-255.503)">
<title>工作表.8</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="28.3465" cy="316.272" width="56.7" height="34.0157"/>
<rect x="0" y="299.264" width="56.6929" height="34.0157" class="st6"/>
<text x="17.58" y="319.87" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape9-16" v:mID="9" v:groupContext="shape" transform="translate(75.4425,-238.495)">
<title>工作表.9</title>
<desc>nodeName: VK</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="55.2756" cy="316.272" width="110.56" height="34.0157"/>
<rect x="0" y="299.264" width="110.551" height="34.0157" class="st6"/>
<text x="18.81" y="319.87" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>nodeName: VK</text> </g>
<g id="shape11-19" v:mID="11" v:groupContext="shape" transform="translate(159.065,-184.636)">
<title>圆角的矩形.11</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
</v:userDefs>
<g id="shadow11-20" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st3"/>
</g>
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st9"/>
</g>
<g id="shape12-24" v:mID="12" v:groupContext="shape" transform="translate(167.568,-187.471)">
<title>工作表.12</title>
<desc>Virtual-Kubelet</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="42.5197" cy="316.272" width="85.04" height="34.0157"/>
<rect x="0" y="299.264" width="85.0394" height="34.0157" class="st6"/>
<text x="4.48" y="319.87" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Virtual-Kubelet</text> </g>
<g id="shape14-27" v:mID="14" v:groupContext="shape" transform="translate(433.184,-143.181) rotate(50.8696)">
<title>工作表.14</title>
<path d="M0 333.28 L53.9 333.28" class="st11"/>
</g>
<g id="shape22-33" v:mID="22" v:groupContext="shape" transform="translate(159.065,-184.636)">
<title>圆角的矩形.22</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
</v:userDefs>
<g id="shadow22-34" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st3"/>
</g>
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st9"/>
</g>
<g id="shape23-38" v:mID="23" v:groupContext="shape" transform="translate(167.568,-187.471)">
<title>工作表.23</title>
<desc>Virtual-Kubelet</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="42.5197" cy="316.272" width="85.04" height="34.0157"/>
<rect x="0" y="299.264" width="85.0394" height="34.0157" class="st6"/>
<text x="4.48" y="319.87" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Virtual-Kubelet</text> </g>
<g id="shape37-41" v:mID="37" v:groupContext="shape" transform="translate(93.9143,-192.618)">
<title>圆角的矩形.37</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
</v:userDefs>
<g id="shadow37-42" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0
0 39.54 304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39
333.28 Z" class="st3"/>
</g>
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0 0 39.54
304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39 333.28 Z"
class="st13"/>
</g>
<g id="shape38-46" v:mID="38" v:groupContext="shape" transform="translate(98.1663,-194.602)">
<title>工作表.38</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="7.66" y="324.41" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape39-49" v:mID="39" v:groupContext="shape" transform="translate(42.5364,-192.618)">
<title>圆角的矩形.39</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
</v:userDefs>
<g id="shadow39-50" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0
0 39.54 304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39
333.28 Z" class="st3"/>
</g>
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0 0 39.54
304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39 333.28 Z"
class="st13"/>
</g>
<g id="shape40-54" v:mID="40" v:groupContext="shape" transform="translate(44.6624,-193.185)">
<title>工作表.40</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="7.66" y="324.41" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape42-57" v:mID="42" v:groupContext="shape" transform="translate(363.159,-240.509)">
<title>圆角的矩形.42</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
</v:userDefs>
<path d="M8.5 333.28 L76.54 333.28 A8.5038 8.5038 -180 0 0 85.04 324.78 L85.04 295.01 A8.5038 8.5038 -180 0 0 76.54 286.51
L8.5 286.51 A8.5038 8.5038 -180 0 0 -0 295.01 L0 324.78 A8.5038 8.5038 -180 0 0 8.5 333.28 Z" class="st5"/>
</g>
<g id="shape43-59" v:mID="43" v:groupContext="shape" transform="translate(312.135,-279.131)">
<title>工作表.43</title>
<desc>CCE-Cluster1</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="62.3622" cy="316.272" width="124.73" height="34.0157"/>
<rect x="0" y="299.264" width="124.724" height="34.0157" class="st6"/>
<text x="20.54" y="321.07" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CCE-Cluster1</text> </g>
<g id="shape44-62" v:mID="44" v:groupContext="shape" transform="translate(374.498,-253.265)">
<title>工作表.44</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="28.3465" cy="316.272" width="56.7" height="34.0157"/>
<rect x="0" y="299.264" width="56.6929" height="34.0157" class="st6"/>
<text x="17.58" y="319.87" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape45-65" v:mID="45" v:groupContext="shape" transform="translate(348.986,-236.257)">
<title>工作表.45</title>
<desc>nodeName: VK</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="55.2756" cy="316.272" width="110.56" height="34.0157"/>
<rect x="0" y="299.264" width="110.551" height="34.0157" class="st6"/>
<text x="18.81" y="319.87" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>nodeName: VK</text> </g>
<g id="shape46-68" v:mID="46" v:groupContext="shape" transform="translate(432.608,-182.399)">
<title>圆角的矩形.46</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
</v:userDefs>
<g id="shadow46-69" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st3"/>
</g>
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st9"/>
</g>
<g id="shape48-73" v:mID="48" v:groupContext="shape" transform="translate(706.728,-140.943) rotate(50.8696)">
<title>工作表.48</title>
<path d="M0 333.28 L53.9 333.28" class="st11"/>
</g>
<g id="shape50-78" v:mID="50" v:groupContext="shape" transform="translate(312.135,-155.693)">
<title>圆角的矩形.50</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
</v:userDefs>
<path d="M24.66 333.28 L221.95 333.28 A24.661 24.661 -180 0 0 246.61 308.62 L246.61 206.51 A24.661 24.661 -180 0 0 221.95
181.85 L24.66 181.85 A24.661 24.661 -180 0 0 -0 206.51 L0 308.62 A24.661 24.661 -180 0 0 24.66 333.28 Z"
class="st1"/>
</g>
<g id="shape51-80" v:mID="51" v:groupContext="shape" transform="translate(400.009,-240.136)">
<title>圆角的矩形.51</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.11811023622047):1"/>
</v:userDefs>
<path d="M8.5 333.28 L76.54 333.28 A8.5038 8.5038 -180 0 0 85.04 324.78 L85.04 295.01 A8.5038 8.5038 -180 0 0 76.54 286.51
L8.5 286.51 A8.5038 8.5038 -180 0 0 -0 295.01 L0 324.78 A8.5038 8.5038 -180 0 0 8.5 333.28 Z" class="st5"/>
</g>
<g id="shape52-82" v:mID="52" v:groupContext="shape" transform="translate(314.97,-278.404)">
<title>工作表.52</title>
<desc>CCE-ClusterN</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="62.3622" cy="316.272" width="124.73" height="34.0157"/>
<rect x="0" y="299.264" width="124.724" height="34.0157" class="st6"/>
<text x="19.43" y="321.07" class="st7" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CCE-ClusterN</text> </g>
<g id="shape53-85" v:mID="53" v:groupContext="shape" transform="translate(411.348,-252.892)">
<title>工作表.53</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="28.3465" cy="316.272" width="56.7" height="34.0157"/>
<rect x="0" y="299.264" width="56.6929" height="34.0157" class="st6"/>
<text x="17.58" y="319.87" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape54-88" v:mID="54" v:groupContext="shape" transform="translate(385.836,-235.884)">
<title>工作表.54</title>
<desc>nodeName: VK</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="55.2756" cy="316.272" width="110.56" height="34.0157"/>
<rect x="0" y="299.264" width="110.551" height="34.0157" class="st6"/>
<text x="18.81" y="319.87" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>nodeName: VK</text> </g>
<g id="shape55-91" v:mID="55" v:groupContext="shape" transform="translate(318.536,-181.69)">
<title>圆角的矩形.55</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.13779527559055):1"/>
</v:userDefs>
<g id="shadow55-92" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st3"/>
</g>
<path d="M9.92 333.28 L89.29 333.28 A9.9211 9.9211 -180 0 0 99.21 323.36 L99.21 303.52 A9.9211 9.9211 -180 0 0 89.29
293.6 L9.92 293.6 A9.9211 9.9211 -180 0 0 0 303.52 L0 323.36 A9.9211 9.9211 -180 0 0 9.92 333.28 Z"
class="st9"/>
</g>
<g id="shape56-96" v:mID="56" v:groupContext="shape" transform="translate(329.28,-185.233)">
<title>工作表.56</title>
<desc>Virtual-Kubelet</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="42.5197" cy="316.272" width="85.04" height="34.0157"/>
<rect x="0" y="299.264" width="85.0394" height="34.0157" class="st6"/>
<text x="4.48" y="319.87" class="st10" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Virtual-Kubelet</text> </g>
<g id="shape65-99" v:mID="65" v:groupContext="shape" transform="translate(164.734,-18.75)">
<title>圆角的矩形.65</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.34251968503937):1"/>
</v:userDefs>
<path d="M24.66 333.28 L221.95 333.28 A24.661 24.661 -180 0 0 246.61 308.62 L246.61 235.48 A24.661 24.661 -180 0 0 221.95
210.82 L24.66 210.82 A24.661 24.661 -180 0 0 -0 235.48 L0 308.62 A24.661 24.661 -180 0 0 24.66 333.28 Z"
class="st1"/>
</g>
<g id="shape66-101" v:mID="66" v:groupContext="shape" transform="translate(275.285,-220.17)">
<title>工作表.66</title>
<desc>. . .</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="9.65" y="325.01" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>. . .</text> </g>
<g id="shape67-104" v:mID="67" v:groupContext="shape" transform="translate(269.616,-113.386)">
<title>工作表.67</title>
<desc>CCI</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="7.82" y="325.61" class="st15" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>CCI</text> </g>
<g id="shape68-107" v:mID="68" v:groupContext="shape" transform="translate(665.854,270.762) rotate(127.093)">
<title>工作表.68</title>
<path d="M0 333.28 L52.84 333.28" class="st11"/>
</g>
<g id="shape69-112" v:mID="69" v:groupContext="shape" transform="translate(181.417,-34.9075)">
<title>圆角的矩形.69</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
</v:userDefs>
<path d="M8.22 333.28 L73.98 333.28 A8.22034 8.22034 -180 0 0 82.2 325.06 L82.2 263.55 A8.22034 8.22034 -180 0 0 73.98
255.33 L8.22 255.33 A8.22034 8.22034 -180 0 0 0 263.55 L0 325.06 A8.22034 8.22034 -180 0 0 8.22 333.28 Z"
class="st16"/>
</g>
<g id="shape70-114" v:mID="70" v:groupContext="shape" transform="translate(185.315,-34.9075)">
<title>工作表.70</title>
<desc>Project-VK1</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="36.4961" cy="319.107" width="73" height="28.3465"/>
<rect x="0" y="304.934" width="72.9921" height="28.3465" class="st6"/>
<text x="7.77" y="322.71" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Project-VK1</text> </g>
<g id="shape71-117" v:mID="71" v:groupContext="shape" transform="translate(197.008,-60.4193)">
<title>圆角的矩形.71</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
</v:userDefs>
<path d="M5.05 333.28 L45.41 333.28 A5.04513 5.04513 -180 0 0 50.45 328.23 L50.45 291.55 A5.04513 5.04513 -180 0 0 45.41
286.51 L5.05 286.51 A5.04513 5.04513 -180 0 0 0 291.55 L0 328.23 A5.04513 5.04513 -180 0 0 5.05 333.28 Z"
class="st17"/>
</g>
<g id="shape76-119" v:mID="76" v:groupContext="shape" transform="translate(202.814,-71.3327)">
<title>工作表.76</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="7.66" y="324.41" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape77-122" v:mID="77" v:groupContext="shape" transform="translate(536.955,91.1513) rotate(80.0665)">
<title>工作表.77</title>
<path d="M0 333.28 L78.62 333.28" class="st11"/>
</g>
<g id="shape78-127" v:mID="78" v:groupContext="shape" transform="translate(311.528,-34.9075)">
<title>圆角的矩形.78</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.11417322834646):1"/>
</v:userDefs>
<path d="M8.22 333.28 L73.98 333.28 A8.22034 8.22034 -180 0 0 82.2 325.06 L82.2 263.55 A8.22034 8.22034 -180 0 0 73.98
255.33 L8.22 255.33 A8.22034 8.22034 -180 0 0 0 263.55 L0 325.06 A8.22034 8.22034 -180 0 0 8.22 333.28 Z"
class="st16"/>
</g>
<g id="shape79-129" v:mID="79" v:groupContext="shape" transform="translate(315.779,-34.9075)">
<title>工作表.79</title>
<desc>Project-VKN</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="36.4961" cy="320.808" width="73" height="24.9449"/>
<rect x="0" y="308.335" width="72.9921" height="24.9449" class="st6"/>
<text x="6.94" y="324.41" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Project-VKN</text> </g>
<g id="shape80-132" v:mID="80" v:groupContext="shape" transform="translate(328.535,-60.4193)">
<title>圆角的矩形.80</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.07007239014478):1"/>
</v:userDefs>
<path d="M5.05 333.28 L45.41 333.28 A5.04513 5.04513 -180 0 0 50.45 328.23 L50.45 291.55 A5.04513 5.04513 -180 0 0 45.41
286.51 L5.05 286.51 A5.04513 5.04513 -180 0 0 0 291.55 L0 328.23 A5.04513 5.04513 -180 0 0 5.05 333.28 Z"
class="st17"/>
</g>
<g id="shape81-134" v:mID="81" v:groupContext="shape" transform="translate(334.342,-71.3327)">
<title>工作表.81</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="7.66" y="324.41" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape82-137" v:mID="82" v:groupContext="shape" transform="translate(695.381,214.76) rotate(100.926)">
<title>工作表.82</title>
<path d="M0 333.28 L75.87 333.28" class="st11"/>
</g>
<g id="shape83-142" v:mID="83" v:groupContext="shape" transform="translate(269.616,-68.5984)">
<title>工作表.83</title>
<desc>. . .</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="9.65" y="325.01" class="st14" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>. . .</text> </g>
<g id="shape86-145" v:mID="86" v:groupContext="shape" transform="translate(64.6135,-169.493)">
<title>工作表.86</title>
<desc>node</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="25.2261" cy="320.808" width="50.46" height="24.9449"/>
<rect x="0" y="308.335" width="50.4521" height="24.9449" class="st6"/>
<text x="10.7" y="325.01" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>node</text> </g>
<g id="shape87-148" v:mID="87" v:groupContext="shape" transform="translate(431.191,-170.09)">
<title>圆角的矩形.87</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<g id="shadow87-149" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M11.34 333.28 L102.05 333.28 A11.3384 11.3384 -180 0 0 113.39 321.94 L113.39 287.93 A11.3384 11.3384 -180
0 0 102.05 276.59 L11.34 276.59 A11.3384 11.3384 -180 0 0 0 287.93 L0 321.94 A11.3384 11.3384 -180 0
0 11.34 333.28 Z" class="st3"/>
</g>
<path d="M11.34 333.28 L102.05 333.28 A11.3384 11.3384 -180 0 0 113.39 321.94 L113.39 287.93 A11.3384 11.3384 -180 0
0 102.05 276.59 L11.34 276.59 A11.3384 11.3384 -180 0 0 0 287.93 L0 321.94 A11.3384 11.3384 -180 0 0 11.34
333.28 Z" class="st4"/>
</g>
<g id="shape88-153" v:mID="88" v:groupContext="shape" transform="translate(491.958,-188.739)">
<title>圆角的矩形.88</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
</v:userDefs>
<g id="shadow88-154" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0
0 39.54 304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39
333.28 Z" class="st3"/>
</g>
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0 0 39.54
304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39 333.28 Z"
class="st13"/>
</g>
<g id="shape89-158" v:mID="89" v:groupContext="shape" transform="translate(496.21,-190.723)">
<title>工作表.89</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="7.66" y="324.41" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape90-161" v:mID="90" v:groupContext="shape" transform="translate(440.58,-188.739)">
<title>圆角的矩形.90</title>
<v:userDefs>
<v:ud v:nameU="CTypeTopLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeTopRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotLeftSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CTypeBotRightSnip" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockHoriz" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockVert" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="CornerLockDiag" v:prompt="" v:val="VT0(0):5"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.15748031496063):24"/>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="TopLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="TopRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotLeftOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
<v:ud v:nameU="BotRightOffset" v:prompt="" v:val="VT0(0.061023622047244):1"/>
</v:userDefs>
<g id="shadow90-162" v:groupContext="shadow" v:shadowOffsetX="0.345598" v:shadowOffsetY="-1.97279" v:shadowType="1"
transform="matrix(1,0,0,1,0.345598,1.97279)" class="st2">
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0
0 39.54 304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39
333.28 Z" class="st3"/>
</g>
<path d="M4.39 333.28 L39.54 333.28 A4.39363 4.39363 -180 0 0 43.94 328.89 L43.94 309.33 A4.39363 4.39363 -180 0 0 39.54
304.93 L4.39 304.93 A4.39363 4.39363 -180 0 0 0 309.33 L0 328.89 A4.39363 4.39363 -180 0 0 4.39 333.28 Z"
class="st13"/>
</g>
<g id="shape91-166" v:mID="91" v:groupContext="shape" transform="translate(442.706,-189.306)">
<title>工作表.91</title>
<desc>POD</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="18.4252" cy="320.808" width="36.86" height="24.9449"/>
<rect x="0" y="308.335" width="36.8504" height="24.9449" class="st6"/>
<text x="7.66" y="324.41" class="st8" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>POD</text> </g>
<g id="shape92-169" v:mID="92" v:groupContext="shape" transform="translate(462.657,-165.614)">
<title>工作表.92</title>
<desc>node</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="25.2261" cy="320.808" width="50.46" height="24.9449"/>
<rect x="0" y="308.335" width="50.4521" height="24.9449" class="st6"/>
<text x="10.7" y="325.01" class="st18" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>node</text> </g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 51 KiB

412
providers/huawei/cci.go Normal file
View File

@@ -0,0 +1,412 @@
package huawei
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
"github.com/virtual-kubelet/virtual-kubelet/manager"
"github.com/virtual-kubelet/virtual-kubelet/providers/huawei/auth"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
const (
podAnnotationNamespaceKey = "virtual-kubelet-namespace"
podAnnotationPodNameKey = "virtual-kubelet-podname"
podAnnotationClusterNameKey = "virtual-kubelet-clustername"
podAnnotationUIDkey = "virtual-kubelet-uid"
podAnnotationNodeName = "virtual-kubelet-nodename"
podAnnotationCreationTimestamp = "virtual-kubelet-creationtimestamp"
)
var defaultApiEndpoint string = "https://cciback.cn-north-1.huaweicloud.com"
// CCIProvider implements the virtual-kubelet provider interface and communicates with Huawei's CCI APIs.
type CCIProvider struct {
appKey string
appSecret string
apiEndpoint string
region string
service string
project string
internalIP string
daemonEndpointPort int32
nodeName string
operatingSystem string
client *Client
resourceManager *manager.ResourceManager
cpu string
memory string
pods string
}
// Client represents the client config for Huawei.
type Client struct {
Signer auth.Signer
HTTPClient http.Client
}
// NewCCIProvider creates a new CCI provider.
func NewCCIProvider(config string, rm *manager.ResourceManager, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*CCIProvider, error) {
p := CCIProvider{}
if config != "" {
f, err := os.Open(config)
if err != nil {
return nil, err
}
defer f.Close()
if err := p.loadConfig(f); err != nil {
return nil, err
}
}
if appKey := os.Getenv("CCI_APP_KEP"); appKey != "" {
p.appKey = appKey
}
if p.appKey == "" {
return nil, errors.New("AppKey can not be empty please set CCI_APP_KEP")
}
if appSecret := os.Getenv("CCI_APP_SECRET"); appSecret != "" {
p.appSecret = appSecret
}
if p.appSecret == "" {
return nil, errors.New("AppSecret can not be empty please set CCI_APP_SECRET")
}
p.client = new(Client)
p.client.Signer = &auth.SignerHws{
AppKey: p.appKey,
AppSecret: p.appSecret,
Region: p.region,
Service: p.service,
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
p.client.HTTPClient = http.Client{
Transport: tr,
}
p.resourceManager = rm
p.apiEndpoint = defaultApiEndpoint
p.nodeName = nodeName
p.operatingSystem = operatingSystem
p.internalIP = internalIP
p.daemonEndpointPort = daemonEndpointPort
if err := p.createProject(); err != nil {
return nil, err
}
return &p, nil
}
func (p *CCIProvider) createProject() error {
// Create the createProject request url
uri := p.apiEndpoint + "/api/v1/namespaces"
// build the request
project := &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: p.project,
},
}
var bodyReader io.Reader
body, err := json.Marshal(project)
if err != nil {
return err
}
if body != nil {
bodyReader = bytes.NewReader(body)
}
r, err := http.NewRequest("POST", uri, bodyReader)
if err != nil {
return err
}
if err = p.signRequest(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
_, err = p.client.HTTPClient.Do(r)
return err
}
func (p *CCIProvider) signRequest(r *http.Request) error {
r.Header.Add("content-type", "application/json; charset=utf-8")
if err := p.client.Signer.Sign(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
return nil
}
func (p *CCIProvider) setPodAnnotations(pod *v1.Pod) {
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationNamespaceKey, pod.Namespace)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationClusterNameKey, pod.ClusterName)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationPodNameKey, pod.Name)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationUIDkey, string(pod.UID))
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationNodeName, pod.Spec.NodeName)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationCreationTimestamp, pod.CreationTimestamp.String())
pod.Namespace = p.project
pod.Name = pod.Namespace + "-" + pod.Name
pod.UID = ""
pod.Spec.NodeName = ""
pod.CreationTimestamp = metav1.Time{}
}
func (p *CCIProvider) deletePodAnnotations(pod *v1.Pod) error {
pod.Name = pod.Annotations[podAnnotationPodNameKey]
pod.Namespace = pod.Annotations[podAnnotationNamespaceKey]
pod.UID = types.UID(pod.Annotations[podAnnotationUIDkey])
pod.ClusterName = pod.Annotations[podAnnotationClusterNameKey]
pod.Spec.NodeName = pod.Annotations[podAnnotationNodeName]
if pod.Annotations[podAnnotationCreationTimestamp] != "" {
t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", pod.Annotations[podAnnotationCreationTimestamp])
if err != nil {
return err
}
podCreationTimestamp := metav1.NewTime(t)
pod.CreationTimestamp = podCreationTimestamp
}
delete(pod.Annotations, podAnnotationPodNameKey)
delete(pod.Annotations, podAnnotationNamespaceKey)
delete(pod.Annotations, podAnnotationUIDkey)
delete(pod.Annotations, podAnnotationClusterNameKey)
delete(pod.Annotations, podAnnotationNodeName)
delete(pod.Annotations, podAnnotationCreationTimestamp)
pod.Annotations = nil
return nil
}
// CreatePod takes a Kubernetes Pod and deploys it within the huawei CCI provider.
func (p *CCIProvider) CreatePod(pod *v1.Pod) error {
// Create the createPod request url
p.setPodAnnotations(pod)
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods"
// build the request
var bodyReader io.Reader
body, err := json.Marshal(pod)
if err != nil {
return err
}
if body != nil {
bodyReader = bytes.NewReader(body)
}
r, err := http.NewRequest("POST", uri, bodyReader)
if err != nil {
return err
}
if err = p.signRequest(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
_, err = p.client.HTTPClient.Do(r)
return err
}
// UpdatePod takes a Kubernetes Pod and updates it within the huawei CCI provider.
func (p *CCIProvider) UpdatePod(pod *v1.Pod) error {
return nil
}
// DeletePod takes a Kubernetes Pod and deletes it from the huawei CCI provider.
func (p *CCIProvider) DeletePod(pod *v1.Pod) error {
// Create the deletePod request url
podName := pod.Namespace + "-" + pod.Name
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods/" + podName
// build the request
r, err := http.NewRequest("DELETE", uri, nil)
if err != nil {
return err
}
if err = p.signRequest(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
_, err = p.client.HTTPClient.Do(r)
return err
}
// GetPod retrieves a pod by name from the huawei CCI provider.
func (p *CCIProvider) GetPod(namespace, name string) (*v1.Pod, error) {
// Create the getPod request url
podName := namespace + "-" + name
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods/" + podName
r, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, fmt.Errorf("Create get POD request failed: %v", err)
}
if err = p.signRequest(r); err != nil {
return nil, fmt.Errorf("Sign the request failed: %v", err)
}
resp, err := p.client.HTTPClient.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var pod v1.Pod
if err = json.Unmarshal(body, &pod); err != nil {
return nil, err
}
if err := p.deletePodAnnotations(&pod); err != nil {
return nil, err
}
return &pod, nil
}
// GetContainerLogs retrieves the logs of a container by name from the huawei CCI provider.
func (p *CCIProvider) GetContainerLogs(namespace, podName, containerName string, tail int) (string, error) {
return "", nil
}
// GetPodStatus retrieves the status of a pod by name from the huawei CCI provider.
func (p *CCIProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
pod, err := p.GetPod(namespace, name)
if err != nil {
return nil, err
}
if pod == nil {
return nil, nil
}
return &pod.Status, nil
}
// GetPods retrieves a list of all pods running on the huawei CCI provider.
func (p *CCIProvider) GetPods() ([]*v1.Pod, error) {
// Create the getPod request url
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods"
r, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, fmt.Errorf("Create get POD request failed: %v", err)
}
if err = p.signRequest(r); err != nil {
return nil, fmt.Errorf("Sign the request failed: %v", err)
}
resp, err := p.client.HTTPClient.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var pods []*v1.Pod
if err = json.Unmarshal(body, &pods); err != nil {
return nil, err
}
for _, pod := range pods {
if err := p.deletePodAnnotations(pod); err != nil {
return nil, err
}
}
return pods, nil
}
// Capacity returns a resource list with the capacity constraints of the huawei CCI provider.
func (p *CCIProvider) Capacity() v1.ResourceList {
return v1.ResourceList{
"cpu": resource.MustParse(p.cpu),
"memory": resource.MustParse(p.memory),
"pods": resource.MustParse(p.pods),
}
}
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), which is
// polled periodically to update the node status within Kubernetes.
func (p *CCIProvider) NodeConditions() []v1.NodeCondition {
// TODO: Make these dynamic and augment with custom CCI specific conditions of interest
return []v1.NodeCondition{
{
Type: "Ready",
Status: v1.ConditionTrue,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletReady",
Message: "kubelet is ready.",
},
{
Type: "OutOfDisk",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasSufficientDisk",
Message: "kubelet has sufficient disk space available",
},
{
Type: "MemoryPressure",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasSufficientMemory",
Message: "kubelet has sufficient memory available",
},
{
Type: "DiskPressure",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasNoDiskPressure",
Message: "kubelet has no disk pressure",
},
{
Type: "NetworkUnavailable",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "RouteCreated",
Message: "RouteController created a route",
},
}
}
// NodeAddresses returns a list of addresses for the node status
// within Kubernetes.
func (p *CCIProvider) NodeAddresses() []v1.NodeAddress {
// TODO: Make these dynamic and augment with custom CCI specific conditions of interest
return []v1.NodeAddress{
{
Type: "InternalIP",
Address: p.internalIP,
},
}
}
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
// within Kubernetes.
func (p *CCIProvider) NodeDaemonEndpoints() *v1.NodeDaemonEndpoints {
return &v1.NodeDaemonEndpoints{
KubeletEndpoint: v1.DaemonEndpoint{
Port: p.daemonEndpointPort,
},
}
}
// OperatingSystem returns the operating system the huawei CCI provider is for.
func (p *CCIProvider) OperatingSystem() string {
return p.operatingSystem
}

View File

@@ -0,0 +1,9 @@
# example configuration file for Huawei CCI virtual-kubelet provider.
Project = "default"
Region = "southchina"
Service = "CCI"
OperatingSystem = "Linux"
CPU = "20"
Memory = "100Gi"
Pods = "20"

151
providers/huawei/cciMock.go Normal file
View File

@@ -0,0 +1,151 @@
package huawei
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"github.com/gorilla/mux"
"k8s.io/api/core/v1"
)
// CCIMock implements a CCI service mock server.
type CCIMock struct {
server *httptest.Server
OnCreateProject func(*v1.Namespace) (int, interface{})
OnCreatePod func(*v1.Pod) (int, interface{})
OnGetPods func() (int, interface{})
OnGetPod func(string, string) (int, interface{})
}
// fakeSigner signature HWS meta
type fakeSigner struct {
AppKey string
AppSecret string
Region string
Service string
}
// Sign set Authorization header
func (s *fakeSigner) Sign(r *http.Request) error {
return nil
}
const (
cciProjectRoute = "/api/v1/namespaces"
cciPodsRoute = cciProjectRoute + "/{namespaceID}/pods"
cciPodRoute = cciPodsRoute + "/{podID}"
)
// NewCCIMock creates a CCI service mock server.
func NewCCIMock() *CCIMock {
mock := new(CCIMock)
mock.start()
return mock
}
// Start the CCI service mock service.
func (mock *CCIMock) start() {
if mock.server != nil {
return
}
router := mux.NewRouter()
router.HandleFunc(
cciProjectRoute,
func(w http.ResponseWriter, r *http.Request) {
var ns v1.Namespace
if err := json.NewDecoder(r.Body).Decode(&ns); err != nil {
panic(err)
}
if mock.OnCreatePod != nil {
statusCode, response := mock.OnCreateProject(&ns)
w.WriteHeader(statusCode)
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(response)
w.Write(b.Bytes())
return
}
w.WriteHeader(http.StatusNotImplemented)
}).Methods("PUT")
router.HandleFunc(
cciPodsRoute,
func(w http.ResponseWriter, r *http.Request) {
var pod v1.Pod
if err := json.NewDecoder(r.Body).Decode(&pod); err != nil {
panic(err)
}
if mock.OnCreatePod != nil {
statusCode, response := mock.OnCreatePod(&pod)
w.WriteHeader(statusCode)
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(response)
w.Write(b.Bytes())
return
}
w.WriteHeader(http.StatusNotImplemented)
}).Methods("PUT")
router.HandleFunc(
cciPodRoute,
func(w http.ResponseWriter, r *http.Request) {
namespace, _ := mux.Vars(r)["namespaceID"]
podname, _ := mux.Vars(r)["podID"]
if mock.OnGetPod != nil {
statusCode, response := mock.OnGetPod(namespace, podname)
w.WriteHeader(statusCode)
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(response)
w.Write(b.Bytes())
return
}
w.WriteHeader(http.StatusNotImplemented)
}).Methods("GET")
router.HandleFunc(
cciPodsRoute,
func(w http.ResponseWriter, r *http.Request) {
if mock.OnGetPods != nil {
statusCode, response := mock.OnGetPods()
w.WriteHeader(statusCode)
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(response)
w.Write(b.Bytes())
return
}
w.WriteHeader(http.StatusNotImplemented)
}).Methods("GET")
mock.server = httptest.NewServer(router)
}
// GetServerURL returns the mock server URL.
func (mock *CCIMock) GetServerURL() string {
if mock.server != nil {
return mock.server.URL
}
panic("Mock server is not initialized.")
}
// Close terminates the CCI mock server.
func (mock *CCIMock) Close() {
if mock.server != nil {
mock.server.Close()
mock.server = nil
}
}

View File

@@ -0,0 +1,212 @@
package huawei
import (
"net/http"
"os"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
)
const (
fakeAppKey = "Whj8f5RAHsvQahveqCdo"
fakeAppSecret = "ymW5JgrdwrIvRS76YxyIqHNXe9s5ocIhaWWvPUhx"
fakeRegion = "southchina"
fakeService = "default"
fakeProject = "vk-project"
fakeNodeName = "vk"
)
// TestCreateProject test create project.
func TestCreateProject(t *testing.T) {
cciServerMocker, provider, err := prepareMocks()
if err != nil {
t.Fatal("Unable to prepare the mocks", err)
}
cciServerMocker.OnCreateProject = func(ns *v1.Namespace) (int, interface{}) {
assert.NotNil(t, ns, "Project is nil")
assert.Equal(t, fakeProject, ns.Name, "pod.Annotations[\"virtual-kubelet-podname\"] is not expected")
return http.StatusOK, &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: fakeProject,
},
}
}
if err := provider.createProject(); err != nil {
t.Fatal("Failed to create project", err)
}
}
// TestCreatePod test create pod.
func TestCreatePod(t *testing.T) {
cciServerMocker, provider, err := prepareMocks()
if err != nil {
t.Fatal("Unable to prepare the mocks", err)
}
podName := "pod-" + string(uuid.NewUUID())
podNamespace := "ns-" + string(uuid.NewUUID())
cciServerMocker.OnCreatePod = func(pod *v1.Pod) (int, interface{}) {
assert.NotNil(t, pod, "Pod is nil")
assert.NotNil(t, pod.Annotations, "pod.Annotations is expected")
assert.Equal(t, podName, pod.Annotations[podAnnotationPodNameKey], "pod.Annotations[\"virtual-kubelet-podname\"] is not expected")
assert.Equal(t, podNamespace, pod.Annotations[podAnnotationNamespaceKey], "pod.Annotations[\"virtual-kubelet-namespace\"] is not expected")
assert.Equal(t, 1, len(pod.Spec.Containers), "1 Container is expected")
assert.Equal(t, "nginx", pod.Spec.Containers[0].Name, "Container nginx is expected")
return http.StatusOK, pod
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: podNamespace,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
v1.Container{
Name: "nginx",
},
},
},
}
if err := provider.CreatePod(pod); err != nil {
t.Fatal("Failed to create pod", err)
}
}
// Tests get pod.
func TestGetPod(t *testing.T) {
cciServerMocker, provider, err := prepareMocks()
if err != nil {
t.Fatal("Unable to prepare the mocks", err)
}
podName := "pod-" + string(uuid.NewUUID())
podNamespace := "ns-" + string(uuid.NewUUID())
cciServerMocker.OnGetPod = func(namespace, name string) (int, interface{}) {
annotations := map[string]string{
podAnnotationPodNameKey: "podname",
podAnnotationNamespaceKey: "podnamespaces",
podAnnotationUIDkey: "poduid",
podAnnotationClusterNameKey: "podclustername",
podAnnotationNodeName: "podnodename",
}
return http.StatusOK, &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: podNamespace,
Annotations: annotations,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
v1.Container{
Name: "nginx",
},
},
},
}
}
pod, err := provider.GetPod(podNamespace, podName)
if err != nil {
t.Fatal("Failed to get pod", err)
}
assert.NotNil(t, pod, "Response pod should not be nil")
assert.NotNil(t, pod.Spec.Containers, "Containers should not be nil")
assert.Equal(t, pod.Name, "podname", "Pod name is not expected")
assert.Equal(t, pod.Namespace, "podnamespaces", "Pod namespace is not expected")
assert.Nil(t, pod.Annotations, "Pod Annotations should be nil")
assert.Equal(t, string(pod.UID), "poduid", "Pod UID is not expected")
assert.Equal(t, pod.ClusterName, "podclustername", "Pod clustername is not expected")
assert.Equal(t, pod.Spec.NodeName, "podnodename", "Pod node name is not expected")
}
// Tests get pod.
func TestGetPods(t *testing.T) {
cciServerMocker, provider, err := prepareMocks()
if err != nil {
t.Fatal("Unable to prepare the mocks", err)
}
podName := "pod-" + string(uuid.NewUUID())
podNamespace := "ns-" + string(uuid.NewUUID())
cciServerMocker.OnGetPods = func() (int, interface{}) {
annotations := map[string]string{
podAnnotationPodNameKey: "podname",
podAnnotationNamespaceKey: "podnamespaces",
podAnnotationUIDkey: "poduid",
podAnnotationClusterNameKey: "podclustername",
podAnnotationNodeName: "podnodename",
}
pod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: podNamespace,
Annotations: annotations,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
v1.Container{
Name: "nginx",
},
},
},
}
return http.StatusOK, []v1.Pod{pod}
}
pods, err := provider.GetPods()
if err != nil {
t.Fatal("Failed to get pods", err)
}
pod := pods[0]
assert.NotNil(t, pod, "Response pod should not be nil")
assert.NotNil(t, pod.Spec.Containers, "Containers should not be nil")
assert.Equal(t, pod.Name, "podname", "Pod name is not expected")
assert.Equal(t, pod.Namespace, "podnamespaces", "Pod namespace is not expected")
assert.Nil(t, pod.Annotations, "Pod Annotations should be nil")
assert.Equal(t, string(pod.UID), "poduid", "Pod UID is not expected")
assert.Equal(t, pod.ClusterName, "podclustername", "Pod clustername is not expected")
assert.Equal(t, pod.Spec.NodeName, "podnodename", "Pod node name is not expected")
}
func prepareMocks() (*CCIMock, *CCIProvider, error) {
cciServerMocker := NewCCIMock()
os.Setenv("CCI_APP_KEP", fakeAppKey)
os.Setenv("CCI_APP_SECRET", fakeAppSecret)
defaultApiEndpoint = cciServerMocker.GetServerURL()
provider, err := NewCCIProvider("cci.toml", nil, fakeNodeName, "Linux", "0.0.0.0", 10250)
if err != nil {
return nil, nil, err
}
provider.project = fakeProject
provider.client.Signer = &fakeSigner{
AppKey: fakeAppKey,
AppSecret: fakeAppSecret,
Region: fakeRegion,
Service: fakeService,
}
return cciServerMocker, provider, nil
}

View File

@@ -0,0 +1,54 @@
package huawei
import (
"io"
"github.com/BurntSushi/toml"
"github.com/virtual-kubelet/virtual-kubelet/providers"
"k8s.io/apimachinery/pkg/util/uuid"
)
type providerConfig struct {
Project string
Region string
Service string
OperatingSystem string
CPU string
Memory string
Pods string
}
func (p *CCIProvider) loadConfig(r io.Reader) error {
var config providerConfig
if _, err := toml.DecodeReader(r, &config); err != nil {
return err
}
p.apiEndpoint = defaultApiEndpoint
p.service = "CCI"
p.region = config.Region
if p.region == "" {
p.region = "southchina"
}
p.cpu = config.CPU
if p.cpu == "" {
p.cpu = "20"
}
p.memory = config.Memory
if p.memory == "" {
p.memory = "100Gi"
}
p.pods = config.Pods
if p.pods == "" {
p.pods = "20"
}
p.project = config.Project
if p.project == "" {
p.project = string(uuid.NewUUID())
}
p.operatingSystem = config.OperatingSystem
if p.operatingSystem == "" {
p.operatingSystem = providers.OperatingSystemLinux
}
return nil
}

9
vendor/github.com/pborman/uuid/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,9 @@
language: go
go:
- 1.4.3
- 1.5.3
- tip
script:
- go test -v ./...

10
vendor/github.com/pborman/uuid/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.

1
vendor/github.com/pborman/uuid/CONTRIBUTORS generated vendored Normal file
View File

@@ -0,0 +1 @@
Paul Borman <borman@google.com>

27
vendor/github.com/pborman/uuid/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

13
vendor/github.com/pborman/uuid/README.md generated vendored Normal file
View File

@@ -0,0 +1,13 @@
This project was automatically exported from code.google.com/p/go-uuid
# uuid ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on [RFC 4122](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services.
###### Install
`go get github.com/pborman/uuid`
###### Documentation
[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](http://godoc.org/github.com/pborman/uuid)
Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here:
http://godoc.org/github.com/pborman/uuid

84
vendor/github.com/pborman/uuid/dce.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) UUID {
uuid := NewUUID()
if uuid != nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCEPerson(Person, uint32(os.Getuid()))
func NewDCEPerson() UUID {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCEGroup(Group, uint32(os.Getgid()))
func NewDCEGroup() UUID {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID or false.
func (uuid UUID) Domain() (Domain, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return Domain(uuid[9]), true
}
// Id returns the id for a Version 2 UUID or false.
func (uuid UUID) Id() (uint32, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return binary.BigEndian.Uint32(uuid[0:4]), true
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

8
vendor/github.com/pborman/uuid/doc.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The uuid package generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
package uuid

53
vendor/github.com/pborman/uuid/hash.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known Name Space IDs and UUIDs
var (
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
NIL = Parse("00000000-0000-0000-0000-000000000000")
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space)
h.Write([]byte(data))
s := h.Sum(nil)
uuid := make([]byte, 16)
copy(uuid, s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data.
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data.
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

83
vendor/github.com/pborman/uuid/marshal.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"errors"
"fmt"
)
// MarshalText implements encoding.TextMarshaler.
func (u UUID) MarshalText() ([]byte, error) {
if len(u) != 16 {
return nil, nil
}
var js [36]byte
encodeHex(js[:], u)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (u *UUID) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
id := Parse(string(data))
if id == nil {
return errors.New("invalid UUID")
}
*u = id
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (u UUID) MarshalBinary() ([]byte, error) {
return u[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (u *UUID) UnmarshalBinary(data []byte) error {
if len(data) == 0 {
return nil
}
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
var id [16]byte
copy(id[:], data)
*u = id[:]
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (u Array) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], u[:])
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (u *Array) UnmarshalText(data []byte) error {
id := Parse(string(data))
if id == nil {
return errors.New("invalid UUID")
}
*u = id.Array()
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (u Array) MarshalBinary() ([]byte, error) {
return u[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (u *Array) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(u[:], data)
return nil
}

124
vendor/github.com/pborman/uuid/marshal_test.go generated vendored Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"encoding/json"
"reflect"
"testing"
)
var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
var testArray = testUUID.Array()
func TestJSON(t *testing.T) {
type S struct {
ID1 UUID
ID2 UUID
}
s1 := S{ID1: testUUID}
data, err := json.Marshal(&s1)
if err != nil {
t.Fatal(err)
}
var s2 S
if err := json.Unmarshal(data, &s2); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(&s1, &s2) {
t.Errorf("got %#v, want %#v", s2, s1)
}
}
func TestJSONArray(t *testing.T) {
type S struct {
ID1 Array
ID2 Array
}
s1 := S{ID1: testArray}
data, err := json.Marshal(&s1)
if err != nil {
t.Fatal(err)
}
var s2 S
if err := json.Unmarshal(data, &s2); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(&s1, &s2) {
t.Errorf("got %#v, want %#v", s2, s1)
}
}
func TestMarshal(t *testing.T) {
data, err := testUUID.MarshalBinary()
if err != nil {
t.Fatalf("MarhsalBinary returned unexpected error %v", err)
}
if !bytes.Equal(data, testUUID) {
t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID)
}
var u UUID
u.UnmarshalBinary(data)
if !Equal(data, u) {
t.Fatalf("UnmarhsalBinary returns %v, want %v", u, testUUID)
}
}
func TestMarshalArray(t *testing.T) {
data, err := testArray.MarshalBinary()
if err != nil {
t.Fatalf("MarhsalBinary returned unexpected error %v", err)
}
if !bytes.Equal(data, testUUID) {
t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID)
}
var a Array
a.UnmarshalBinary(data)
if a != testArray {
t.Fatalf("UnmarhsalBinary returns %v, want %v", a, testArray)
}
}
func TestMarshalTextArray(t *testing.T) {
data, err := testArray.MarshalText()
if err != nil {
t.Fatalf("MarhsalText returned unexpected error %v", err)
}
var a Array
a.UnmarshalText(data)
if a != testArray {
t.Fatalf("UnmarhsalText returns %v, want %v", a, testArray)
}
}
func BenchmarkUUID_MarshalJSON(b *testing.B) {
x := &struct {
UUID UUID `json:"uuid"`
}{}
x.UUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if x.UUID == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
js, err := json.Marshal(x)
if err != nil {
b.Fatalf("marshal json: %#v (%v)", js, err)
}
}
}
func BenchmarkUUID_UnmarshalJSON(b *testing.B) {
js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`)
var x *struct {
UUID UUID `json:"uuid"`
}
for i := 0; i < b.N; i++ {
err := json.Unmarshal(js, &x)
if err != nil {
b.Fatalf("marshal json: %#v (%v)", js, err)
}
}
}

108
vendor/github.com/pborman/uuid/node.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"sync"
)
var (
nodeMu sync.Mutex
ifname string // name of interface being used
nodeID []byte // hardware for version 1 UUIDs
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
defer nodeMu.Unlock()
nodeMu.Lock()
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID != nil {
return true
}
return setNodeInterface(name)
}
func setNodeInterface(name string) bool {
iname, addr := getHardwareInterface(name) // null implementation for js
if iname != "" && setNodeID(addr) {
ifname = iname
return true
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
if nodeID == nil {
nodeID = make([]byte, 6)
}
randomBits(nodeID)
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID == nil {
setNodeInterface("")
}
nid := make([]byte, 6)
copy(nid, nodeID)
return nid
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
if setNodeID(id) {
ifname = "user"
return true
}
return false
}
func setNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
if nodeID == nil {
nodeID = make([]byte, 6)
}
copy(nodeID, id)
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
if len(uuid) != 16 {
return nil
}
node := make([]byte, 6)
copy(node, uuid[10:])
return node
}

12
vendor/github.com/pborman/uuid/node_js.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build js
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This remvoves the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

36
vendor/github.com/pborman/uuid/node_net.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !js
package uuid
import "net"
var interfaces []net.Interface // cached list of interfaces
// getHardwareInterface returns the name and hardware address of interface name.
// If name is "" then the name and hardware address of one of the system's
// interfaces is returned. If no interfaces are found (name does not exist or
// there are no interfaces) then "", nil is returned.
//
// Only addresses of at least 6 bytes are returned.
func getHardwareInterface(name string) (string, []byte) {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil {
return "", nil
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
if setNodeID(ifs.HardwareAddr) {
ifname = ifs.Name
return ifname, nodeID
}
}
}
return "", nil
}

66
vendor/github.com/pborman/uuid/seq_test.go generated vendored Normal file
View File

@@ -0,0 +1,66 @@
// Copyright 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"flag"
"runtime"
"testing"
"time"
)
// This test is only run when --regressions is passed on the go test line.
var regressions = flag.Bool("regressions", false, "run uuid regression tests")
// TestClockSeqRace tests for a particular race condition of returning two
// identical Version1 UUIDs. The duration of 1 minute was chosen as the race
// condition, before being fixed, nearly always occured in under 30 seconds.
func TestClockSeqRace(t *testing.T) {
if !*regressions {
t.Skip("skipping regression tests")
}
duration := time.Minute
done := make(chan struct{})
defer close(done)
ch := make(chan UUID, 10000)
ncpu := runtime.NumCPU()
switch ncpu {
case 0, 1:
// We can't run the test effectively.
t.Skip("skipping race test, only one CPU detected")
return
default:
runtime.GOMAXPROCS(ncpu)
}
for i := 0; i < ncpu; i++ {
go func() {
for {
select {
case <-done:
return
case ch <- NewUUID():
}
}
}()
}
uuids := make(map[string]bool)
cnt := 0
start := time.Now()
for u := range ch {
s := u.String()
if uuids[s] {
t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s)
return
}
uuids[s] = true
if time.Since(start) > duration {
return
}
cnt++
}
}

66
vendor/github.com/pborman/uuid/sql.go generated vendored Normal file
View File

@@ -0,0 +1,66 @@
// Copyright 2015 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"database/sql/driver"
"errors"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src.(type) {
case string:
// if an empty UUID comes from a table, we return a null UUID
if src.(string) == "" {
return nil
}
// see uuid.Parse for required string format
parsed := Parse(src.(string))
if parsed == nil {
return errors.New("Scan: invalid UUID format")
}
*uuid = parsed
case []byte:
b := src.([]byte)
// if an empty UUID comes from a table, we return a null UUID
if len(b) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(b) == 16 {
*uuid = UUID(b)
} else {
u := Parse(string(b))
if u == nil {
return errors.New("Scan: invalid UUID format")
}
*uuid = u
}
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}

96
vendor/github.com/pborman/uuid/sql_test.go generated vendored Normal file
View File

@@ -0,0 +1,96 @@
// Copyright 2015 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"strings"
"testing"
)
func TestScan(t *testing.T) {
var stringTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d479"
var byteTest []byte = Parse(stringTest)
var badTypeTest int = 6
var invalidTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d4"
// sunny day tests
var uuid UUID
err := (&uuid).Scan(stringTest)
if err != nil {
t.Fatal(err)
}
err = (&uuid).Scan([]byte(stringTest))
if err != nil {
t.Fatal(err)
}
err = (&uuid).Scan(byteTest)
if err != nil {
t.Fatal(err)
}
// bad type tests
err = (&uuid).Scan(badTypeTest)
if err == nil {
t.Error("int correctly parsed and shouldn't have")
}
if !strings.Contains(err.Error(), "unable to scan type") {
t.Error("attempting to parse an int returned an incorrect error message")
}
// invalid/incomplete uuids
err = (&uuid).Scan(invalidTest)
if err == nil {
t.Error("invalid uuid was parsed without error")
}
if !strings.Contains(err.Error(), "invalid UUID") {
t.Error("attempting to parse an invalid UUID returned an incorrect error message")
}
err = (&uuid).Scan(byteTest[:len(byteTest)-2])
if err == nil {
t.Error("invalid byte uuid was parsed without error")
}
if !strings.Contains(err.Error(), "invalid UUID") {
t.Error("attempting to parse an invalid byte UUID returned an incorrect error message")
}
// empty tests
uuid = nil
var emptySlice []byte
err = (&uuid).Scan(emptySlice)
if err != nil {
t.Fatal(err)
}
if uuid != nil {
t.Error("UUID was not nil after scanning empty byte slice")
}
uuid = nil
var emptyString string
err = (&uuid).Scan(emptyString)
if err != nil {
t.Fatal(err)
}
if uuid != nil {
t.Error("UUID was not nil after scanning empty string")
}
}
func TestValue(t *testing.T) {
stringTest := "f47ac10b-58cc-0372-8567-0e02b2c3d479"
uuid := Parse(stringTest)
val, _ := uuid.Value()
if val != stringTest {
t.Error("Value() did not return expected string")
}
}

132
vendor/github.com/pborman/uuid/time.go generated vendored Normal file
View File

@@ -0,0 +1,132 @@
// Copyright 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
timeMu sync.Mutex
lasttime uint64 // last time we returned
clock_seq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) {
defer timeMu.Unlock()
timeMu.Lock()
return getTime()
}
func getTime() (Time, uint16, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clock_seq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), clock_seq, nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence a new random
// clock sequence is generated the first time a clock sequence is requested by
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
// for
func ClockSequence() int {
defer timeMu.Unlock()
timeMu.Lock()
return clockSequence()
}
func clockSequence() int {
if clock_seq == 0 {
setClockSequence(-1)
}
return int(clock_seq & 0x3fff)
}
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer timeMu.Unlock()
timeMu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
old_seq := clock_seq
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if old_seq != clock_seq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. It returns false if uuid is not valid. The time is only well defined
// for version 1 and 2 UUIDs.
func (uuid UUID) Time() (Time, bool) {
if len(uuid) != 16 {
return 0, false
}
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time), true
}
// ClockSequence returns the clock sequence encoded in uuid. It returns false
// if uuid is not valid. The clock sequence is only well defined for version 1
// and 2 UUIDs.
func (uuid UUID) ClockSequence() (int, bool) {
if len(uuid) != 16 {
return 0, false
}
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
}

43
vendor/github.com/pborman/uuid/util.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts the the first two hex bytes of x into a byte.
func xtob(x string) (byte, bool) {
b1 := xvalues[x[0]]
b2 := xvalues[x[1]]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

201
vendor/github.com/pborman/uuid/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,201 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"strings"
)
// Array is a pass-by-value UUID that can be used as an effecient key in a map.
type Array [16]byte
// UUID converts uuid into a slice.
func (uuid Array) UUID() UUID {
return uuid[:]
}
// String returns the string representation of uuid,
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
func (uuid Array) String() string {
return uuid.UUID().String()
}
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID []byte
// A Version represents a UUIDs version.
type Version byte
// A Variant represents a UUIDs variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// New returns a new random (version 4) UUID as a string. It is a convenience
// function for NewRandom().String().
func New() string {
return NewRandom().String()
}
// Parse decodes s into a UUID or returns nil. Both the UUID form of
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
func Parse(s string) UUID {
if len(s) == 36+9 {
if strings.ToLower(s[:9]) != "urn:uuid:" {
return nil
}
s = s[9:]
} else if len(s) != 36 {
return nil
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return nil
}
var uuid [16]byte
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
if v, ok := xtob(s[x:]); !ok {
return nil
} else {
uuid[i] = v
}
}
return uuid[:]
}
// Equal returns true if uuid1 and uuid2 are equal.
func Equal(uuid1, uuid2 UUID) bool {
return bytes.Equal(uuid1, uuid2)
}
// Array returns an array representation of uuid that can be used as a map key.
// Array panics if uuid is not valid.
func (uuid UUID) Array() Array {
if len(uuid) != 16 {
panic("invalid uuid")
}
var a Array
copy(a[:], uuid)
return a
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
if len(uuid) != 16 {
return ""
}
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
if len(uuid) != 16 {
return ""
}
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst[:], uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid. It returns Invalid if
// uuid is invalid.
func (uuid UUID) Variant() Variant {
if len(uuid) != 16 {
return Invalid
}
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid. It returns false if uuid is not
// valid.
func (uuid UUID) Version() (Version, bool) {
if len(uuid) != 16 {
return 0, false
}
return Version(uuid[6] >> 4), true
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}

552
vendor/github.com/pborman/uuid/uuid_test.go generated vendored Normal file
View File

@@ -0,0 +1,552 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"fmt"
"os"
"runtime"
"strings"
"testing"
"time"
)
type test struct {
in string
version Version
variant Variant
isuuid bool
}
var tests = []test{
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
}
var constants = []struct {
c interface{}
name string
}{
{Person, "Person"},
{Group, "Group"},
{Org, "Org"},
{Invalid, "Invalid"},
{RFC4122, "RFC4122"},
{Reserved, "Reserved"},
{Microsoft, "Microsoft"},
{Future, "Future"},
{Domain(17), "Domain17"},
{Variant(42), "BadVariant42"},
}
func testTest(t *testing.T, in string, tt test) {
uuid := Parse(in)
if ok := (uuid != nil); ok != tt.isuuid {
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
}
if uuid == nil {
return
}
if v := uuid.Variant(); v != tt.variant {
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
}
if v, _ := uuid.Version(); v != tt.version {
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
}
}
func TestUUID(t *testing.T) {
for _, tt := range tests {
testTest(t, tt.in, tt)
testTest(t, strings.ToUpper(tt.in), tt)
}
}
func TestConstants(t *testing.T) {
for x, tt := range constants {
v, ok := tt.c.(fmt.Stringer)
if !ok {
t.Errorf("%x: %v: not a stringer", x, v)
} else if s := v.String(); s != tt.name {
v, _ := tt.c.(int)
t.Errorf("%x: Constant %T:%d gives %q, expected %q", x, tt.c, v, s, tt.name)
}
}
}
func TestRandomUUID(t *testing.T) {
m := make(map[string]bool)
for x := 1; x < 32; x++ {
uuid := NewRandom()
s := uuid.String()
if m[s] {
t.Errorf("NewRandom returned duplicated UUID %s", s)
}
m[s] = true
if v, _ := uuid.Version(); v != 4 {
t.Errorf("Random UUID of version %s", v)
}
if uuid.Variant() != RFC4122 {
t.Errorf("Random UUID is variant %d", uuid.Variant())
}
}
}
func TestNew(t *testing.T) {
m := make(map[string]bool)
for x := 1; x < 32; x++ {
s := New()
if m[s] {
t.Errorf("New returned duplicated UUID %s", s)
}
m[s] = true
uuid := Parse(s)
if uuid == nil {
t.Errorf("New returned %q which does not decode", s)
continue
}
if v, _ := uuid.Version(); v != 4 {
t.Errorf("Random UUID of version %s", v)
}
if uuid.Variant() != RFC4122 {
t.Errorf("Random UUID is variant %d", uuid.Variant())
}
}
}
func clockSeq(t *testing.T, uuid UUID) int {
seq, ok := uuid.ClockSequence()
if !ok {
t.Fatalf("%s: invalid clock sequence", uuid)
}
return seq
}
func TestClockSeq(t *testing.T) {
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
monTime := time.Now()
timeNow = func() time.Time {
monTime = monTime.Add(1 * time.Second)
return monTime
}
SetClockSequence(-1)
uuid1 := NewUUID()
uuid2 := NewUUID()
if clockSeq(t, uuid1) != clockSeq(t, uuid2) {
t.Errorf("clock sequence %d != %d", clockSeq(t, uuid1), clockSeq(t, uuid2))
}
SetClockSequence(-1)
uuid2 = NewUUID()
// Just on the very off chance we generated the same sequence
// two times we try again.
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
SetClockSequence(-1)
uuid2 = NewUUID()
}
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
t.Errorf("Duplicate clock sequence %d", clockSeq(t, uuid1))
}
SetClockSequence(0x1234)
uuid1 = NewUUID()
if seq := clockSeq(t, uuid1); seq != 0x1234 {
t.Errorf("%s: expected seq 0x1234 got 0x%04x", uuid1, seq)
}
}
func TestCoding(t *testing.T) {
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
data := UUID{
0x7d, 0x44, 0x48, 0x40,
0x9d, 0xc0,
0x11, 0xd1,
0xb2, 0x45,
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
}
if v := data.String(); v != text {
t.Errorf("%x: encoded to %s, expected %s", data, v, text)
}
if v := data.URN(); v != urn {
t.Errorf("%x: urn is %s, expected %s", data, v, urn)
}
uuid := Parse(text)
if !Equal(uuid, data) {
t.Errorf("%s: decoded to %s, expected %s", text, uuid, data)
}
}
func TestVersion1(t *testing.T) {
uuid1 := NewUUID()
uuid2 := NewUUID()
if Equal(uuid1, uuid2) {
t.Errorf("%s:duplicate uuid", uuid1)
}
if v, _ := uuid1.Version(); v != 1 {
t.Errorf("%s: version %s expected 1", uuid1, v)
}
if v, _ := uuid2.Version(); v != 1 {
t.Errorf("%s: version %s expected 1", uuid2, v)
}
n1 := uuid1.NodeID()
n2 := uuid2.NodeID()
if !bytes.Equal(n1, n2) {
t.Errorf("Different nodes %x != %x", n1, n2)
}
t1, ok := uuid1.Time()
if !ok {
t.Errorf("%s: invalid time", uuid1)
}
t2, ok := uuid2.Time()
if !ok {
t.Errorf("%s: invalid time", uuid2)
}
q1, ok := uuid1.ClockSequence()
if !ok {
t.Errorf("%s: invalid clock sequence", uuid1)
}
q2, ok := uuid2.ClockSequence()
if !ok {
t.Errorf("%s: invalid clock sequence", uuid2)
}
switch {
case t1 == t2 && q1 == q2:
t.Error("time stopped")
case t1 > t2 && q1 == q2:
t.Error("time reversed")
case t1 < t2 && q1 != q2:
t.Error("clock sequence chaned unexpectedly")
}
}
func TestNode(t *testing.T) {
// This test is mostly to make sure we don't leave nodeMu locked.
ifname = ""
if ni := NodeInterface(); ni != "" {
t.Errorf("NodeInterface got %q, want %q", ni, "")
}
nodeID = nil // Reset global state for next test
if SetNodeInterface("xyzzy") {
t.Error("SetNodeInterface succeeded on a bad interface name")
}
nodeID = nil // Reset global state for next test
if !SetNodeInterface("") {
t.Error("SetNodeInterface failed")
}
if runtime.GOARCH != "js" {
if ni := NodeInterface(); ni == "" {
t.Error("NodeInterface returned an empty string")
}
}
ni := NodeID()
if len(ni) != 6 {
t.Errorf("ni got %d bytes, want 6", len(ni))
}
hasData := false
for _, b := range ni {
if b != 0 {
hasData = true
}
}
if !hasData {
t.Error("nodeid is all zeros")
}
id := []byte{1, 2, 3, 4, 5, 6, 7, 8}
SetNodeID(id)
ni = NodeID()
if !bytes.Equal(ni, id[:6]) {
t.Errorf("got nodeid %v, want %v", ni, id[:6])
}
if ni := NodeInterface(); ni != "user" {
t.Errorf("got inteface %q, want %q", ni, "user")
}
}
func TestNodeAndTime(t *testing.T) {
// Time is February 5, 1998 12:30:23.136364800 AM GMT
uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
ts, ok := uuid.Time()
if ok {
c := time.Unix(ts.UnixTime())
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
if !c.Equal(want) {
t.Errorf("Got time %v, want %v", c, want)
}
} else {
t.Errorf("%s: bad time", uuid)
}
if !bytes.Equal(node, uuid.NodeID()) {
t.Errorf("Expected node %v got %v", node, uuid.NodeID())
}
}
func TestMD5(t *testing.T) {
uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String()
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
if uuid != want {
t.Errorf("MD5: got %q expected %q", uuid, want)
}
}
func TestSHA1(t *testing.T) {
uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String()
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
if uuid != want {
t.Errorf("SHA1: got %q expected %q", uuid, want)
}
}
func TestNodeID(t *testing.T) {
nid := []byte{1, 2, 3, 4, 5, 6}
nodeID = nil // Reset global state for next test
SetNodeInterface("")
s := NodeInterface()
if runtime.GOARCH != "js" {
if s == "" || s == "user" {
t.Errorf("NodeInterface %q after SetInterface", s)
}
}
node1 := NodeID()
if node1 == nil {
t.Error("NodeID nil after SetNodeInterface", s)
}
SetNodeID(nid)
s = NodeInterface()
if s != "user" {
t.Errorf("Expected NodeInterface %q got %q", "user", s)
}
node2 := NodeID()
if node2 == nil {
t.Error("NodeID nil after SetNodeID", s)
}
if bytes.Equal(node1, node2) {
t.Error("NodeID not changed after SetNodeID", s)
} else if !bytes.Equal(nid, node2) {
t.Errorf("NodeID is %x, expected %x", node2, nid)
}
}
func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) {
if uuid == nil {
t.Errorf("%s failed", name)
return
}
if v, _ := uuid.Version(); v != 2 {
t.Errorf("%s: %s: expected version 2, got %s", name, uuid, v)
return
}
if v, ok := uuid.Domain(); !ok || v != domain {
if !ok {
t.Errorf("%s: %d: Domain failed", name, uuid)
} else {
t.Errorf("%s: %s: expected domain %d, got %d", name, uuid, domain, v)
}
}
if v, ok := uuid.Id(); !ok || v != id {
if !ok {
t.Errorf("%s: %d: Id failed", name, uuid)
} else {
t.Errorf("%s: %s: expected id %d, got %d", name, uuid, id, v)
}
}
}
func TestDCE(t *testing.T) {
testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678)
testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid()))
testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid()))
}
type badRand struct{}
func (r badRand) Read(buf []byte) (int, error) {
for i, _ := range buf {
buf[i] = byte(i)
}
return len(buf), nil
}
func TestBadRand(t *testing.T) {
SetRand(badRand{})
uuid1 := New()
uuid2 := New()
if uuid1 != uuid2 {
t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2)
}
SetRand(nil)
uuid1 = New()
uuid2 = New()
if uuid1 == uuid2 {
t.Errorf("unexpected duplicates, got %q", uuid1)
}
}
func TestUUID_Array(t *testing.T) {
expect := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
t.Fatal("invalid uuid")
}
if uuid.Array() != expect {
t.Fatal("invalid array")
}
}
func TestArray_UUID(t *testing.T) {
array := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if expect == nil {
t.Fatal("invalid uuid")
}
if !bytes.Equal(array.UUID(), expect) {
t.Fatal("invalid uuid")
}
}
func BenchmarkParse(b *testing.B) {
for i := 0; i < b.N; i++ {
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
}
}
func BenchmarkNew(b *testing.B) {
for i := 0; i < b.N; i++ {
New()
}
}
func BenchmarkUUID_String(b *testing.B) {
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if uuid.String() == "" {
b.Fatal("invalid uuid")
}
}
}
func BenchmarkUUID_URN(b *testing.B) {
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if uuid.URN() == "" {
b.Fatal("invalid uuid")
}
}
}
func BenchmarkUUID_Array(b *testing.B) {
expect := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if uuid.Array() != expect {
b.Fatal("invalid array")
}
}
}
func BenchmarkArray_UUID(b *testing.B) {
array := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if expect == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if !bytes.Equal(array.UUID(), expect) {
b.Fatal("invalid uuid")
}
}
}

39
vendor/github.com/pborman/uuid/version1.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil.
func NewUUID() UUID {
SetNodeInterface("")
now, seq, err := GetTime()
if err != nil {
return nil
}
uuid := make([]byte, 16)
time_low := uint32(now & 0xffffffff)
time_mid := uint16((now >> 32) & 0xffff)
time_hi := uint16((now >> 48) & 0x0fff)
time_hi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], time_low)
binary.BigEndian.PutUint16(uuid[4:], time_mid)
binary.BigEndian.PutUint16(uuid[6:], time_hi)
binary.BigEndian.PutUint16(uuid[8:], seq)
copy(uuid[10:], nodeID)
return uuid
}

25
vendor/github.com/pborman/uuid/version4.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
// Random returns a Random (Version 4) UUID or panics.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() UUID {
uuid := make([]byte, 16)
randomBits([]byte(uuid))
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid
}

30
vendor/k8s.io/apimachinery/pkg/util/uuid/BUILD generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["uuid.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/uuid",
importpath = "k8s.io/apimachinery/pkg/util/uuid",
deps = [
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

43
vendor/k8s.io/apimachinery/pkg/util/uuid/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package uuid
import (
"sync"
"github.com/pborman/uuid"
"k8s.io/apimachinery/pkg/types"
)
var uuidLock sync.Mutex
var lastUUID uuid.UUID
func NewUUID() types.UID {
uuidLock.Lock()
defer uuidLock.Unlock()
result := uuid.NewUUID()
// The UUID package is naive and can generate identical UUIDs if the
// time interval is quick enough.
// The UUID uses 100 ns increments so it's short enough to actively
// wait for a new value.
for uuid.Equal(lastUUID, result) == true {
result = uuid.NewUUID()
}
lastUUID = result
return types.UID(result.String())
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/virtual-kubelet/virtual-kubelet/providers/azure"
"github.com/virtual-kubelet/virtual-kubelet/providers/azurebatch"
"github.com/virtual-kubelet/virtual-kubelet/providers/cri"
"github.com/virtual-kubelet/virtual-kubelet/providers/huawei"
"github.com/virtual-kubelet/virtual-kubelet/providers/hypersh"
"github.com/virtual-kubelet/virtual-kubelet/providers/mock"
"github.com/virtual-kubelet/virtual-kubelet/providers/vic"
@@ -121,6 +122,11 @@ func New(nodeName, operatingSystem, namespace, kubeConfig, taint, provider, prov
if err != nil {
return nil, err
}
case "huawei":
p, err = huawei.NewCCIProvider(providerConfig, rm, nodeName, operatingSystem, internalIP, daemonEndpointPort)
if err != nil {
return nil, err
}
default:
fmt.Printf("Provider '%s' is not supported\n", provider)
}