github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/openstack/provider_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"net/http/httptest"
    10  
    11  	gitjujutesting "github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/goose.v2/identity"
    16  	"gopkg.in/goose.v2/neutron"
    17  	"gopkg.in/goose.v2/nova"
    18  	"gopkg.in/yaml.v2"
    19  
    20  	"github.com/juju/juju/cloud"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/environs/context"
    23  	"github.com/juju/juju/network"
    24  )
    25  
    26  // localTests contains tests which do not require a live service or test double to run.
    27  type localTests struct {
    28  	gitjujutesting.IsolationSuite
    29  }
    30  
    31  var _ = gc.Suite(&localTests{})
    32  
    33  // ported from lp:juju/juju/providers/openstack/tests/test_machine.py
    34  var addressTests = []struct {
    35  	summary    string
    36  	floatingIP string
    37  	private    []nova.IPAddress
    38  	public     []nova.IPAddress
    39  	networks   []string
    40  	expected   string
    41  	failure    error
    42  }{{
    43  	summary:  "missing",
    44  	expected: "",
    45  }, {
    46  	summary:  "empty",
    47  	private:  []nova.IPAddress{},
    48  	networks: []string{"private"},
    49  	expected: "",
    50  }, {
    51  	summary:  "private IPv4 only",
    52  	private:  []nova.IPAddress{{4, "192.168.0.1", "fixed"}},
    53  	networks: []string{"private"},
    54  	expected: "192.168.0.1",
    55  }, {
    56  	summary:  "private IPv6 only",
    57  	private:  []nova.IPAddress{{6, "fc00::1", "fixed"}},
    58  	networks: []string{"private"},
    59  	expected: "fc00::1",
    60  }, {
    61  	summary:  "private only, both IPv4 and IPv6",
    62  	private:  []nova.IPAddress{{4, "192.168.0.1", "fixed"}, {6, "fc00::1", "fixed"}},
    63  	networks: []string{"private"},
    64  	expected: "192.168.0.1",
    65  }, {
    66  	summary:  "private IPv4 plus (what HP cloud used to do)",
    67  	private:  []nova.IPAddress{{4, "10.0.0.1", "fixed"}, {4, "8.8.4.4", "fixed"}},
    68  	networks: []string{"private"},
    69  	expected: "8.8.4.4",
    70  }, {
    71  	summary:  "public IPv4 only",
    72  	public:   []nova.IPAddress{{4, "8.8.8.8", "floating"}},
    73  	networks: []string{"", "public"},
    74  	expected: "8.8.8.8",
    75  }, {
    76  	summary:  "public IPv6 only",
    77  	public:   []nova.IPAddress{{6, "2001:db8::1", "floating"}},
    78  	networks: []string{"", "public"},
    79  	expected: "2001:db8::1",
    80  }, {
    81  	summary:  "public only, both IPv4 and IPv6",
    82  	public:   []nova.IPAddress{{4, "8.8.8.8", "floating"}, {6, "2001:db8::1", "floating"}},
    83  	networks: []string{"", "public"},
    84  	expected: "8.8.8.8",
    85  }, {
    86  	summary:  "public and private both IPv4",
    87  	private:  []nova.IPAddress{{4, "10.0.0.4", "fixed"}},
    88  	public:   []nova.IPAddress{{4, "8.8.4.4", "floating"}},
    89  	networks: []string{"private", "public"},
    90  	expected: "8.8.4.4",
    91  }, {
    92  	summary:  "public and private both IPv6",
    93  	private:  []nova.IPAddress{{6, "fc00::1", "fixed"}},
    94  	public:   []nova.IPAddress{{6, "2001:db8::1", "floating"}},
    95  	networks: []string{"private", "public"},
    96  	expected: "2001:db8::1",
    97  }, {
    98  	summary:  "public, private, and localhost IPv4",
    99  	private:  []nova.IPAddress{{4, "127.0.0.4", "fixed"}, {4, "192.168.0.1", "fixed"}},
   100  	public:   []nova.IPAddress{{4, "8.8.8.8", "floating"}},
   101  	networks: []string{"private", "public"},
   102  	expected: "8.8.8.8",
   103  }, {
   104  	summary:  "public, private, and localhost IPv6",
   105  	private:  []nova.IPAddress{{6, "::1", "fixed"}, {6, "fc00::1", "fixed"}},
   106  	public:   []nova.IPAddress{{6, "2001:db8::1", "floating"}},
   107  	networks: []string{"private", "public"},
   108  	expected: "2001:db8::1",
   109  }, {
   110  	summary:  "public, private, and localhost - both IPv4 and IPv6",
   111  	private:  []nova.IPAddress{{4, "127.0.0.4", "fixed"}, {4, "192.168.0.1", "fixed"}, {6, "::1", "fixed"}, {6, "fc00::1", "fixed"}},
   112  	public:   []nova.IPAddress{{4, "8.8.8.8", "floating"}, {6, "2001:db8::1", "floating"}},
   113  	networks: []string{"private", "public"},
   114  	expected: "8.8.8.8",
   115  }, {
   116  	summary:  "custom only IPv4",
   117  	private:  []nova.IPAddress{{4, "192.168.0.1", "fixed"}},
   118  	networks: []string{"special"},
   119  	expected: "192.168.0.1",
   120  }, {
   121  	summary:  "custom only IPv6",
   122  	private:  []nova.IPAddress{{6, "fc00::1", "fixed"}},
   123  	networks: []string{"special"},
   124  	expected: "fc00::1",
   125  }, {
   126  	summary:  "custom only - both IPv4 and IPv6",
   127  	private:  []nova.IPAddress{{4, "192.168.0.1", "fixed"}, {6, "fc00::1", "fixed"}},
   128  	networks: []string{"special"},
   129  	expected: "192.168.0.1",
   130  }, {
   131  	summary:  "custom and public IPv4",
   132  	private:  []nova.IPAddress{{4, "172.16.0.1", "fixed"}},
   133  	public:   []nova.IPAddress{{4, "8.8.8.8", "floating"}},
   134  	networks: []string{"special", "public"},
   135  	expected: "8.8.8.8",
   136  }, {
   137  	summary:  "custom and public IPv6",
   138  	private:  []nova.IPAddress{{6, "fc00::1", "fixed"}},
   139  	public:   []nova.IPAddress{{6, "2001:db8::1", "floating"}},
   140  	networks: []string{"special", "public"},
   141  	expected: "2001:db8::1",
   142  }, {
   143  	summary:  "custom and public - both IPv4 and IPv6",
   144  	private:  []nova.IPAddress{{4, "172.16.0.1", "fixed"}, {6, "fc00::1", "fixed"}},
   145  	public:   []nova.IPAddress{{4, "8.8.8.8", "floating"}, {6, "2001:db8::1", "floating"}},
   146  	networks: []string{"special", "public"},
   147  	expected: "8.8.8.8",
   148  }, {
   149  	summary:    "floating and public, same address",
   150  	floatingIP: "8.8.8.8",
   151  	public:     []nova.IPAddress{{4, "8.8.8.8", "floating"}},
   152  	networks:   []string{"", "public"},
   153  	expected:   "8.8.8.8",
   154  }, {
   155  	summary:    "floating and public, different address",
   156  	floatingIP: "8.8.4.4",
   157  	public:     []nova.IPAddress{{4, "8.8.8.8", "floating"}},
   158  	networks:   []string{"", "public"},
   159  	expected:   "8.8.4.4",
   160  }, {
   161  	summary:    "floating and private",
   162  	floatingIP: "8.8.4.4",
   163  	private:    []nova.IPAddress{{4, "10.0.0.1", "fixed"}},
   164  	networks:   []string{"private"},
   165  	expected:   "8.8.4.4",
   166  }, {
   167  	summary:    "floating, custom and public",
   168  	floatingIP: "8.8.4.4",
   169  	private:    []nova.IPAddress{{4, "172.16.0.1", "fixed"}},
   170  	public:     []nova.IPAddress{{4, "8.8.8.8", "floating"}},
   171  	networks:   []string{"special", "public"},
   172  	expected:   "8.8.4.4",
   173  }}
   174  
   175  func (t *localTests) TestGetServerAddresses(c *gc.C) {
   176  	for i, t := range addressTests {
   177  		c.Logf("#%d. %s -> %s (%v)", i, t.summary, t.expected, t.failure)
   178  		addresses := make(map[string][]nova.IPAddress)
   179  		if t.private != nil {
   180  			if len(t.networks) < 1 {
   181  				addresses["private"] = t.private
   182  			} else {
   183  				addresses[t.networks[0]] = t.private
   184  			}
   185  		}
   186  		if t.public != nil {
   187  			if len(t.networks) < 2 {
   188  				addresses["public"] = t.public
   189  			} else {
   190  				addresses[t.networks[1]] = t.public
   191  			}
   192  		}
   193  		addr := InstanceAddress(t.floatingIP, addresses)
   194  		c.Check(addr, gc.Equals, t.expected)
   195  	}
   196  }
   197  
   198  func (*localTests) TestPortsToRuleInfo(c *gc.C) {
   199  	groupId := "groupid"
   200  	testCases := []struct {
   201  		about    string
   202  		rules    []network.IngressRule
   203  		expected []neutron.RuleInfoV2
   204  	}{{
   205  		about: "single port",
   206  		rules: []network.IngressRule{network.MustNewIngressRule("tcp", 80, 80)},
   207  		expected: []neutron.RuleInfoV2{{
   208  			Direction:      "ingress",
   209  			IPProtocol:     "tcp",
   210  			PortRangeMin:   80,
   211  			PortRangeMax:   80,
   212  			RemoteIPPrefix: "0.0.0.0/0",
   213  			ParentGroupId:  groupId,
   214  		}},
   215  	}, {
   216  		about: "multiple ports",
   217  		rules: []network.IngressRule{network.MustNewIngressRule("tcp", 80, 82)},
   218  		expected: []neutron.RuleInfoV2{{
   219  			Direction:      "ingress",
   220  			IPProtocol:     "tcp",
   221  			PortRangeMin:   80,
   222  			PortRangeMax:   82,
   223  			RemoteIPPrefix: "0.0.0.0/0",
   224  			ParentGroupId:  groupId,
   225  		}},
   226  	}, {
   227  		about: "multiple port ranges",
   228  		rules: []network.IngressRule{
   229  			network.MustNewIngressRule("tcp", 80, 82),
   230  			network.MustNewIngressRule("tcp", 100, 120),
   231  		},
   232  		expected: []neutron.RuleInfoV2{{
   233  			Direction:      "ingress",
   234  			IPProtocol:     "tcp",
   235  			PortRangeMin:   80,
   236  			PortRangeMax:   82,
   237  			RemoteIPPrefix: "0.0.0.0/0",
   238  			ParentGroupId:  groupId,
   239  		}, {
   240  			Direction:      "ingress",
   241  			IPProtocol:     "tcp",
   242  			PortRangeMin:   100,
   243  			PortRangeMax:   120,
   244  			RemoteIPPrefix: "0.0.0.0/0",
   245  			ParentGroupId:  groupId,
   246  		}},
   247  	}, {
   248  		about: "source range",
   249  		rules: []network.IngressRule{network.MustNewIngressRule(
   250  			"tcp", 80, 100, "192.168.1.0/24", "0.0.0.0/0")},
   251  		expected: []neutron.RuleInfoV2{{
   252  			Direction:      "ingress",
   253  			IPProtocol:     "tcp",
   254  			PortRangeMin:   80,
   255  			PortRangeMax:   100,
   256  			RemoteIPPrefix: "192.168.1.0/24",
   257  			ParentGroupId:  groupId,
   258  		}, {
   259  			Direction:      "ingress",
   260  			IPProtocol:     "tcp",
   261  			PortRangeMin:   80,
   262  			PortRangeMax:   100,
   263  			RemoteIPPrefix: "0.0.0.0/0",
   264  			ParentGroupId:  groupId,
   265  		}},
   266  	}}
   267  
   268  	for i, t := range testCases {
   269  		c.Logf("test %d: %s", i, t.about)
   270  		rules := PortsToRuleInfo(groupId, t.rules)
   271  		c.Check(len(rules), gc.Equals, len(t.expected))
   272  		c.Check(rules, gc.DeepEquals, t.expected)
   273  	}
   274  }
   275  
   276  func (*localTests) TestSecGroupMatchesIngressRule(c *gc.C) {
   277  	proto_tcp := "tcp"
   278  	proto_udp := "udp"
   279  	port_80 := 80
   280  	port_85 := 85
   281  
   282  	testCases := []struct {
   283  		about        string
   284  		rule         network.IngressRule
   285  		secGroupRule neutron.SecurityGroupRuleV2
   286  		expected     bool
   287  	}{{
   288  		about: "single port",
   289  		rule:  network.MustNewIngressRule(proto_tcp, 80, 80),
   290  		secGroupRule: neutron.SecurityGroupRuleV2{
   291  			IPProtocol:   &proto_tcp,
   292  			PortRangeMin: &port_80,
   293  			PortRangeMax: &port_80,
   294  		},
   295  		expected: true,
   296  	}, {
   297  		about: "multiple port",
   298  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85),
   299  		secGroupRule: neutron.SecurityGroupRuleV2{
   300  			IPProtocol:   &proto_tcp,
   301  			PortRangeMin: &port_80,
   302  			PortRangeMax: &port_85,
   303  		},
   304  		expected: true,
   305  	}, {
   306  		about: "nil rule components",
   307  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85),
   308  		secGroupRule: neutron.SecurityGroupRuleV2{
   309  			IPProtocol:   nil,
   310  			PortRangeMin: nil,
   311  			PortRangeMax: nil,
   312  		},
   313  		expected: false,
   314  	}, {
   315  		about: "nil rule component: PortRangeMin",
   316  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85, "0.0.0.0/0", "192.168.1.0/24"),
   317  		secGroupRule: neutron.SecurityGroupRuleV2{
   318  			IPProtocol:     &proto_tcp,
   319  			PortRangeMin:   nil,
   320  			PortRangeMax:   &port_85,
   321  			RemoteIPPrefix: "192.168.100.0/24",
   322  		},
   323  		expected: false,
   324  	}, {
   325  		about: "nil rule component: PortRangeMax",
   326  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85, "0.0.0.0/0", "192.168.1.0/24"),
   327  		secGroupRule: neutron.SecurityGroupRuleV2{
   328  			IPProtocol:     &proto_tcp,
   329  			PortRangeMin:   &port_85,
   330  			PortRangeMax:   nil,
   331  			RemoteIPPrefix: "192.168.100.0/24",
   332  		},
   333  		expected: false,
   334  	}, {
   335  		about: "mismatched port range and rule",
   336  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85),
   337  		secGroupRule: neutron.SecurityGroupRuleV2{
   338  			IPProtocol:   &proto_udp,
   339  			PortRangeMin: &port_80,
   340  			PortRangeMax: &port_80,
   341  		},
   342  		expected: false,
   343  	}, {
   344  		about: "default RemoteIPPrefix",
   345  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85),
   346  		secGroupRule: neutron.SecurityGroupRuleV2{
   347  			IPProtocol:     &proto_tcp,
   348  			PortRangeMin:   &port_80,
   349  			PortRangeMax:   &port_85,
   350  			RemoteIPPrefix: "0.0.0.0/0",
   351  		},
   352  		expected: true,
   353  	}, {
   354  		about: "matching RemoteIPPrefix",
   355  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85, "0.0.0.0/0", "192.168.1.0/24"),
   356  		secGroupRule: neutron.SecurityGroupRuleV2{
   357  			IPProtocol:     &proto_tcp,
   358  			PortRangeMin:   &port_80,
   359  			PortRangeMax:   &port_85,
   360  			RemoteIPPrefix: "192.168.1.0/24",
   361  		},
   362  		expected: true,
   363  	}, {
   364  		about: "non-matching RemoteIPPrefix",
   365  		rule:  network.MustNewIngressRule(proto_tcp, 80, 85, "0.0.0.0/0", "192.168.1.0/24"),
   366  		secGroupRule: neutron.SecurityGroupRuleV2{
   367  			IPProtocol:     &proto_tcp,
   368  			PortRangeMin:   &port_80,
   369  			PortRangeMax:   &port_85,
   370  			RemoteIPPrefix: "192.168.100.0/24",
   371  		},
   372  		expected: false,
   373  	}}
   374  	for i, t := range testCases {
   375  		c.Logf("test %d: %s", i, t.about)
   376  		c.Check(SecGroupMatchesIngressRule(t.secGroupRule, t.rule), gc.Equals, t.expected)
   377  	}
   378  }
   379  
   380  func (s *localTests) TestDetectRegionsNoRegionName(c *gc.C) {
   381  	_, err := s.detectRegions(c)
   382  	c.Assert(err, gc.ErrorMatches, "OS_REGION_NAME environment variable not set")
   383  }
   384  
   385  func (s *localTests) TestDetectRegionsNoAuthURL(c *gc.C) {
   386  	s.PatchEnvironment("OS_REGION_NAME", "oceania")
   387  	_, err := s.detectRegions(c)
   388  	c.Assert(err, gc.ErrorMatches, "OS_AUTH_URL environment variable not set")
   389  }
   390  
   391  func (s *localTests) TestDetectRegions(c *gc.C) {
   392  	s.PatchEnvironment("OS_REGION_NAME", "oceania")
   393  	s.PatchEnvironment("OS_AUTH_URL", "http://keystone.internal")
   394  	regions, err := s.detectRegions(c)
   395  	c.Assert(err, jc.ErrorIsNil)
   396  	c.Assert(regions, jc.DeepEquals, []cloud.Region{
   397  		{Name: "oceania", Endpoint: "http://keystone.internal"},
   398  	})
   399  }
   400  
   401  func (s *localTests) detectRegions(c *gc.C) ([]cloud.Region, error) {
   402  	provider, err := environs.Provider("openstack")
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	c.Assert(provider, gc.Implements, new(environs.CloudRegionDetector))
   405  	return provider.(environs.CloudRegionDetector).DetectRegions()
   406  }
   407  
   408  func (s *localTests) TestSchema(c *gc.C) {
   409  	y := []byte(`
   410  auth-types: [userpass, access-key]
   411  endpoint: http://foo.com/openstack
   412  regions: 
   413    one:
   414      endpoint: http://foo.com/bar
   415    two:
   416      endpoint: http://foo2.com/bar2
   417  `[1:])
   418  	var v interface{}
   419  	err := yaml.Unmarshal(y, &v)
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	v, err = utils.ConformYAML(v)
   422  	c.Assert(err, jc.ErrorIsNil)
   423  
   424  	p, err := environs.Provider("openstack")
   425  	err = p.CloudSchema().Validate(v)
   426  	c.Assert(err, jc.ErrorIsNil)
   427  }
   428  
   429  func (localTests) TestPingInvalidHost(c *gc.C) {
   430  	tests := []string{
   431  		"foo.com",
   432  		"http://IHopeNoOneEverBuysThisVerySpecificJujuDomainName.com",
   433  		"http://IHopeNoOneEverBuysThisVerySpecificJujuDomainName:77",
   434  	}
   435  
   436  	p, err := environs.Provider("openstack")
   437  	c.Assert(err, jc.ErrorIsNil)
   438  	callCtx := context.NewCloudCallContext()
   439  	for _, t := range tests {
   440  		err = p.Ping(callCtx, t)
   441  		if err == nil {
   442  			c.Errorf("ping %q: expected error, but got nil.", t)
   443  			continue
   444  		}
   445  		expected := "No Openstack server running at " + t
   446  		if err.Error() != expected {
   447  			c.Errorf("ping %q: expected %q got %v", t, expected, err)
   448  		}
   449  	}
   450  }
   451  func (localTests) TestPingNoEndpoint(c *gc.C) {
   452  	server := httptest.NewServer(http.HandlerFunc(http.NotFound))
   453  	defer server.Close()
   454  	p, err := environs.Provider("openstack")
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	err = p.Ping(context.NewCloudCallContext(), server.URL)
   457  	c.Assert(err, gc.ErrorMatches, "No Openstack server running at "+server.URL)
   458  }
   459  
   460  func (localTests) TestPingInvalidResponse(c *gc.C) {
   461  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   462  		fmt.Fprint(w, "Hi!")
   463  	}))
   464  	defer server.Close()
   465  	p, err := environs.Provider("openstack")
   466  	c.Assert(err, jc.ErrorIsNil)
   467  	err = p.Ping(context.NewCloudCallContext(), server.URL)
   468  	c.Assert(err, gc.ErrorMatches, "No Openstack server running at "+server.URL)
   469  }
   470  
   471  func (localTests) TestPingOKCACertificate(c *gc.C) {
   472  	server := httptest.NewTLSServer(handlerFunc)
   473  	defer server.Close()
   474  	pingOk(c, server)
   475  }
   476  
   477  func (localTests) TestPingOK(c *gc.C) {
   478  	server := httptest.NewServer(handlerFunc)
   479  	defer server.Close()
   480  	pingOk(c, server)
   481  }
   482  
   483  func pingOk(c *gc.C, server *httptest.Server) {
   484  	p, err := environs.Provider("openstack")
   485  	c.Assert(err, jc.ErrorIsNil)
   486  	err = p.Ping(context.NewCloudCallContext(), server.URL)
   487  	c.Assert(err, jc.ErrorIsNil)
   488  }
   489  
   490  var handlerFunc = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   491  	// This line is critical, the openstack provider will reject the message
   492  	// if you return 200 like a mere mortal.
   493  	w.WriteHeader(http.StatusMultipleChoices)
   494  	fmt.Fprint(w, `
   495  {
   496    "versions": {
   497      "values": [
   498        {
   499          "status": "stable",
   500          "updated": "2013-03-06T00:00:00Z",
   501          "media-types": [
   502            {
   503              "base": "application/json",
   504              "type": "application/vnd.openstack.identity-v3+json"
   505            },
   506            {
   507              "base": "application/xml",
   508              "type": "application/vnd.openstack.identity-v3+xml"
   509            }
   510          ],
   511          "id": "v3.0",
   512          "links": [
   513            {
   514              "href": "http://10.24.0.177:5000/v3/",
   515              "rel": "self"
   516            }
   517          ]
   518        },
   519        {
   520          "status": "stable",
   521          "updated": "2014-04-17T00:00:00Z",
   522          "media-types": [
   523            {
   524              "base": "application/json",
   525              "type": "application/vnd.openstack.identity-v2.0+json"
   526            },
   527            {
   528              "base": "application/xml",
   529              "type": "application/vnd.openstack.identity-v2.0+xml"
   530            }
   531          ],
   532          "id": "v2.0",
   533          "links": [
   534            {
   535              "href": "http://10.24.0.177:5000/v2.0/",
   536              "rel": "self"
   537            },
   538            {
   539              "href": "http://docs.openstack.org/api/openstack-identity-service/2.0/content/",
   540              "type": "text/html",
   541              "rel": "describedby"
   542            },
   543            {
   544              "href": "http://docs.openstack.org/api/openstack-identity-service/2.0/identity-dev-guide-2.0.pdf",
   545              "type": "application/pdf",
   546              "rel": "describedby"
   547            }
   548          ]
   549        }
   550      ]
   551    }
   552  }
   553  `)
   554  })
   555  
   556  type providerUnitTests struct {
   557  	gitjujutesting.IsolationSuite
   558  }
   559  
   560  var _ = gc.Suite(&providerUnitTests{})
   561  
   562  func checkIdentityClientVersionInvalid(c *gc.C, url string) {
   563  	_, err := identityClientVersion(url)
   564  	c.Check(err, gc.ErrorMatches, fmt.Sprintf("version part of identity url %s not valid", url))
   565  }
   566  
   567  func checkIdentityClientVersion(c *gc.C, url string, expversion int) {
   568  	version, err := identityClientVersion(url)
   569  	c.Assert(err, jc.ErrorIsNil)
   570  	c.Check(version, gc.Equals, expversion)
   571  }
   572  func (s *providerUnitTests) TestIdentityClientVersion_BadURLErrors(c *gc.C) {
   573  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/a")
   574  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/v")
   575  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/V")
   576  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/V/")
   577  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/100")
   578  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/vot")
   579  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/identity/vot")
   580  	checkIdentityClientVersionInvalid(c, "https://keystone.internal/identity/2")
   581  
   582  	_, err := identityClientVersion("abc123")
   583  	c.Check(err, gc.ErrorMatches, `url abc123 is malformed`)
   584  }
   585  
   586  func (s *providerUnitTests) TestIdentityClientVersion_ParsesGoodURL(c *gc.C) {
   587  	checkIdentityClientVersion(c, "https://keystone.internal/v2.0", 2)
   588  	checkIdentityClientVersion(c, "https://keystone.internal/v3.0/", 3)
   589  	checkIdentityClientVersion(c, "https://keystone.internal/v2/", 2)
   590  	checkIdentityClientVersion(c, "https://keystone.internal/V2/", 2)
   591  	checkIdentityClientVersion(c, "https://keystone.internal/internal/V2/", 2)
   592  	checkIdentityClientVersion(c, "https://keystone.internal/internal/v3.0/", 3)
   593  	checkIdentityClientVersion(c, "https://keystone.internal/internal/v3.2///", 3)
   594  	checkIdentityClientVersion(c, "https://keystone.internal", -1)
   595  	checkIdentityClientVersion(c, "https://keystone.internal/", -1)
   596  }
   597  
   598  func (s *providerUnitTests) TestNewCredentialsWithVersion3(c *gc.C) {
   599  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   600  		"version":     "3",
   601  		"username":    "user",
   602  		"password":    "secret",
   603  		"tenant-name": "someTenant",
   604  		"tenant-id":   "someID",
   605  	})
   606  	clouldSpec := environs.CloudSpec{
   607  		Type:       "openstack",
   608  		Region:     "openstack_region",
   609  		Name:       "openstack",
   610  		Endpoint:   "http://endpoint",
   611  		Credential: &creds,
   612  	}
   613  	cred, authmode, err := newCredentials(clouldSpec)
   614  	c.Assert(err, jc.ErrorIsNil)
   615  	c.Check(cred, gc.Equals, identity.Credentials{
   616  		URL:           "http://endpoint",
   617  		User:          "user",
   618  		Secrets:       "secret",
   619  		Region:        "openstack_region",
   620  		TenantName:    "someTenant",
   621  		TenantID:      "someID",
   622  		Version:       3,
   623  		Domain:        "",
   624  		UserDomain:    "",
   625  		ProjectDomain: "",
   626  	})
   627  	c.Check(authmode, gc.Equals, identity.AuthUserPassV3)
   628  }
   629  
   630  func (s *providerUnitTests) TestNewCredentialsWithFaultVersion(c *gc.C) {
   631  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   632  		"version":     "abc",
   633  		"username":    "user",
   634  		"password":    "secret",
   635  		"tenant-name": "someTenant",
   636  		"tenant-id":   "someID",
   637  	})
   638  	clouldSpec := environs.CloudSpec{
   639  		Type:       "openstack",
   640  		Region:     "openstack_region",
   641  		Name:       "openstack",
   642  		Endpoint:   "http://endpoint",
   643  		Credential: &creds,
   644  	}
   645  	_, _, err := newCredentials(clouldSpec)
   646  	c.Assert(err, gc.ErrorMatches,
   647  		"cred.Version is not a valid integer type : strconv.Atoi: parsing \"abc\": invalid syntax")
   648  }
   649  
   650  func (s *providerUnitTests) TestNewCredentialsWithoutVersion(c *gc.C) {
   651  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   652  		"username":    "user",
   653  		"password":    "secret",
   654  		"tenant-name": "someTenant",
   655  		"tenant-id":   "someID",
   656  	})
   657  	clouldSpec := environs.CloudSpec{
   658  		Type:       "openstack",
   659  		Region:     "openstack_region",
   660  		Name:       "openstack",
   661  		Endpoint:   "http://endpoint",
   662  		Credential: &creds,
   663  	}
   664  	cred, authmode, err := newCredentials(clouldSpec)
   665  	c.Assert(err, jc.ErrorIsNil)
   666  	c.Check(cred, gc.Equals, identity.Credentials{
   667  		URL:           "http://endpoint",
   668  		User:          "user",
   669  		Secrets:       "secret",
   670  		Region:        "openstack_region",
   671  		TenantName:    "someTenant",
   672  		TenantID:      "someID",
   673  		Domain:        "",
   674  		UserDomain:    "",
   675  		ProjectDomain: "",
   676  	})
   677  	c.Check(authmode, gc.Equals, identity.AuthUserPass)
   678  }
   679  
   680  func (s *providerUnitTests) TestNewCredentialsWithFaultVersionandProjectDomainName(c *gc.C) {
   681  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   682  		"version":             "abc",
   683  		"username":            "user",
   684  		"password":            "secret",
   685  		"tenant-name":         "someTenant",
   686  		"tenant-id":           "someID",
   687  		"project-domain-name": "openstack_projectdomain",
   688  	})
   689  	clouldSpec := environs.CloudSpec{
   690  		Type:       "openstack",
   691  		Region:     "openstack_region",
   692  		Name:       "openstack",
   693  		Endpoint:   "http://endpoint",
   694  		Credential: &creds,
   695  	}
   696  	_, _, err := newCredentials(clouldSpec)
   697  	c.Assert(err, gc.NotNil)
   698  	c.Assert(err, gc.ErrorMatches,
   699  		"cred.Version is not a valid integer type : strconv.Atoi: parsing \"abc\": invalid syntax")
   700  }
   701  func (s *providerUnitTests) TestNewCredentialsWithoutVersionwithProjectDomain(c *gc.C) {
   702  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   703  		"username":            "user",
   704  		"password":            "secret",
   705  		"tenant-name":         "someTenant",
   706  		"tenant-id":           "someID",
   707  		"project-domain-name": "openstack_projectdomain",
   708  	})
   709  	clouldSpec := environs.CloudSpec{
   710  		Type:       "openstack",
   711  		Region:     "openstack_region",
   712  		Name:       "openstack",
   713  		Endpoint:   "http://endpoint",
   714  		Credential: &creds,
   715  	}
   716  	cred, authmode, err := newCredentials(clouldSpec)
   717  	c.Assert(err, jc.ErrorIsNil)
   718  	c.Check(cred, gc.Equals, identity.Credentials{
   719  		URL:           "http://endpoint",
   720  		User:          "user",
   721  		Secrets:       "secret",
   722  		Region:        "openstack_region",
   723  		TenantName:    "someTenant",
   724  		TenantID:      "someID",
   725  		Domain:        "",
   726  		UserDomain:    "",
   727  		ProjectDomain: "openstack_projectdomain",
   728  	})
   729  	c.Check(authmode, gc.Equals, identity.AuthUserPassV3)
   730  }
   731  
   732  func (s *providerUnitTests) TestNewCredentialsWithoutVersionwithUserDomain(c *gc.C) {
   733  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   734  		"username":         "user",
   735  		"password":         "secret",
   736  		"tenant-name":      "someTenant",
   737  		"tenant-id":        "someID",
   738  		"user-domain-name": "openstack_userdomain",
   739  	})
   740  	clouldSpec := environs.CloudSpec{
   741  		Type:       "openstack",
   742  		Region:     "openstack_region",
   743  		Name:       "openstack",
   744  		Endpoint:   "http://endpoint",
   745  		Credential: &creds,
   746  	}
   747  	cred, authmode, err := newCredentials(clouldSpec)
   748  	c.Assert(err, jc.ErrorIsNil)
   749  	c.Check(cred, gc.Equals, identity.Credentials{
   750  		URL:           "http://endpoint",
   751  		User:          "user",
   752  		Secrets:       "secret",
   753  		Region:        "openstack_region",
   754  		TenantName:    "someTenant",
   755  		TenantID:      "someID",
   756  		Version:       0,
   757  		Domain:        "",
   758  		UserDomain:    "openstack_userdomain",
   759  		ProjectDomain: "",
   760  	})
   761  	c.Check(authmode, gc.Equals, identity.AuthUserPassV3)
   762  }
   763  
   764  func (s *providerUnitTests) TestNewCredentialsWithVersion2(c *gc.C) {
   765  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   766  		"version":     "2",
   767  		"username":    "user",
   768  		"password":    "secret",
   769  		"tenant-name": "someTenant",
   770  		"tenant-id":   "someID",
   771  	})
   772  	clouldSpec := environs.CloudSpec{
   773  		Type:       "openstack",
   774  		Region:     "openstack_region",
   775  		Name:       "openstack",
   776  		Endpoint:   "http://endpoint",
   777  		Credential: &creds,
   778  	}
   779  	cred, authmode, err := newCredentials(clouldSpec)
   780  	c.Assert(err, jc.ErrorIsNil)
   781  	c.Check(cred, gc.Equals, identity.Credentials{
   782  		URL:           "http://endpoint",
   783  		User:          "user",
   784  		Secrets:       "secret",
   785  		Region:        "openstack_region",
   786  		TenantName:    "someTenant",
   787  		TenantID:      "someID",
   788  		Version:       2,
   789  		Domain:        "",
   790  		UserDomain:    "",
   791  		ProjectDomain: "",
   792  	})
   793  	c.Check(authmode, gc.Equals, identity.AuthUserPass)
   794  }
   795  
   796  func (s *providerUnitTests) TestNewCredentialsWithVersion2AndDomain(c *gc.C) {
   797  	creds := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   798  		"version":             "2",
   799  		"username":            "user",
   800  		"password":            "secret",
   801  		"tenant-name":         "someTenant",
   802  		"tenant-id":           "someID",
   803  		"project-domain-name": "openstack_projectdomain",
   804  	})
   805  	clouldSpec := environs.CloudSpec{
   806  		Type:       "openstack",
   807  		Region:     "openstack_region",
   808  		Name:       "openstack",
   809  		Endpoint:   "http://endpoint",
   810  		Credential: &creds,
   811  	}
   812  	cred, authmode, err := newCredentials(clouldSpec)
   813  	c.Assert(err, jc.ErrorIsNil)
   814  	c.Check(cred, gc.Equals, identity.Credentials{
   815  		URL:           "http://endpoint",
   816  		User:          "user",
   817  		Secrets:       "secret",
   818  		Region:        "openstack_region",
   819  		TenantName:    "someTenant",
   820  		TenantID:      "someID",
   821  		Version:       2,
   822  		Domain:        "",
   823  		UserDomain:    "",
   824  		ProjectDomain: "openstack_projectdomain",
   825  	})
   826  	c.Check(authmode, gc.Equals, identity.AuthUserPass)
   827  }