github.com/drycc/workflow-cli@v1.5.3-0.20240322092846-d4ee25983af9/cmd/limits_test.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"testing"
     8  
     9  	drycc "github.com/drycc/controller-sdk-go"
    10  	"github.com/drycc/controller-sdk-go/api"
    11  	"github.com/drycc/workflow-cli/pkg/testutil"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  const getPlanFixture string = `
    16  {
    17  	"id": "std1.large.c1m1",
    18  	"spec": {
    19  	  "id": "std1",
    20  	  "cpu": {
    21  		"name": "Unknown CPU",
    22  		"cores": 32,
    23  		"clock": "3100MHZ",
    24  		"boost": "3700MHZ",
    25  		"threads": 64
    26  	  },
    27  	  "memory": {
    28  		"size": "64GB",
    29  		"type": "DDR4-ECC"
    30  	  },
    31  	  "features": {
    32  		"gpu": {
    33  		  "name": "Unknown Integrated GPU",
    34  		  "tmus": 1,
    35  		  "rops": 1,
    36  		  "cores": 128,
    37  		  "memory": {
    38  			"size": "shared",
    39  			"type": "shared"
    40  		  }
    41  		},
    42  		"network": "10G"
    43  	  },
    44  	  "keywords": [
    45  		"amd",
    46  		"intel",
    47  		"unknown"
    48  	  ],
    49  	  "disabled": false
    50  	},
    51  	"cpu": 1,
    52  	"memory": 1,
    53  	"features": {
    54  		"gpu": 1,
    55  		"network": 1
    56  	},
    57  	"disabled": false
    58  }
    59  `
    60  const specsFixture string = `
    61  {
    62  	"results": [{
    63  		"id": "std1",
    64  		"cpu": {
    65  			"name": "Unknown CPU",
    66  			"cores": 32,
    67  			"clock": "3100MHZ",
    68  			"boost": "3700MHZ",
    69  			"threads": 64
    70  		},
    71  		"memory": {
    72  			"size": "64GB",
    73  			"type": "DDR4-ECC"
    74  		},
    75  		"features": {
    76  			"gpu": {
    77  				"name": "Unknown Integrated GPU",
    78  				"tmus": 1,
    79  				"rops": 1,
    80  				"cores": 128,
    81  				"memory": {
    82  					"size": "shared",
    83  					"type": "shared"
    84  				}
    85  			},
    86  			"network": "10G"
    87  		},
    88  		"keywords": [
    89  			"amd",
    90  			"intel",
    91  			"unknown"
    92  		],
    93  		"disabled": false
    94  	}],
    95  	"count": 1
    96  }
    97  `
    98  
    99  const plansFixture string = `
   100  {
   101  	"results": [{
   102  			"id": "std1.large.c1m1",
   103  			"spec": {
   104  				"id": "std1",
   105  				"cpu": {
   106  					"name": "Unknown CPU",
   107  					"cores": 32,
   108  					"clock": "3100MHZ",
   109  					"boost": "3700MHZ",
   110  					"threads": 64
   111  				},
   112  				"memory": {
   113  					"size": "64GB",
   114  					"type": "DDR4-ECC"
   115  				},
   116  				"features": {
   117  					"gpu": {
   118  						"name": "Unknown Integrated GPU",
   119  						"tmus": 1,
   120  						"rops": 1,
   121  						"cores": 128,
   122  						"memory": {
   123  							"size": "shared",
   124  							"type": "shared"
   125  						}
   126  					},
   127  					"network": "10G"
   128  				},
   129  				"keywords": [
   130  					"amd",
   131  					"intel",
   132  					"unknown"
   133  				],
   134  				"disabled": false
   135  			},
   136  			"cpu": 1,
   137  			"memory": 1,
   138  			"disabled": false
   139  		},
   140  		{
   141  			"id": "std1.large.c1m2",
   142  			"spec": {
   143  				"id": "std1",
   144  				"cpu": {
   145  					"name": "Unknown CPU",
   146  					"cores": 32,
   147  					"clock": "3100MHZ",
   148  					"boost": "3700MHZ",
   149  					"threads": 64
   150  				},
   151  				"memory": {
   152  					"size": "64GB",
   153  					"type": "DDR4-ECC"
   154  				},
   155  				"features": {
   156  					"gpu": {
   157  						"name": "Unknown Integrated GPU",
   158  						"tmus": 1,
   159  						"rops": 1,
   160  						"cores": 128,
   161  						"memory": {
   162  							"size": "shared",
   163  							"type": "shared"
   164  						}
   165  					},
   166  					"network": "10G"
   167  				},
   168  				"keywords": [
   169  					"amd",
   170  					"intel",
   171  					"unknown"
   172  				],
   173  				"disabled": false
   174  			},
   175  			"cpu": 1,
   176  			"memory": 2,
   177  			"disabled": false
   178  		}
   179  	],
   180  	"count": 2
   181  }
   182  `
   183  
   184  type parseLimitCase struct {
   185  	Input         string
   186  	Key           string
   187  	Value         string
   188  	ExpectedError bool
   189  	ExpectedMsg   string
   190  }
   191  
   192  func newTestServer(t *testing.T) (string, *testutil.TestServer) {
   193  	cf, server, err := testutil.NewTestServerAndClient()
   194  	if err != nil {
   195  		t.Fatal(err)
   196  	}
   197  	server.Mux.HandleFunc("/v2/limits/plans/std1.large.c1m1/", func(w http.ResponseWriter, _ *http.Request) {
   198  		w.Header().Add("DRYCC_API_VERSION", drycc.APIVersion)
   199  		testutil.SetHeaders(w)
   200  		fmt.Fprint(w, getPlanFixture)
   201  	})
   202  	server.Mux.HandleFunc("/v2/limits/specs/", func(w http.ResponseWriter, _ *http.Request) {
   203  		w.Header().Add("DRYCC_API_VERSION", drycc.APIVersion)
   204  		testutil.SetHeaders(w)
   205  		fmt.Fprint(w, specsFixture)
   206  	})
   207  
   208  	server.Mux.HandleFunc("/v2/limits/plans/", func(w http.ResponseWriter, _ *http.Request) {
   209  		w.Header().Add("DRYCC_API_VERSION", drycc.APIVersion)
   210  		testutil.SetHeaders(w)
   211  		fmt.Fprint(w, plansFixture)
   212  	})
   213  
   214  	return cf, server
   215  }
   216  
   217  func TestParseLimit(t *testing.T) {
   218  	t.Parallel()
   219  
   220  	var errorHint = ` doesn't fit format type=#unit or type=#
   221  Examples: web=std1.large.c1m1`
   222  
   223  	cases := []parseLimitCase{
   224  		{"web=std1.large.c1m1", "web", "std1.large.c1m1", false, ""},
   225  		{"web=std1.large.c2m2", "web", "std1.large.c2m2", false, ""},
   226  		{"task=std1.large.c2m2", "task", "std1.large.c2m2", false, ""},
   227  		{"task=std1.large.c2m4", "task", "std1.large.c2m4", false, ""},
   228  		{"task-big=std1.large.c2m4", "task-big", "std1.large.c2m4", false, ""},
   229  		{"task=[]std1.large.c2m4", "", "", true, "task=[]std1.large.c2m4" + errorHint},
   230  		{"task[]=&std1.large.c2m4", "", "", true, "task[]=&std1.large.c2m4" + errorHint},
   231  		{"task~!=&std1.large.c2m4", "", "", true, "task~!=&std1.large.c2m4" + errorHint},
   232  	}
   233  
   234  	for _, check := range cases {
   235  		key, value, err := parseLimit(check.Input)
   236  		if check.ExpectedError {
   237  			assert.Equal(t, err.Error(), check.ExpectedMsg, "error")
   238  		} else {
   239  			assert.NoError(t, err)
   240  			assert.Equal(t, key, check.Key, "key")
   241  			assert.Equal(t, value, check.Value, "value")
   242  		}
   243  	}
   244  }
   245  
   246  type parseLimitsCase struct {
   247  	Input         []string
   248  	ExpectedMap   map[string]interface{}
   249  	ExpectedError bool
   250  	ExpectedMsg   string
   251  }
   252  
   253  func TestLimitTags(t *testing.T) {
   254  	t.Parallel()
   255  
   256  	cases := []parseLimitsCase{
   257  		{[]string{"web=std1.large.c1m1", "worker=std1.large.c1m2"}, map[string]interface{}{"web": "std1.large.c1m1", "worker": "std1.large.c1m2"}, false, ""},
   258  		{[]string{"foo=", "web=std1.large.c1m1"}, nil, true, `foo= doesn't fit format type=#unit or type=#
   259  Examples: web=std1.large.c1m1`},
   260  	}
   261  
   262  	for _, check := range cases {
   263  		actual, err := parseLimits(check.Input)
   264  		if check.ExpectedError {
   265  			assert.Equal(t, err.Error(), check.ExpectedMsg, "error")
   266  		} else {
   267  			assert.NoError(t, err)
   268  			assert.Equal(t, actual, check.ExpectedMap, "map")
   269  		}
   270  	}
   271  }
   272  
   273  func TestLimitsList(t *testing.T) {
   274  	t.Parallel()
   275  	cf, server := newTestServer(t)
   276  	defer server.Close()
   277  
   278  	server.Mux.HandleFunc("/v2/apps/enterprise/config/", func(w http.ResponseWriter, _ *http.Request) {
   279  		testutil.SetHeaders(w)
   280  		fmt.Fprintf(w, `{
   281  			"owner": "jkirk",
   282  			"app": "enterprise",
   283  			"values": {},
   284  			"limits": {
   285  				"web": "std1.large.c1m1",
   286  				"worker": "std1.large.c1m1",
   287  				"db": "std1.large.c1m1"
   288  			},
   289  			"tags": {},
   290  			"registry": {},
   291  			"created": "2014-01-01T00:00:00UTC",
   292  			"updated": "2014-01-01T00:00:00UTC",
   293  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   294  		}`)
   295  	})
   296  
   297  	var b bytes.Buffer
   298  	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
   299  
   300  	err := cmdr.LimitsList("enterprise")
   301  	assert.NoError(t, err)
   302  	assert.Equal(t, b.String(), `PTYPE     PLAN               VCPUS    MEMORY    FEATURES                          
   303  db        std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   304  web       std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   305  worker    std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   306  `, "output")
   307  
   308  	server.Mux.HandleFunc("/v2/apps/franklin/config/", func(w http.ResponseWriter, _ *http.Request) {
   309  		testutil.SetHeaders(w)
   310  		fmt.Fprintf(w, `{
   311  			"owner": "bedison",
   312  			"app": "franklin",
   313  			"limits": {},
   314  			"cpu": {},
   315  			"tags": {},
   316  			"registry": {},
   317  			"created": "2014-01-01T00:00:00UTC",
   318  			"updated": "2014-01-01T00:00:00UTC",
   319  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   320  			}`)
   321  	})
   322  	b.Reset()
   323  
   324  	err = cmdr.LimitsList("franklin")
   325  	assert.NoError(t, err)
   326  	assert.Equal(t, b.String(), `No limits found in franklin app.
   327  `, "output")
   328  }
   329  
   330  func TestLimitsSet(t *testing.T) {
   331  	t.Parallel()
   332  	cf, server := newTestServer(t)
   333  	defer server.Close()
   334  
   335  	server.Mux.HandleFunc("/v2/apps/foo/config/", func(w http.ResponseWriter, r *http.Request) {
   336  		testutil.SetHeaders(w)
   337  		if r.Method == "POST" {
   338  			testutil.AssertBody(t, api.Config{
   339  				Limits: map[string]interface{}{
   340  					"web": "std1.large.c1m1",
   341  				},
   342  			}, r)
   343  		}
   344  
   345  		fmt.Fprintf(w, `{
   346  			"owner": "jkirk",
   347  			"app": "foo",
   348  			"values": {},
   349  			"limits": {"web": "std1.large.c1m1"},
   350  			"tags": {},
   351  			"registry": {},
   352  			"created": "2014-01-01T00:00:00UTC",
   353  			"updated": "2014-01-01T00:00:00UTC",
   354  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   355  		}`)
   356  	})
   357  
   358  	var b bytes.Buffer
   359  	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
   360  
   361  	err := cmdr.LimitsSet("foo", []string{"web=std1.large.c1m1"})
   362  	assert.NoError(t, err)
   363  
   364  	assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done
   365  
   366  PTYPE    PLAN               VCPUS    MEMORY    FEATURES                          
   367  web      std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   368  `, "output")
   369  
   370  	server.Mux.HandleFunc("/v2/apps/franklin/config/", func(w http.ResponseWriter, r *http.Request) {
   371  		testutil.SetHeaders(w)
   372  		if r.Method == "POST" {
   373  			testutil.AssertBody(t, api.Config{
   374  				Limits: map[string]interface{}{
   375  					"web": "std1.large.c1m1",
   376  				},
   377  			}, r)
   378  		}
   379  
   380  		fmt.Fprintf(w, `{
   381  			"owner": "bedison",
   382  			"app": "franklin",
   383  			"values": {},
   384  			"limits": {
   385  				"web": "std1.large.c1m1"
   386  			},
   387  			"tags": {},
   388  			"registry": {},
   389  			"created": "2014-01-01T00:00:00UTC",
   390  			"updated": "2014-01-01T00:00:00UTC",
   391  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   392  		}`)
   393  	})
   394  	b.Reset()
   395  
   396  	err = cmdr.LimitsSet("franklin", []string{"web=std1.large.c1m1"})
   397  	assert.NoError(t, err)
   398  
   399  	assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done
   400  
   401  PTYPE    PLAN               VCPUS    MEMORY    FEATURES                          
   402  web      std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   403  `, "output")
   404  
   405  	// with requests/limit parameter
   406  	server.Mux.HandleFunc("/v2/apps/jim/config/", func(w http.ResponseWriter, r *http.Request) {
   407  		testutil.SetHeaders(w)
   408  		if r.Method == "POST" {
   409  			testutil.AssertBody(t, api.Config{
   410  				Limits: map[string]interface{}{
   411  					"web":    "std1.large.c1m1",
   412  					"worker": "std1.large.c1m1",
   413  					"db":     "std1.large.c1m1",
   414  				},
   415  			}, r)
   416  		}
   417  
   418  		fmt.Fprintf(w, `{
   419  			"owner": "foo",
   420  			"app": "jim",
   421  			"values": {},
   422  			"limits": {
   423  				"web": "std1.large.c1m1",
   424  				"worker": "std1.large.c1m1",
   425  				"db": "std1.large.c1m1"
   426  			},
   427  			"tags": {},
   428  			"registry": {},
   429  			"created": "2014-01-01T00:00:00UTC",
   430  			"updated": "2014-01-01T00:00:00UTC",
   431  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   432  		}`)
   433  	})
   434  	b.Reset()
   435  
   436  	err = cmdr.LimitsSet("jim", []string{"web=std1.large.c1m1", "worker=std1.large.c1m1", "db=std1.large.c1m1"})
   437  	assert.NoError(t, err)
   438  
   439  	assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done
   440  
   441  PTYPE     PLAN               VCPUS    MEMORY    FEATURES                          
   442  db        std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   443  web       std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   444  worker    std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   445  `, "output")
   446  
   447  	// with requests/limit parameter
   448  	server.Mux.HandleFunc("/v2/apps/phew/config/", func(w http.ResponseWriter, r *http.Request) {
   449  		testutil.SetHeaders(w)
   450  		if r.Method == "POST" {
   451  			testutil.AssertBody(t, api.Config{
   452  				Limits: map[string]interface{}{
   453  					"web":    "std1.large.c1m1",
   454  					"worker": "std1.large.c1m1",
   455  					"db":     "std1.large.c1m1",
   456  				},
   457  			}, r)
   458  		}
   459  
   460  		fmt.Fprintf(w, `{
   461  			"owner": "foo",
   462  			"app": "jim",
   463  			"values": {},
   464  			"limits": {
   465  				"web": "std1.large.c1m1",
   466  				"worker": "std1.large.c1m1",
   467  				"db": "std1.large.c1m1"
   468  			},
   469  			"tags": {},
   470  			"registry": {},
   471  			"created": "2014-01-01T00:00:00UTC",
   472  			"updated": "2014-01-01T00:00:00UTC",
   473  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   474  		}`)
   475  	})
   476  	b.Reset()
   477  
   478  	err = cmdr.LimitsSet("phew", []string{"web=std1.large.c1m1", "worker=std1.large.c1m1", "db=std1.large.c1m1"})
   479  	assert.NoError(t, err)
   480  
   481  	assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done
   482  
   483  PTYPE     PLAN               VCPUS    MEMORY    FEATURES                          
   484  db        std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   485  web       std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   486  worker    std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   487  `, "output")
   488  }
   489  
   490  func TestLimitsUnset(t *testing.T) {
   491  	t.Parallel()
   492  	cf, server := newTestServer(t)
   493  	defer server.Close()
   494  
   495  	server.Mux.HandleFunc("/v2/apps/foo/config/", func(w http.ResponseWriter, r *http.Request) {
   496  		testutil.SetHeaders(w)
   497  		if r.Method == "POST" {
   498  			testutil.AssertBody(t, api.Config{
   499  				Limits: map[string]interface{}{
   500  					"web": nil,
   501  				},
   502  			}, r)
   503  		}
   504  
   505  		fmt.Fprintf(w, `{
   506  			"owner": "jkirk",
   507  			"app": "foo",
   508  			"values": {},
   509  			"limits": {
   510  				"web": "std1.large.c1m1"
   511  			},
   512  			"tags": {},
   513  			"registry": {},
   514  			"created": "2014-01-01T00:00:00UTC",
   515  			"updated": "2014-01-01T00:00:00UTC",
   516  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   517  		}`)
   518  	})
   519  
   520  	var b bytes.Buffer
   521  	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
   522  
   523  	err := cmdr.LimitsUnset("foo", []string{"web"})
   524  	assert.NoError(t, err)
   525  
   526  	assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done
   527  
   528  PTYPE    PLAN               VCPUS    MEMORY    FEATURES                          
   529  web      std1.large.c1m1    1        1 GiB     Unknown Integrated GPU shared * 1    
   530  `, "output")
   531  
   532  	server.Mux.HandleFunc("/v2/apps/franklin/config/", func(w http.ResponseWriter, r *http.Request) {
   533  		testutil.SetHeaders(w)
   534  		if r.Method == "POST" {
   535  			testutil.AssertBody(t, api.Config{
   536  				Limits: map[string]interface{}{
   537  					"web": nil,
   538  				},
   539  			}, r)
   540  		}
   541  
   542  		fmt.Fprintf(w, `{
   543  			"owner": "bedison",
   544  			"app": "franklin",
   545  			"values": {},
   546  			"limits": {},
   547  			"tags": {},
   548  			"registry": {},
   549  			"created": "2014-01-01T00:00:00UTC",
   550  			"updated": "2014-01-01T00:00:00UTC",
   551  			"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75"
   552  		}`)
   553  	})
   554  	b.Reset()
   555  
   556  	err = cmdr.LimitsUnset("franklin", []string{"web"})
   557  	assert.NoError(t, err)
   558  
   559  	assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done
   560  
   561  No limits found in franklin app.
   562  `, "output")
   563  }
   564  
   565  func TestLimitsSpecs(t *testing.T) {
   566  	t.Parallel()
   567  	cf, server := newTestServer(t)
   568  	defer server.Close()
   569  
   570  	var b bytes.Buffer
   571  	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
   572  
   573  	err := cmdr.LimitsSpecs("", 10)
   574  	assert.NoError(t, err)
   575  	assert.Equal(t, b.String(), `ID      CPU            CLOCK      BOOST      CORES    THREADS    NETWORK    FEATURES                      
   576  std1    Unknown CPU    3100MHZ    3700MHZ    32       64         10G        Unknown Integrated GPU shared    
   577  `, "output")
   578  }
   579  
   580  func TestLimitsPlans(t *testing.T) {
   581  	t.Parallel()
   582  	cf, server := newTestServer(t)
   583  	defer server.Close()
   584  
   585  	var b bytes.Buffer
   586  	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
   587  
   588  	err := cmdr.LimitsPlans("", 0, 0, 100)
   589  	assert.NoError(t, err)
   590  	assert.Equal(t, b.String(), `ID                 SPEC    CPU            VCPUS    MEMORY    FEATURES                      
   591  std1.large.c1m1    std1    Unknown CPU    1        1 GiB     Unknown Integrated GPU shared    
   592  std1.large.c1m2    std1    Unknown CPU    1        2 GiB     Unknown Integrated GPU shared    
   593  `, "output")
   594  }