diff --git a/octavia_f5/common/config.py b/octavia_f5/common/config.py index 4a0614c..06a05d1 100644 --- a/octavia_f5/common/config.py +++ b/octavia_f5/common/config.py @@ -205,6 +205,10 @@ def setup_logging(conf): item_type=cfg.types.URI(schemes=['http', 'https']), default=[], help=_('The URL of the bigip vcmp host devices')), + cfg.BoolOpt('vcmp_rseries', + default=False, + help=_("Whether the BigIP vcmp host device belongs to the " + "r-Series and thus uses the F5OS-A API")), cfg.ListOpt('override_vcmp_guest_names', default=[], help=_('List of vcmp guest names to use for identifying the ' diff --git a/octavia_f5/controller/worker/controller_worker.py b/octavia_f5/controller/worker/controller_worker.py index fc67729..93680bc 100644 --- a/octavia_f5/controller/worker/controller_worker.py +++ b/octavia_f5/controller/worker/controller_worker.py @@ -56,24 +56,24 @@ class ControllerWorker(object): 'octavia_as3_worker_queue', 'Number of items in AS3 worker queue', ['octavia_host']) def __init__(self): - self._repositories = repo.Repositories() - self._loadbalancer_repo = f5_repos.LoadBalancerRepository() - self._amphora_repo = repo.AmphoraRepository() - self._health_mon_repo = repo.HealthMonitorRepository() - self._listener_repo = f5_repos.ListenerRepository() - self._member_repo = repo.MemberRepository() - self._pool_repo = f5_repos.PoolRepository() - self._l7policy_repo = f5_repos.L7PolicyRepository() - self._l7rule_repo = repo.L7RuleRepository() - self._vip_repo = repo.VipRepository() - self._quota_repo = f5_repos.QuotasRepository() - self._az_repo = repo.AvailabilityZoneRepository() - self._azp_repo = repo.AvailabilityZoneProfileRepository() - self.queue = SetQueue() + # self._repositories = repo.Repositories() + # self._loadbalancer_repo = f5_repos.LoadBalancerRepository() + # self._amphora_repo = repo.AmphoraRepository() + # self._health_mon_repo = repo.HealthMonitorRepository() + # self._listener_repo = f5_repos.ListenerRepository() + # self._member_repo = repo.MemberRepository() + # self._pool_repo = f5_repos.PoolRepository() + # self._l7policy_repo = f5_repos.L7PolicyRepository() + # self._l7rule_repo = repo.L7RuleRepository() + # self._vip_repo = repo.VipRepository() + # self._quota_repo = f5_repos.QuotasRepository() + # self._az_repo = repo.AvailabilityZoneRepository() + # self._azp_repo = repo.AvailabilityZoneProfileRepository() + # self.queue = SetQueue() # instantiate managers/drivers - self.status = status_manager.StatusManager() - self.sync = sync_manager.SyncManager(self.status, self._loadbalancer_repo) + # self.status = status_manager.StatusManager() + # self.sync = sync_manager.SyncManager(self.status, self._loadbalancer_repo) self.l2sync = l2_sync_manager.L2SyncManager() self.network_driver = driver_utils.get_network_driver() diff --git a/octavia_f5/controller/worker/flows/f5_flows.py b/octavia_f5/controller/worker/flows/f5_flows.py index 807f214..3febf3a 100644 --- a/octavia_f5/controller/worker/flows/f5_flows.py +++ b/octavia_f5/controller/worker/flows/f5_flows.py @@ -12,17 +12,24 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg from oslo_log import log as logging from taskflow import flow from taskflow.patterns import unordered_flow, linear_flow from octavia.network import data_models as network_models -from octavia_f5.controller.worker.tasks import f5_tasks +from octavia_f5.controller.worker.tasks import f5_tasks, f5_tasks_rseries +CONF = cfg.CONF LOG = logging.getLogger(__name__) class F5Flows(object): + + def __init__(self):#, f5os_a_api=CONF.networking.vcmp_rseries): + self.f5os_a_api = False#f5os_a_api + # TODO use f5os_a_api to distinguish, which tasks to use + def make_ensure_l2_flow(self, selfips: [network_models.Port], store: dict) -> flow.Flow: """ Construct and return a flow to ensure complete L2 configuration for a new partition. diff --git a/octavia_f5/controller/worker/l2_sync_manager.py b/octavia_f5/controller/worker/l2_sync_manager.py index d00e204..8817be2 100644 --- a/octavia_f5/controller/worker/l2_sync_manager.py +++ b/octavia_f5/controller/worker/l2_sync_manager.py @@ -42,17 +42,20 @@ class L2SyncManager(BaseTaskFlowEngine): _metric_failed_futures = prometheus.metrics.Counter( 'octavia_l2_failed_futures', 'Failed l2 task futures', ['device', 'task']) - def __init__(self): + def __init__(self, foo=CONF.networking.vcmp_urls): super(L2SyncManager).__init__() self._bigips = list(self.initialize_bigips(CONF.f5_agent.bigip_urls)) - self._vcmps = list(self.initialize_bigips(CONF.networking.vcmp_urls)) + self._vcmps = list(self.initialize_bigips(CONF.networking.vcmp_urls, + r_series_vcmp_host=CONF.networking.vcmp_rseries)) self._f5flows = f5_flows.F5Flows() self._network_driver = driver_utils.get_network_driver() self.executor = futures.ThreadPoolExecutor(max_workers=CONF.networking.max_workers) - def initialize_bigips(self, bigip_urls: [str]): + def initialize_bigips(self, bigip_urls: [str], r_series_vcmp_host=False): """ Initialize a BigIP client. This is used for both, vCMP hosts and guests. + + :param r_series_vcmp_host: Whether we're initializing rSeries vCMP hosts """ if CONF.f5_agent.dry_run: @@ -66,7 +69,7 @@ def initialize_bigips(self, bigip_urls: [str]): 'verify': CONF.f5_agent.bigip_verify} if CONF.f5_agent.bigip_token: - kwargs['auth'] = bigip_auth.BigIPTokenAuth(bigip_url) + kwargs['auth'] = bigip_auth.BigIPTokenAuth(bigip_url, r_series=r_series_vcmp_host) else: kwargs['auth'] = bigip_auth.BigIPBasicAuth(bigip_url) diff --git a/octavia_f5/restclient/bigip/bigip_auth.py b/octavia_f5/restclient/bigip/bigip_auth.py index 61bf58e..d38b4ec 100644 --- a/octavia_f5/restclient/bigip/bigip_auth.py +++ b/octavia_f5/restclient/bigip/bigip_auth.py @@ -19,9 +19,12 @@ from requests.auth import HTTPBasicAuth, AuthBase BIGIP_TOKEN_HEADER = 'X-F5-Auth-Token' +BIGIP_TOKEN_HEADER_F5OS_A = 'X-Auth-Token' BIGIP_TOKEN_MAX_TIMEOUT = '36000' +# FIXME What's the equivalent for F5OS-A? BIGIP_TOKENS_PATH = '/mgmt/shared/authz/tokens' BIGIP_LOGIN_PATH = '/mgmt/shared/authn/login' +BIGIP_LOGIN_PATH_F5OS_A = '/data/openconfig-system:system/aaa' class BigIPBasicAuth(HTTPBasicAuth): @@ -36,13 +39,17 @@ def __init__(self, url): class BigIPTokenAuth(AuthBase): """ A requests custom Auth provider that installs a response hook to detect authentication responses and acquires a BigIP authentication token for follow up http requests. """ - def __init__(self, url): + + def __init__(self, url, r_series=False): self.url = url parse_result = parse.urlparse(url, allow_fragments=False) self.username = parse_result.username self.password = parse.unquote(parse_result.password) + self.f5os_a = r_series # Use single global token self.token = None + self._token_endpoint = BIGIP_LOGIN_PATH if not r_series else BIGIP_LOGIN_PATH_F5OS_A + self._token_header = BIGIP_TOKEN_HEADER if not r_series else BIGIP_TOKEN_HEADER_F5OS_A def handle_401(self, r, **kwargs): """ This response hook will fetch a fresh token if encountered an 401 response code. @@ -60,7 +67,7 @@ def handle_401(self, r, **kwargs): r.content r.raw.release_conn() prep = r.request.copy() - prep.headers[BIGIP_TOKEN_HEADER] = self.get_token() + prep.headers[self._token_header] = self.get_token() _r = r.connection.send(prep, **kwargs) _r.history.append(r) @@ -71,7 +78,7 @@ def handle_401(self, r, **kwargs): def __call__(self, r): # No token, no fun if self.token: - r.headers[BIGIP_TOKEN_HEADER] = self.token + r.headers[self._token_header] = self.token # handle 401 case r.register_hook('response', self.handle_401) @@ -85,21 +92,24 @@ def get_token(self): """ Get F5-Auth-Token https://clouddocs.f5.com/products/extensions/f5-declarative-onboarding/latest/authentication.html """ + credentials = { "username": self.username, "password": self.password, - "loginProviderName": "tmos" } + if not self.f5os_a: + credentials["loginProviderName"] = "tmos" auth = (self.username, self.password) - r = requests.post(parse.urljoin(self.url, BIGIP_LOGIN_PATH), + r = requests.post(parse.urljoin(self.url, self._token_endpoint), json=credentials, auth=auth, timeout=10, verify=False) # Handle maximum active login tokens condition if r.status_code == 400 and 'maximum active login tokens' in r.text: # Delete all existing tokens + # TODO what is happening here? Does this have an equivalent in F5OS-A? requests.delete(parse.urljoin(self.url, BIGIP_TOKENS_PATH), auth=auth, timeout=10, verify=False) - r = requests.post(parse.urljoin(self.url, BIGIP_LOGIN_PATH), json=credentials, + r = requests.post(parse.urljoin(self.url, self._token_endpoint), json=credentials, auth=auth, timeout=10, verify=False) r.raise_for_status()