diff --git a/releasenotes/notes/nsxv3-multi-managers-b645c4202a8476e9.yaml b/releasenotes/notes/nsxv3-multi-managers-b645c4202a8476e9.yaml new file mode 100644 index 0000000000..d48988883b --- /dev/null +++ b/releasenotes/notes/nsxv3-multi-managers-b645c4202a8476e9.yaml @@ -0,0 +1,7 @@ +--- +prelude: > + The NSX-v3 plugin supports different credentials for the NSX managers. +features: + The nsxv3 configuration parameters ca_file, nsx_api_user & nsx_api_password + are now lists, in order to support different credentials for each of the + NSX managers. diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index ba2af8ab1d..76b3788fdb 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -246,15 +246,15 @@ nsx_common_opts = [ ] nsx_v3_opts = [ - cfg.StrOpt('nsx_api_user', + cfg.ListOpt('nsx_api_user', deprecated_name='nsx_user', - default='admin', - help=_('User name for the NSX manager')), - cfg.StrOpt('nsx_api_password', + default=['admin'], + help=_('User names for the NSX managers')), + cfg.ListOpt('nsx_api_password', deprecated_name='nsx_password', - default='default', + default=['default'], secret=True, - help=_('Password for the NSX manager')), + help=_('Passwords for the NSX managers')), cfg.ListOpt('nsx_api_managers', default=[], deprecated_name='nsx_manager', @@ -291,10 +291,10 @@ nsx_v3_opts = [ default=10, help=_('Maximum number of times to retry API requests upon ' 'stale revision errors.')), - cfg.StrOpt('ca_file', - help=_('Specify a CA bundle file to use in verifying the NSX ' - 'Manager server certificate. This option is ignored if ' - '"insecure" is set to True. If "insecure" is set to ' + cfg.ListOpt('ca_file', + help=_('Specify a CA bundle files to use in verifying the NSX ' + 'Managers server certificate. This option is ignored ' + 'if "insecure" is set to True. If "insecure" is set to ' 'False and ca_file is unset, the system root CAs will ' 'be used to verify the server certificate.')), cfg.BoolOpt('insecure', diff --git a/vmware_nsx/nsxlib/v3/cluster.py b/vmware_nsx/nsxlib/v3/cluster.py index d4e94d1ca6..48e768062b 100644 --- a/vmware_nsx/nsxlib/v3/cluster.py +++ b/vmware_nsx/nsxlib/v3/cluster.py @@ -124,14 +124,14 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider): def new_connection(self, cluster_api, provider): session = TimeoutSession(cluster_api.http_timeout, cluster_api.http_read_timeout) - session.auth = (cluster_api.username, cluster_api.password) + session.auth = (provider.username, provider.password) # NSX v3 doesn't use redirects session.max_redirects = 0 session.verify = not cluster_api.insecure - if session.verify and cluster_api.ca_file: + if session.verify and provider.ca_file: # verify using the said ca bundle path - session.verify = cluster_api.ca_file + session.verify = provider.ca_file # we are pooling with eventlet in the cluster class adapter = adapters.HTTPAdapter( @@ -172,12 +172,15 @@ class EndpointState(object): class Provider(object): """Data holder for a provider which has a unique id - and a connection URL. + a connection URL, and the credential details. """ - def __init__(self, provider_id, provider_url): + def __init__(self, provider_id, provider_url, username, password, ca_file): self.id = provider_id self.url = provider_url + self.username = username + self.password = password + self.ca_file = ca_file def __str__(self): return str(self.url) @@ -459,11 +462,15 @@ class NSXClusteredAPI(ClusteredAPI): http_read_timeout=None, conn_idle_timeout=None, http_provider=None): - self.username = username or cfg.CONF.nsx_v3.nsx_api_user - self.password = password or cfg.CONF.nsx_v3.nsx_api_password self.retries = retries or cfg.CONF.nsx_v3.http_retries self.insecure = insecure or cfg.CONF.nsx_v3.insecure - self.ca_file = ca_file or cfg.CONF.nsx_v3.ca_file + + # username, password & ca_file may be lists, in order to support + # different credentials per nsx manager + self._username = username or cfg.CONF.nsx_v3.nsx_api_user + self._password = password or cfg.CONF.nsx_v3.nsx_api_password + self._ca_file = ca_file or cfg.CONF.nsx_v3.ca_file + self.conns_per_pool = (concurrent_connections or cfg.CONF.nsx_v3.concurrent_connections) self.http_timeout = http_timeout or cfg.CONF.nsx_v3.http_timeout @@ -494,14 +501,40 @@ class NSXClusteredAPI(ClusteredAPI): conf_urls = cfg.CONF.nsx_v3.nsx_api_managers[:] urls = [] providers = [] - + provider_index = -1 for conf_url in conf_urls: + provider_index += 1 conf_url = _schemed_url(conf_url) if conf_url in urls: LOG.warning(_LW("'%s' already defined in configuration file. " "Skipping."), urlparse.urlunparse(conf_url)) continue urls.append(conf_url) - providers.append(Provider( - conf_url.netloc, urlparse.urlunparse(conf_url))) + providers.append( + Provider( + conf_url.netloc, + urlparse.urlunparse(conf_url), + self.username(provider_index), + self.password(provider_index), + self.ca_file(provider_index))) return providers + + def _attribute_by_index(self, scalar_or_list, index): + if isinstance(scalar_or_list, list): + if not len(scalar_or_list): + return None + if len(scalar_or_list) > index: + return scalar_or_list[index] + # if not long enough - use the first one as default + return scalar_or_list[0] + # this is a scalar + return scalar_or_list + + def username(self, index): + return self._attribute_by_index(self._username, index) + + def password(self, index): + return self._attribute_by_index(self._password, index) + + def ca_file(self, index): + return self._attribute_by_index(self._ca_file, index) diff --git a/vmware_nsx/tests/unit/nsxlib/v3/test_cluster.py b/vmware_nsx/tests/unit/nsxlib/v3/test_cluster.py index c99a33af4c..466c609adb 100644 --- a/vmware_nsx/tests/unit/nsxlib/v3/test_cluster.py +++ b/vmware_nsx/tests/unit/nsxlib/v3/test_cluster.py @@ -39,16 +39,17 @@ class RequestsHTTPProviderTestCase(unittest.TestCase): def test_new_connection(self): mock_api = mock.Mock() - mock_api.username = 'nsxuser' - mock_api.password = 'nsxpassword' + mock_api._username = 'nsxuser' + mock_api._password = 'nsxpassword' mock_api.retries = 100 mock_api.insecure = True - mock_api.ca_file = None + mock_api._ca_file = None mock_api.http_timeout = 99 mock_api.conn_idle_timeout = 39 provider = cluster.NSXRequestsHTTPProvider() session = provider.new_connection( - mock_api, cluster.Provider('9.8.7.6', 'https://9.8.7.6')) + mock_api, cluster.Provider('9.8.7.6', 'https://9.8.7.6', + 'nsxuser', 'nsxpassword', None)) self.assertEqual(session.auth, ('nsxuser', 'nsxpassword')) self.assertEqual(session.verify, False)