diff --git a/CHANGELOG.md b/CHANGELOG.md index 572f3d01e4..dcff4ca437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,12 +37,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Changed ORT Config Generation to be deterministic, which will prevent spurious diffs when nothing actually changed. - Changed the access logs in Traffic Ops to now show the route ID with every API endpoint call. The Route ID is appended to the end of the access log line. - With the addition of multiple server interfaces, interface data is constructed from IP Address/Gateway/Netmask (and their IPv6 counterparts) and Interface Name and Interface MTU fields on services. These **MUST** have proper, valid data before attempting to upgrade or the upgrade **WILL** fail. In particular IP fields need to be valid IP addresses/netmasks, and MTU must only be positive integers of at least 1280. +- The `/servers` and `/servers/{{ID}}}` API endpoints have been updated to use and reflect multi-interface servers. ### Deprecated - Deprecated the non-nullable `DeliveryService` Go struct and other structs that use it. `DeliveryServiceNullable` structs should be used instead. ### Removed - Removed deprecated Traffic Ops Go Client methods. +- Configuration generation logic in the TO API (v1) for: + - `ip_allow.config` + - `parent.config` + - `remap.config` ## [4.1.0] - 2020-04-23 ### Added diff --git a/docs/source/admin/quick_howto/content_invalidation.rst b/docs/source/admin/quick_howto/content_invalidation.rst index 98248eb439..32d2aa28ba 100644 --- a/docs/source/admin/quick_howto/content_invalidation.rst +++ b/docs/source/admin/quick_howto/content_invalidation.rst @@ -18,11 +18,11 @@ **************************** Forcing Content Invalidation **************************** -Invalidating content on the CDN is sometimes necessary when the :term:`origin` was mis-configured and something is cached in the CDN that needs to be removed. +Invalidating content on the CDN is sometimes necessary when the :term:`Origin` was mis-configured and something is cached in the CDN that needs to be removed. .. impl-detail:: Given the size of a typical Traffic Control CDN and the amount of content that can be cached in it, removing the content from all the caches may take a long time. To speed up content invalidation, Traffic Control does not try to remove the content from the caches, but it makes the content inaccessible using the `regex_revalidate plugin for Apache Traffic Server `_. This forces a "re-validation" of the content. -.. Note:: This method forces :term:`cache servers` to "re-validate" content, so in order to work properly the :term:`origin` needs to support revalidation according to section 13 of :rfc:`2616`. +.. Note:: This method forces :term:`cache servers` to "re-validate" content, so in order to work properly the :term:`Origin` needs to support revalidation according to section 13 of :rfc:`2616`. To invalidate content for a specific :term:`Delivery Service`, follow these steps: @@ -42,7 +42,7 @@ To invalidate content for a specific :term:`Delivery Service`, follow these step Select 'Manage Invalidation Requests' -#. Click/tap on the :guilabel:`+` button to open the submission form for a new content invalidation. Fill out this form. The "Path Regex" field should be a `PCRE `_-compatible regular expression that matches all content that must be invalidated - and should **not** match any content that must *not* be invalidated. "TTL (hours)" specifies the number of hours for which the invalidation should remain active. Best practice is to set this to the same as the content's cache lifetime (typically set in the :term:`origin`'s ``Cache-Control`` response header). +#. Click/tap on the :guilabel:`+` button to open the submission form for a new content invalidation. Fill out this form. The "Path Regex" field should be a `PCRE `_-compatible regular expression that matches all content that must be invalidated - and should **not** match any content that must *not* be invalidated. "TTL (hours)" specifies the number of hours for which the invalidation should remain active. Best practice is to set this to the same as the content's cache lifetime (typically set in the :term:`Origin`'s ``Cache-Control`` response header). .. figure:: content_invalidation/03.png :align: center diff --git a/docs/source/admin/quick_howto/oauth_login.rst b/docs/source/admin/quick_howto/oauth_login.rst index 4e76ce96c8..98a7d89f2b 100644 --- a/docs/source/admin/quick_howto/oauth_login.rst +++ b/docs/source/admin/quick_howto/oauth_login.rst @@ -26,7 +26,7 @@ An opt-in configuration for SSO using OAuth is supported and can be configured t .. Note:: The ``POST`` from the API to the OAuth provider to exchange the code for a token expects the response to have the token in JSON format with `access_token` as the desired field (and can include other fields). It also supports a response with just the token itself as the body. Further development work will need to be done to allow other resposne forms or other response fields. -.. Note:: Users must exist in both Traffic Ops as well as in the OAuth provider's system. The user's rights are defined by the :term:`role` assigned to the user. +.. Note:: Users must exist in both Traffic Ops as well as in the OAuth provider's system. The user's rights are defined by the :term:`Role` assigned to the user. To configure OAuth login: diff --git a/docs/source/admin/quick_howto/server_capabililty.rst b/docs/source/admin/quick_howto/server_capabililty.rst index d1246990d9..76ed2e0abd 100644 --- a/docs/source/admin/quick_howto/server_capabililty.rst +++ b/docs/source/admin/quick_howto/server_capabililty.rst @@ -18,9 +18,9 @@ ************************** Manage Server Capabilities ************************** -Server capabilities are designed to enable users with the Operations or Admin :term:`Role` to control the flow of :term:`delivery service` traffic through only the :term:`cache servers` (:term:`Edge` or :term:`Mid`) that have the required capabilities. For example, :term:`delivery services` designed to serve large binary files should only have requests routed to :term:`cache servers` with sufficient disk cache. Currently, this can be controlled at the :term:`Edge-tier` where system operators can explicitly assign only :term:`Edge-tier caches` with sufficient disk cache to the :term:`delivery service`. However, operators do not have control of :term:`Mid-tier cache` assignments and cannot dictate which :term:`Mid-tier caches` are qualified to serve these large binary files. This will cause a problem if a :term:`Mid-tier cache` with insufficient disk cache is asked to serve the :term:`delivery services` large binary files. +Server capabilities are designed to enable users with the Operations or Admin :term:`Role` to control the flow of :term:`Delivery Service` traffic through only the :term:`cache servers` (:term:`Edge` or :term:`Mid`) that have the required capabilities. For example, :term:`Delivery Services` designed to serve large binary files should only have requests routed to :term:`cache servers` with sufficient disk cache. Currently, this can be controlled at the :term:`Edge-tier` where system operators can explicitly assign only :term:`Edge-tier caches` with sufficient disk cache to the :term:`Delivery Service`. However, operators do not have control of :term:`Mid-tier cache` assignments and cannot dictate which :term:`Mid-tier caches` are qualified to serve these large binary files. This will cause a problem if a :term:`Mid-tier cache` with insufficient disk cache is asked to serve the :term:`Delivery Services`' large binary files. -A list of the server capabilities can be found under :menuselection:`Configure --> Server Capabilities`. Users with a higher-level :term:`role` ("operations" or "admin") can create or delete server capabilities. Server capabilities can only be deleted if they are not currently being used by a :term:`cache server` or required by a :term:`delivery service`. +A list of the server capabilities can be found under :menuselection:`Configure --> Server Capabilities`. Users with a higher-level :term:`Role` ("operations" or "admin") can create or delete server capabilities. Server capabilities can only be deleted if they are not currently being used by a :term:`cache server` or required by a :term:`Delivery Service`. .. figure:: server_capability/server_caps_table.png :align: center @@ -44,12 +44,12 @@ Users with the Operations or Admin :term:`Role` can associate one or more server Manage delivery service required server capabilities ==================================================== -Users with the Operations or Admin :term:`Role` can associate one or more required server capabilities with a :term:`delivery service` by navigating to a :term:`delivery service` via :menuselection:`Services --> Delivery Services` and using the context menu for the :term:`delivery services` table and selecting :menuselection:`Manage Required Server Capabilities` or by navigating to :menuselection:`Services --> Delivery Services --> Delivery Service --> More --> Manage Required Server Capabilities`. +Users with the Operations or Admin :term:`Role` can associate one or more required server capabilities with a :term:`Delivery Service` by navigating to a :term:`Delivery Service` via :menuselection:`Services --> Delivery Services` and using the context menu for the :term:`Delivery Services` table and selecting :menuselection:`Manage Required Server Capabilities` or by navigating to :menuselection:`Services --> Delivery Services --> Delivery Service --> More --> Manage Required Server Capabilities`. -Adding a required server capability to a :term:`delivery service` will ensure two things: +Adding a required server capability to a :term:`Delivery Service` will ensure two things: -1. Only :term:`Edge-tier caches` with the required capability can be assigned to the :term:`delivery service` -2. Only :term:`Mid-tier caches` with the required capability will handle requests of the :term:`delivery service` (if applicable) +1. Only :term:`Edge-tier caches` with the required capability can be assigned to the :term:`Delivery Service` +2. Only :term:`Mid-tier caches` with the required capability will handle requests of the :term:`Delivery Service` (if applicable) .. figure:: server_capability/ds_server_caps_table.png :align: center diff --git a/docs/source/admin/traffic_portal/usingtrafficportal.rst b/docs/source/admin/traffic_portal/usingtrafficportal.rst index 661d6ae853..d77442a584 100644 --- a/docs/source/admin/traffic_portal/usingtrafficportal.rst +++ b/docs/source/admin/traffic_portal/usingtrafficportal.rst @@ -418,33 +418,33 @@ Server management includes the ability to (where applicable): Origins ------- -A table of all :term:`origins`. These are automatically created for the :term:`origins` served by :term:`Delivery Services` throughout all CDNs, but additional ones can be created at will. The table has the following columns: +A table of all :term:`Origins`. These are automatically created for the :term:`Origins` served by :term:`Delivery Services` throughout all CDNs, but additional ones can be created at will. The table has the following columns: -:Name: The name of the :term:`origin`. If this :term:`origin` was created automatically for a :term:`Delivery Service`, this will be the :ref:`ds-xmlid` of that :term:`Delivery Service`. -:Tenant: The name of the :term:`Tenant` that owns this :term:`origin` - this is not necessarily the same as the :term:`Tenant` that owns the :term:`Delivery Service` to which this :term:`origin` belongs. -:Primary: Either ``true`` to indicate that this is the "primary" :term:`origin` for the :term:`Delivery Service` to which it is assigned, or ``false`` otherwise. -:Delivery Service: The :ref:`ds-xmlid` of the :term:`Delivery Service` to which this :term:`origin` is assigned. -:FQDN: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin server`. -:IPv4 Address: The :term:`origin`'s IPv4 address, if configured. -:IPv6 Address: The :term:`origin`'s IPv6 address, if configured. -:Protocol: The protocol this :term:`origin` uses to serve content. One of +:Name: The name of the :term:`Origin`. If this :term:`Origin` was created automatically for a :term:`Delivery Service`, this will be the :ref:`ds-xmlid` of that :term:`Delivery Service`. +:Tenant: The name of the :term:`Tenant` that owns this :term:`Origin` - this is not necessarily the same as the :term:`Tenant` that owns the :term:`Delivery Service` to which this :term:`Origin` belongs. +:Primary: Either ``true`` to indicate that this is the "primary" :term:`Origin` for the :term:`Delivery Service` to which it is assigned, or ``false`` otherwise. +:Delivery Service: The :ref:`ds-xmlid` of the :term:`Delivery Service` to which this :term:`Origin` is assigned. +:FQDN: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin`. +:IPv4 Address: The :term:`Origin`'s IPv4 address, if configured. +:IPv6 Address: The :term:`Origin`'s IPv6 address, if configured. +:Protocol: The protocol this :term:`Origin` uses to serve content. One of - http - https -:Port: The port on which the :term:`origin server` listens for incoming HTTP(S) requests. +:Port: The port on which the :term:`Origin` listens for incoming HTTP(S) requests. - .. note:: If this field appears blank in the table, it means that a default was chosen for the :term:`origin` based on its Protocol - ``80`` for "http", ``443`` for "https". + .. note:: If this field appears blank in the table, it means that a default was chosen for the :term:`Origin` based on its Protocol - ``80`` for "http", ``443`` for "https". -:Coordinate: The name of the geographic coordinate pair that defines the physical location of this :term:`origin server`. :term:`Origins` created for :term:`Delivery Services` automatically will **not** have associated Coordinates. This can be rectified on the details pages for said :term:`origins` -:Cachegroup: The :ref:`Name of the Cache Group ` to which this :term:`origin` belongs, if any. -:Profile: The :ref:`profile-name` of a :term:`Profile` used by this :term:`origin`. +:Coordinate: The name of the geographic coordinate pair that defines the physical location of this :term:`Origin`. :term:`Origins` created for :term:`Delivery Services` automatically will **not** have associated Coordinates. This can be rectified on the details pages for said :term:`Origins` +:Cachegroup: The :ref:`Name of the Cache Group ` to which this :term:`Origin` belongs, if any. +:Profile: The :ref:`profile-name` of a :term:`Profile` used by this :term:`Origin`. :term:`Origin` management includes the ability to (where applicable): -- create a new :term:`origin` -- update an existing :term:`origin` -- delete an existing :term:`origin` +- create a new :term:`Origin` +- update an existing :term:`Origin` +- delete an existing :term:`Origin` .. _tp-configure-profiles: @@ -740,7 +740,7 @@ User management includes the ability to (where applicable): - update an existing user - view :term:`Delivery Services` visible to a user -.. Note:: If OAuth is enabled, the username must exist both here as well as with the OAuth provider. A user's rights are defined by the :term:`role` assigned to the user in Traffic Ops. Creating/deleting a user here will update the user's :term:`role` but the user needs to be created/deleted with the OAuth provider as well. +.. Note:: If OAuth is enabled, the username must exist both here as well as with the OAuth provider. A user's rights are defined by the :term:`Role` assigned to the user in Traffic Ops. Creating/deleting a user here will update the user's :term:`Role` but the user needs to be created/deleted with the OAuth provider as well. Tenants ------- diff --git a/docs/source/admin/traffic_router.rst b/docs/source/admin/traffic_router.rst index 80803b5790..789a98a291 100644 --- a/docs/source/admin/traffic_router.rst +++ b/docs/source/admin/traffic_router.rst @@ -211,7 +211,7 @@ Much of a Traffic Router's configuration can be obtained through the :term:`Para | deepcoveragezone.polling.url | CRConfig.json | The location (URL) where a :term:`Deep Coverage Zone Map` may be found. | +-----------------------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------+ | client.steering.forced.diversity | CRConfig.json | When this :term:`Parameter` exists and is exactly "true", it enables the "Client Steering Forced Diversity" feature to diversify | - | | | CLIENT_STEERING results by including more unique :term:`Edge-Tier Cache Servers` in the response to the client's request. | + | | | CLIENT_STEERING results by including more unique :term:`Edge-tier cache servers` in the response to the client's request. | +-----------------------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------+ | tld.soa.expire | CRConfig.json | The value for the "expire" field the Traffic Router DNS Server will respond with on :abbr:`SOA (Start of Authority)` records. | +-----------------------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 783eb2a835..04f84d8b0e 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -102,6 +102,17 @@ Object ``undefined`` No ``response`` object is present in the response payload. Unless the format is otherwise noted, this means that there should be no field list in the "Response Structure" subsection. +Summary +------- +The top-level ``summary`` object is used to provide summary statistics about object collections. In general the use of ``summary`` is left to be defined by API endpoints (subject to some restrictions). When an endpoint uses the ``summary`` object, its fields will be explained in a subsection of the "Response Structure" named "Summary Fields". + +The following, reserved properties of ``summary`` are guaranteed to always have their herein-described meaning. + +.. _reserved-summary-fields: + +``count`` + ``count`` contains an unsigned integer that defines the total number of results that could possibly be returned given the non-pagination query parameters supplied by the client. + Using API Endpoints =================== #. Authenticate with valid Traffic Control user account credentials (the same used by Traffic Portal). diff --git a/docs/source/api/v1/cdns_name_snapshot.rst b/docs/source/api/v1/cdns_name_snapshot.rst index 69214a271b..746d41d1f9 100644 --- a/docs/source/api/v1/cdns_name_snapshot.rst +++ b/docs/source/api/v1/cdns_name_snapshot.rst @@ -116,7 +116,7 @@ Response Structure .. seealso:: :ref:`health-proto` -:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-Tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` @@ -186,9 +186,9 @@ Response Structure :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: "false" - :term:`Cache servers` will be chosen consistently + :term:`cache servers` will be chosen consistently "true" - :term:`Cache servers` will be chosen at random + :term:`cache servers` will be chosen at random :domains: An array of domains served by this :term:`Delivery Service` :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: diff --git a/docs/source/api/v1/cdns_name_snapshot_new.rst b/docs/source/api/v1/cdns_name_snapshot_new.rst index 289ec5a3e8..479fd5975b 100644 --- a/docs/source/api/v1/cdns_name_snapshot_new.rst +++ b/docs/source/api/v1/cdns_name_snapshot_new.rst @@ -115,7 +115,7 @@ Response Structure .. seealso:: :ref:`health-proto` -:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-Tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` @@ -185,9 +185,9 @@ Response Structure :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: "false" - :term:`Cache servers` will be chosen consistently + :term:`cache servers` will be chosen consistently "true" - :term:`Cache servers` will be chosen at random + :term:`cache servers` will be chosen at random :domains: An array of domains served by this :term:`Delivery Service` :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: diff --git a/docs/source/api/v1/deliveryservices_id_servers_eligible.rst b/docs/source/api/v1/deliveryservices_id_servers_eligible.rst index 73e6512219..8357a3706c 100644 --- a/docs/source/api/v1/deliveryservices_id_servers_eligible.rst +++ b/docs/source/api/v1/deliveryservices_id_servers_eligible.rst @@ -23,7 +23,7 @@ ``GET`` ======= -Retrieves properties of :term:`Edge-Tier cache servers` eligible for assignment to a particular :term:`Delivery Service`. Eligibility is determined based on the following properties: +Retrieves properties of :term:`Edge-tier cache servers` eligible for assignment to a particular :term:`Delivery Service`. Eligibility is determined based on the following properties: - The name of the server's :term:`Type` must match one of the glob patterns ``EDGE*``, ``ORG*`` - The server and :term:`Delivery Service` must belong to the same CDN diff --git a/docs/source/api/v1/origins.rst b/docs/source/api/v1/origins.rst index 1ea43ceac9..df865b2c18 100644 --- a/docs/source/api/v1/origins.rst +++ b/docs/source/api/v1/origins.rst @@ -22,7 +22,7 @@ ``GET`` ======= -Gets all requested :term:`origins`. +Gets all requested :term:`Origins`. :Auth. Required: Yes :Roles Required: None @@ -35,22 +35,22 @@ Request Structure +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Name | Required | Description | +=================+==========+===================================================================================================================================================================+ - | cachegroup | no | Return only :term:`origins` within the :term:`Cache Group` that has this :ref:`cache-group-id` | + | cachegroup | no | Return only :term:`Origins` within the :term:`Cache Group` that has this :ref:`cache-group-id` | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | coordinate | no | Return only :term:`origins` located at the geographic coordinates identified by this integral, unique identifier | + | coordinate | no | Return only :term:`Origins` located at the geographic coordinates identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | deliveryservice | no | Return only :term:`origins` that belong to the :term:`Delivery Service` identified by this integral, unique identifier | + | deliveryservice | no | Return only :term:`Origins` that belong to the :term:`Delivery Service` identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | id | no | Return only the :term:`origin` that has this integral, unique identifier | + | id | no | Return only the :term:`Origin` that has this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | name | no | Return only :term:`origins` by this name | + | name | no | Return only :term:`Origins` by this name | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | profileId | no | Return only :term:`origins` which use the :term:`Profile` that has this :ref:`profile-id` | + | profileId | no | Return only :term:`Origins` which use the :term:`Profile` that has this :ref:`profile-id` | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | primary | no | If ``true``, return only :term:`origins` which are the the primary :term:`origin` of the :term:`Delivery Service` to which they belong - if ``false`` return only | - | | | :term:`origins` which are *not* the primary :term:`origin` of the :term:`Delivery Service` to which they belong | + | primary | no | If ``true``, return only :term:`Origins` which are the the primary :term:`Origin` of the :term:`Delivery Service` to which they belong - if ``false`` return only | + | | | :term:`Origins` which are *not* the primary :term:`Origin` of the :term:`Delivery Service` to which they belong | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | tenant | no | Return only :term:`origins` belonging to the tenant identified by this integral, unique identifier | + | tenant | no | Return only :term:`Origins` belonging to the tenant identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | | | | array | @@ -78,25 +78,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` of the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` of the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -149,21 +149,21 @@ Creates a new origin definition. Request Structure ----------------- -:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`origin` shall belong -:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`origin`'s geographic location -:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the new :term:`origin` shall belong -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:ip6Address: An optional string containing the IPv6 address of the :term:`origin` -:ipAddress: An optional string containing the IPv4 address of the :term:`origin` -:isPrimary: An optional boolean which, if ``true`` will set this :term:`origin` as the 'primary' :term:`origin` served by the :term:`Delivery Service` identified by ``deliveryServiceID`` +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the new :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' :term:`Origin` served by the :term:`Delivery Service` identified by ``deliveryServiceID`` .. note:: Though not specifying this field in this request will leave it as ``null`` in the output, Traffic Ops will silently coerce that to its default value: ``false``. :name: A human-friendly name of the :term:`Origin` -:port: An optional port number on which the :term:`origin` listens for incoming TCP connections -:profileId: An optional :ref:`profile-id` ofa :term:`Profile` that shall be used by this :term:`origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` ofa :term:`Profile` that shall be used by this :term:`Origin` :protocol: The protocol used by the origin - must be one of 'http' or 'https' -:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`origin` +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` .. code-block:: http :caption: Request Example @@ -189,25 +189,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -254,7 +254,7 @@ Response Structure ``PUT`` ======= -Updates an :term:`origin` definition. +Updates an :term:`Origin` definition. :Auth. Required: Yes :Roles Required: "admin" or "operations" @@ -267,21 +267,21 @@ Request Structure +------+----------+-------------------------------------------------------------------------------+ | Name | Required | Description | +======+==========+===============================================================================+ - | id | yes | The integral, unique identifier of the :term:`origin` definition being edited | + | id | yes | The integral, unique identifier of the :term:`Origin` definition being edited | +------+----------+-------------------------------------------------------------------------------+ -:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`origin` shall belong -:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`origin`'s geographic location -:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the :term:`origin` shall belong -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:ip6Address: An optional string containing the IPv6 address of the :term:`origin` -:ipAddress: An optional string containing the IPv4 address of the :term:`origin` -:isPrimary: An optional boolean which, if ``true`` will set this :term:`origin` as the 'primary' origin served by the :term:`Delivery Service` identified by ``deliveryServiceID`` +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' origin served by the :term:`Delivery Service` identified by ``deliveryServiceID`` :name: A human-friendly name of the :term:`Origin` -:port: An optional port number on which the :term:`origin` listens for incoming TCP connections -:profileId: An optional :ref:`profile-id` of the :term:`Profile` that shall be used by this :term:`origin` -:protocol: The protocol used by the :term:`origin` - must be one of 'http' or 'https' -:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` of the :term:`Profile` that shall be used by this :term:`Origin` +:protocol: The protocol used by the :term:`Origin` - must be one of 'http' or 'https' +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` .. code-block:: http :caption: Request Example @@ -307,25 +307,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -372,7 +372,7 @@ Response Structure ``DELETE`` ========== -Deletes an :term:`origin` definition. +Deletes an :term:`Origin` definition. :Auth. Required: Yes :Roles Required: "admin" or "operations" @@ -385,7 +385,7 @@ Request Structure +------+----------+--------------------------------------------------------------------------------+ | Name | Required | Description | +======+==========+================================================================================+ - | id | yes | The integral, unique identifier of the :term:`origin` definition being deleted | + | id | yes | The integral, unique identifier of the :term:`Origin` definition being deleted | +------+----------+--------------------------------------------------------------------------------+ .. code-block:: http diff --git a/docs/source/api/v1/regions_name_phys_locations.rst b/docs/source/api/v1/regions_name_phys_locations.rst index b8750d2c8a..f770e2d25a 100644 --- a/docs/source/api/v1/regions_name_phys_locations.rst +++ b/docs/source/api/v1/regions_name_phys_locations.rst @@ -22,7 +22,7 @@ ``POST`` ======== -Creates a new :term:`Physical Location` within the specified :term:`region`. +Creates a new :term:`Physical Location` within the specified :term:`Region`. :Auth. Required: Yes :Roles Required: "admin" or "operations" diff --git a/docs/source/api/v1/server_server_capabilities.rst b/docs/source/api/v1/server_server_capabilities.rst index ddd616759b..8dbf7b1749 100644 --- a/docs/source/api/v1/server_server_capabilities.rst +++ b/docs/source/api/v1/server_server_capabilities.rst @@ -23,7 +23,7 @@ ``GET`` ======= -Gets all associations of :term:`Server Capabilities` to :term:`Cache Servers`. +Gets all associations of :term:`Server Capabilities` to :term:`cache servers`. :Auth. Required: Yes :Roles Required: None @@ -115,7 +115,7 @@ Request Structure :serverId: The integral, unique identifier of a server to be associated with a :term:`Server Capability` :serverCapability: The :term:`Server Capability`'s name to associate -.. note:: The server referenced must be either an :term:`Edge-tier` or :term:`Mid-tier Cache Server`. +.. note:: The server referenced must be either an :term:`Edge-tier` or :term:`Mid-tier cache server`. .. code-block:: http :caption: Request Example diff --git a/docs/source/api/v1/servers_server_configfiles_ats.rst b/docs/source/api/v1/servers_server_configfiles_ats.rst index 1bc98ea53c..2dc4f196c6 100644 --- a/docs/source/api/v1/servers_server_configfiles_ats.rst +++ b/docs/source/api/v1/servers_server_configfiles_ats.rst @@ -21,6 +21,9 @@ .. seealso:: The :ref:`to-api-v1-servers-server-configfiles-ats-filename`, :ref:`to-api-v1-cdns-cdn-configfiles-ats-filename`, and :ref:`to-api-v1-profiles-profile-configfiles-ats-filename` endpoints. +.. deprecated:: 1.4 + Using the API to retrieve generated configuration files for servers is deprecated, and unavailable in more recent API versions. Also, in ATC version 4.x and higher, it is not guaranteed that configuration files will be output correctly, or even successfully. Instead, configuration file generation is now the responsibility of :ref:`atstccfg`. + ``GET`` ======= Gets a list of the configuration files used by ``server`` diff --git a/docs/source/api/v1/servers_server_configfiles_ats_filename.rst b/docs/source/api/v1/servers_server_configfiles_ats_filename.rst index 69c8849f62..045c4405e9 100644 --- a/docs/source/api/v1/servers_server_configfiles_ats_filename.rst +++ b/docs/source/api/v1/servers_server_configfiles_ats_filename.rst @@ -21,6 +21,9 @@ .. seealso:: The :ref:`to-api-v1-servers-server-configfiles-ats` endpoint +.. deprecated:: 1.4 + Using the API to retrieve generated configuration files for servers is deprecated, and unavailable in more recent API versions. Also, in ATC version 4.x and higher, it is not guaranteed that configuration files will be output correctly, or even successfully. Instead, configuration file generation is now the responsibility of :ref:`atstccfg`. + ``GET`` ======= Returns the requested configuration file for download. diff --git a/docs/source/api/v2/cdns_name_snapshot.rst b/docs/source/api/v2/cdns_name_snapshot.rst index e82dd50a2f..bcdc07b7a5 100644 --- a/docs/source/api/v2/cdns_name_snapshot.rst +++ b/docs/source/api/v2/cdns_name_snapshot.rst @@ -116,7 +116,7 @@ Response Structure .. seealso:: :ref:`health-proto` -:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-Tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` @@ -180,9 +180,9 @@ Response Structure :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: "false" - :term:`Cache servers` will be chosen consistently + :term:`cache servers` will be chosen consistently "true" - :term:`Cache servers` will be chosen at random + :term:`cache servers` will be chosen at random :domains: An array of domains served by this :term:`Delivery Service` :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: diff --git a/docs/source/api/v2/cdns_name_snapshot_new.rst b/docs/source/api/v2/cdns_name_snapshot_new.rst index ecb20e1275..dfe2dbd5c5 100644 --- a/docs/source/api/v2/cdns_name_snapshot_new.rst +++ b/docs/source/api/v2/cdns_name_snapshot_new.rst @@ -115,7 +115,7 @@ Response Structure .. seealso:: :ref:`health-proto` -:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-Tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` @@ -179,9 +179,9 @@ Response Structure :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: "false" - :term:`Cache servers` will be chosen consistently + :term:`cache servers` will be chosen consistently "true" - :term:`Cache servers` will be chosen at random + :term:`cache servers` will be chosen at random :domains: An array of domains served by this :term:`Delivery Service` :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: diff --git a/docs/source/api/v2/deliveryservices_id_servers_eligible.rst b/docs/source/api/v2/deliveryservices_id_servers_eligible.rst index f623b0c21e..33727f5cf2 100644 --- a/docs/source/api/v2/deliveryservices_id_servers_eligible.rst +++ b/docs/source/api/v2/deliveryservices_id_servers_eligible.rst @@ -23,7 +23,7 @@ ``GET`` ======= -Retrieves properties of :term:`Edge-Tier cache servers` eligible for assignment to a particular :term:`Delivery Service`. Eligibility is determined based on the following properties: +Retrieves properties of :term:`Edge-tier cache servers` eligible for assignment to a particular :term:`Delivery Service`. Eligibility is determined based on the following properties: - The name of the server's :term:`Type` must match one of the glob patterns ``EDGE*``, ``ORG*`` - The server and :term:`Delivery Service` must belong to the same CDN diff --git a/docs/source/api/v2/origins.rst b/docs/source/api/v2/origins.rst index 3a94af4161..2aa998e229 100644 --- a/docs/source/api/v2/origins.rst +++ b/docs/source/api/v2/origins.rst @@ -21,7 +21,7 @@ ``GET`` ======= -Gets all requested :term:`origins`. +Gets all requested :term:`Origins`. :Auth. Required: Yes :Roles Required: None @@ -34,22 +34,22 @@ Request Structure +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Name | Required | Description | +=================+==========+===================================================================================================================================================================+ - | cachegroup | no | Return only :term:`origins` within the :term:`Cache Group` that has this :ref:`cache-group-id` | + | cachegroup | no | Return only :term:`Origins` within the :term:`Cache Group` that has this :ref:`cache-group-id` | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | coordinate | no | Return only :term:`origins` located at the geographic coordinates identified by this integral, unique identifier | + | coordinate | no | Return only :term:`Origins` located at the geographic coordinates identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | deliveryservice | no | Return only :term:`origins` that belong to the :term:`Delivery Service` identified by this integral, unique identifier | + | deliveryservice | no | Return only :term:`Origins` that belong to the :term:`Delivery Service` identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | id | no | Return only the :term:`origin` that has this integral, unique identifier | + | id | no | Return only the :term:`Origin` that has this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | name | no | Return only :term:`origins` by this name | + | name | no | Return only :term:`Origins` by this name | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | profileId | no | Return only :term:`origins` which use the :term:`Profile` that has this :ref:`profile-id` | + | profileId | no | Return only :term:`Origins` which use the :term:`Profile` that has this :ref:`profile-id` | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | primary | no | If ``true``, return only :term:`origins` which are the the primary :term:`origin` of the :term:`Delivery Service` to which they belong - if ``false`` return only | - | | | :term:`origins` which are *not* the primary :term:`origin` of the :term:`Delivery Service` to which they belong | + | primary | no | If ``true``, return only :term:`Origins` which are the the primary :term:`Origin` of the :term:`Delivery Service` to which they belong - if ``false`` return only | + | | | :term:`Origins` which are *not* the primary :term:`Origin` of the :term:`Delivery Service` to which they belong | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | tenant | no | Return only :term:`origins` belonging to the tenant identified by this integral, unique identifier | + | tenant | no | Return only :term:`Origins` belonging to the tenant identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | | | | array | @@ -77,25 +77,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` of the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` of the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -148,21 +148,21 @@ Creates a new origin definition. Request Structure ----------------- -:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`origin` shall belong -:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`origin`'s geographic location -:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the new :term:`origin` shall belong -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:ip6Address: An optional string containing the IPv6 address of the :term:`origin` -:ipAddress: An optional string containing the IPv4 address of the :term:`origin` -:isPrimary: An optional boolean which, if ``true`` will set this :term:`origin` as the 'primary' :term:`origin` served by the :term:`Delivery Service` identified by ``deliveryServiceID`` +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the new :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' :term:`Origin` served by the :term:`Delivery Service` identified by ``deliveryServiceID`` .. note:: Though not specifying this field in this request will leave it as ``null`` in the output, Traffic Ops will silently coerce that to its default value: ``false``. :name: A human-friendly name of the :term:`Origin` -:port: An optional port number on which the :term:`origin` listens for incoming TCP connections -:profileId: An optional :ref:`profile-id` ofa :term:`Profile` that shall be used by this :term:`origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` ofa :term:`Profile` that shall be used by this :term:`Origin` :protocol: The protocol used by the origin - must be one of 'http' or 'https' -:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`origin` +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` .. code-block:: http :caption: Request Example @@ -188,25 +188,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -253,7 +253,7 @@ Response Structure ``PUT`` ======= -Updates an :term:`origin` definition. +Updates an :term:`Origin` definition. :Auth. Required: Yes :Roles Required: "admin" or "operations" @@ -266,21 +266,21 @@ Request Structure +------+----------+-------------------------------------------------------------------------------+ | Name | Required | Description | +======+==========+===============================================================================+ - | id | yes | The integral, unique identifier of the :term:`origin` definition being edited | + | id | yes | The integral, unique identifier of the :term:`Origin` definition being edited | +------+----------+-------------------------------------------------------------------------------+ -:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`origin` shall belong -:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`origin`'s geographic location -:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the :term:`origin` shall belong -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:ip6Address: An optional string containing the IPv6 address of the :term:`origin` -:ipAddress: An optional string containing the IPv4 address of the :term:`origin` -:isPrimary: An optional boolean which, if ``true`` will set this :term:`origin` as the 'primary' origin served by the :term:`Delivery Service` identified by ``deliveryServiceID`` +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' origin served by the :term:`Delivery Service` identified by ``deliveryServiceID`` :name: A human-friendly name of the :term:`Origin` -:port: An optional port number on which the :term:`origin` listens for incoming TCP connections -:profileId: An optional :ref:`profile-id` of the :term:`Profile` that shall be used by this :term:`origin` -:protocol: The protocol used by the :term:`origin` - must be one of 'http' or 'https' -:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` of the :term:`Profile` that shall be used by this :term:`Origin` +:protocol: The protocol used by the :term:`Origin` - must be one of 'http' or 'https' +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` .. code-block:: http :caption: Request Example @@ -306,25 +306,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -371,7 +371,7 @@ Response Structure ``DELETE`` ========== -Deletes an :term:`origin` definition. +Deletes an :term:`Origin` definition. :Auth. Required: Yes :Roles Required: "admin" or "operations" @@ -384,7 +384,7 @@ Request Structure +------+----------+--------------------------------------------------------------------------------+ | Name | Required | Description | +======+==========+================================================================================+ - | id | yes | The integral, unique identifier of the :term:`origin` definition being deleted | + | id | yes | The integral, unique identifier of the :term:`Origin` definition being deleted | +------+----------+--------------------------------------------------------------------------------+ .. code-block:: http diff --git a/docs/source/api/v2/server_server_capabilities.rst b/docs/source/api/v2/server_server_capabilities.rst index b3f10b3959..3154538bda 100644 --- a/docs/source/api/v2/server_server_capabilities.rst +++ b/docs/source/api/v2/server_server_capabilities.rst @@ -21,7 +21,7 @@ ``GET`` ======= -Gets all associations of :term:`Server Capabilities` to :term:`Cache Servers`. +Gets all associations of :term:`Server Capabilities` to :term:`cache servers`. :Auth. Required: Yes :Roles Required: None @@ -113,7 +113,7 @@ Request Structure :serverId: The integral, unique identifier of a server to be associated with a :term:`Server Capability` :serverCapability: The :term:`Server Capability`'s name to associate -.. note:: The server referenced must be either an :term:`Edge-tier` or :term:`Mid-tier Cache Server`. +.. note:: The server referenced must be either an :term:`Edge-tier` or :term:`Mid-tier cache server`. .. code-block:: http :caption: Request Example diff --git a/docs/source/api/v3/cdns_name_snapshot.rst b/docs/source/api/v3/cdns_name_snapshot.rst index c93af850c4..942f32d451 100644 --- a/docs/source/api/v3/cdns_name_snapshot.rst +++ b/docs/source/api/v3/cdns_name_snapshot.rst @@ -116,7 +116,7 @@ Response Structure .. seealso:: :ref:`health-proto` -:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-Tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` @@ -180,9 +180,9 @@ Response Structure :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: "false" - :term:`Cache servers` will be chosen consistently + :term:`cache servers` will be chosen consistently "true" - :term:`Cache servers` will be chosen at random + :term:`cache servers` will be chosen at random :domains: An array of domains served by this :term:`Delivery Service` :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: diff --git a/docs/source/api/v3/cdns_name_snapshot_new.rst b/docs/source/api/v3/cdns_name_snapshot_new.rst index e722d2d9cc..0920421c44 100644 --- a/docs/source/api/v3/cdns_name_snapshot_new.rst +++ b/docs/source/api/v3/cdns_name_snapshot_new.rst @@ -115,7 +115,7 @@ Response Structure .. seealso:: :ref:`health-proto` -:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-Tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` @@ -179,9 +179,9 @@ Response Structure :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: "false" - :term:`Cache servers` will be chosen consistently + :term:`cache servers` will be chosen consistently "true" - :term:`Cache servers` will be chosen at random + :term:`cache servers` will be chosen at random :domains: An array of domains served by this :term:`Delivery Service` :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: diff --git a/docs/source/api/v3/deliveryservices_id_servers_eligible.rst b/docs/source/api/v3/deliveryservices_id_servers_eligible.rst index 8cef711efd..f6bd15b11b 100644 --- a/docs/source/api/v3/deliveryservices_id_servers_eligible.rst +++ b/docs/source/api/v3/deliveryservices_id_servers_eligible.rst @@ -23,7 +23,7 @@ ``GET`` ======= -Retrieves properties of :term:`Edge-Tier cache servers` eligible for assignment to a particular :term:`Delivery Service`. Eligibility is determined based on the following properties: +Retrieves properties of :term:`Edge-tier cache servers` eligible for assignment to a particular :term:`Delivery Service`. Eligibility is determined based on the following properties: - The name of the server's :term:`Type` must match one of the glob patterns ``EDGE*``, ``ORG*`` - The server and :term:`Delivery Service` must belong to the same CDN diff --git a/docs/source/api/v3/origins.rst b/docs/source/api/v3/origins.rst index 650b8d5ad2..175abc6681 100644 --- a/docs/source/api/v3/origins.rst +++ b/docs/source/api/v3/origins.rst @@ -21,7 +21,7 @@ ``GET`` ======= -Gets all requested :term:`origins`. +Gets all requested :term:`Origins`. :Auth. Required: Yes :Roles Required: None @@ -34,22 +34,22 @@ Request Structure +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Name | Required | Description | +=================+==========+===================================================================================================================================================================+ - | cachegroup | no | Return only :term:`origins` within the :term:`Cache Group` that has this :ref:`cache-group-id` | + | cachegroup | no | Return only :term:`Origins` within the :term:`Cache Group` that has this :ref:`cache-group-id` | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | coordinate | no | Return only :term:`origins` located at the geographic coordinates identified by this integral, unique identifier | + | coordinate | no | Return only :term:`Origins` located at the geographic coordinates identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | deliveryservice | no | Return only :term:`origins` that belong to the :term:`Delivery Service` identified by this integral, unique identifier | + | deliveryservice | no | Return only :term:`Origins` that belong to the :term:`Delivery Service` identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | id | no | Return only the :term:`origin` that has this integral, unique identifier | + | id | no | Return only the :term:`Origin` that has this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | name | no | Return only :term:`origins` by this name | + | name | no | Return only :term:`Origins` by this name | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | profileId | no | Return only :term:`origins` which use the :term:`Profile` that has this :ref:`profile-id` | + | profileId | no | Return only :term:`Origins` which use the :term:`Profile` that has this :ref:`profile-id` | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | primary | no | If ``true``, return only :term:`origins` which are the the primary :term:`origin` of the :term:`Delivery Service` to which they belong - if ``false`` return only | - | | | :term:`origins` which are *not* the primary :term:`origin` of the :term:`Delivery Service` to which they belong | + | primary | no | If ``true``, return only :term:`Origins` which are the the primary :term:`Origin` of the :term:`Delivery Service` to which they belong - if ``false`` return only | + | | | :term:`Origins` which are *not* the primary :term:`Origin` of the :term:`Delivery Service` to which they belong | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - | tenant | no | Return only :term:`origins` belonging to the tenant identified by this integral, unique identifier | + | tenant | no | Return only :term:`Origins` belonging to the tenant identified by this integral, unique identifier | +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | | | | array | @@ -77,25 +77,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` of the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` of the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -148,21 +148,21 @@ Creates a new origin definition. Request Structure ----------------- -:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`origin` shall belong -:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`origin`'s geographic location -:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the new :term:`origin` shall belong -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:ip6Address: An optional string containing the IPv6 address of the :term:`origin` -:ipAddress: An optional string containing the IPv4 address of the :term:`origin` -:isPrimary: An optional boolean which, if ``true`` will set this :term:`origin` as the 'primary' :term:`origin` served by the :term:`Delivery Service` identified by ``deliveryServiceID`` +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the new :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' :term:`Origin` served by the :term:`Delivery Service` identified by ``deliveryServiceID`` .. note:: Though not specifying this field in this request will leave it as ``null`` in the output, Traffic Ops will silently coerce that to its default value: ``false``. :name: A human-friendly name of the :term:`Origin` -:port: An optional port number on which the :term:`origin` listens for incoming TCP connections -:profileId: An optional :ref:`profile-id` ofa :term:`Profile` that shall be used by this :term:`origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` ofa :term:`Profile` that shall be used by this :term:`Origin` :protocol: The protocol used by the origin - must be one of 'http' or 'https' -:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`origin` +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` .. code-block:: http :caption: Request Example @@ -188,25 +188,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -253,7 +253,7 @@ Response Structure ``PUT`` ======= -Updates an :term:`origin` definition. +Updates an :term:`Origin` definition. :Auth. Required: Yes :Roles Required: "admin" or "operations" @@ -266,21 +266,21 @@ Request Structure +------+----------+-------------------------------------------------------------------------------+ | Name | Required | Description | +======+==========+===============================================================================+ - | id | yes | The integral, unique identifier of the :term:`origin` definition being edited | + | id | yes | The integral, unique identifier of the :term:`Origin` definition being edited | +------+----------+-------------------------------------------------------------------------------+ -:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`origin` shall belong -:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`origin`'s geographic location -:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the :term:`origin` shall belong -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:ip6Address: An optional string containing the IPv6 address of the :term:`origin` -:ipAddress: An optional string containing the IPv4 address of the :term:`origin` -:isPrimary: An optional boolean which, if ``true`` will set this :term:`origin` as the 'primary' origin served by the :term:`Delivery Service` identified by ``deliveryServiceID`` +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' origin served by the :term:`Delivery Service` identified by ``deliveryServiceID`` :name: A human-friendly name of the :term:`Origin` -:port: An optional port number on which the :term:`origin` listens for incoming TCP connections -:profileId: An optional :ref:`profile-id` of the :term:`Profile` that shall be used by this :term:`origin` -:protocol: The protocol used by the :term:`origin` - must be one of 'http' or 'https' -:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` of the :term:`Profile` that shall be used by this :term:`Origin` +:protocol: The protocol used by the :term:`Origin` - must be one of 'http' or 'https' +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` .. code-block:: http :caption: Request Example @@ -306,25 +306,25 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`origin` belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`origin` belongs +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs :coordinate: The name of a coordinate pair that defines the origin's geographic location -:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`origin`'s geographic location -:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`origin` belongs -:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`origin` belongs -:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`origin` -:id: An integral, unique identifier for this :term:`origin` +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` :ip6Address: The IPv6 address of the :term:`Origin` :ipAddress: The IPv4 address of the :term:`Origin` -:isPrimary: A boolean value which, when ``true`` specifies this :term:`origin` as the 'primary' :term:`origin` served by ``deliveryService`` -:lastUpdated: The date and time at which this :term:`origin` was last modified -:name: The name of the :term:`origin` -:port: The TCP port on which the :term:`origin` listens -:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`origin` -:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` :protocol: The protocol used by this origin - will be one of 'http' or 'https' -:tenant: The name of the :term:`Tenant` that owns this :term:`origin` -:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`origin` +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` .. code-block:: http :caption: Response Example @@ -371,7 +371,7 @@ Response Structure ``DELETE`` ========== -Deletes an :term:`origin` definition. +Deletes an :term:`Origin` definition. :Auth. Required: Yes :Roles Required: "admin" or "operations" @@ -384,7 +384,7 @@ Request Structure +------+----------+--------------------------------------------------------------------------------+ | Name | Required | Description | +======+==========+================================================================================+ - | id | yes | The integral, unique identifier of the :term:`origin` definition being deleted | + | id | yes | The integral, unique identifier of the :term:`Origin` definition being deleted | +------+----------+--------------------------------------------------------------------------------+ .. code-block:: http diff --git a/docs/source/api/v3/server_server_capabilities.rst b/docs/source/api/v3/server_server_capabilities.rst index 81ef556494..f3ea276818 100644 --- a/docs/source/api/v3/server_server_capabilities.rst +++ b/docs/source/api/v3/server_server_capabilities.rst @@ -21,7 +21,7 @@ ``GET`` ======= -Gets all associations of :term:`Server Capabilities` to :term:`Cache Servers`. +Gets all associations of :term:`Server Capabilities` to :term:`cache servers`. :Auth. Required: Yes :Roles Required: None @@ -113,7 +113,7 @@ Request Structure :serverId: The integral, unique identifier of a server to be associated with a :term:`Server Capability` :serverCapability: The :term:`Server Capability`'s name to associate -.. note:: The server referenced must be either an :term:`Edge-tier` or :term:`Mid-tier Cache Server`. +.. note:: The server referenced must be either an :term:`Edge-tier` or :term:`Mid-tier cache server`. .. code-block:: http :caption: Request Example diff --git a/docs/source/api/v3/servers.rst b/docs/source/api/v3/servers.rst index 508ad999f7..90cf54a24d 100644 --- a/docs/source/api/v3/servers.rst +++ b/docs/source/api/v3/servers.rst @@ -70,36 +70,55 @@ Request Structure Response Structure ------------------ -:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belongs -:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belongs -:cdnId: The integral, unique identifier of the CDN to which the server belongs -:cdnName: Name of the CDN to which the server belongs -:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` -:guid: An identifier used to uniquely identify the server +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belongs +:cdnId: The integral, unique identifier of the CDN to which the server belongs +:cdnName: Name of the CDN to which the server belongs +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:guid: An identifier used to uniquely identify the server .. note:: This is a legacy key which only still exists for compatibility reasons - it should always be ``null`` -:hostName: The (short) hostname of the server -:httpsPort: The port on which the server listens for incoming HTTPS connections/requests -:id: An integral, unique identifier for this server -:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [1]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` -:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:interfaceMtu: The :abbr:`MTU (Maximum Transmission Unit)` configured on ``interfaceName`` -:interfaceName: The name of the primary network interface used by the server -:ip6Address: The IPv6 address and subnet mask of ``interfaceName`` -:ip6IsService: A boolean value which if ``true`` indicates that the IPv6 address will be used for routing content. -:ip6Gateway: The IPv6 address of the gateway used by ``interfaceName`` -:ipAddress: The IPv4 address of ``interfaceName`` -:ipIsService: A boolean value which if ``true`` indicates that the IPv4 address will be used for routing content. -:ipGateway: The IPv4 address of the gateway used by ``interfaceName`` -:ipNetmask: The IPv4 subnet mask used by ``interfaceName`` -:lastUpdated: The date and time at which this server description was last modified -:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' -:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' -:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listens for incoming HTTPS connections/requests +:id: An integral, unique identifier for this server +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be present, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be present, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + +:lastUpdated: The date and time at which this server description was last modified +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + :offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status :physLocation: The name of the physical location where the server resides :physLocationId: An integral, unique identifier for the physical location where the server resides @@ -132,66 +151,74 @@ Response Structure :caption: Response Example HTTP/1.1 200 OK - Access-Control-Allow-Credentials: true - Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie - Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE - Access-Control-Allow-Origin: * + Content-Encoding: gzip Content-Type: application/json - Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly - Whole-Content-Sha512: WyapQctUIhjzEALka5QbBiZRZ58Mlc6MJSwjBeGyJS2UzbL3W6lN/4kvAZtPrP4qMWQBWz6JjbF7Y5lNRASUmQ== + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:06:25 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding X-Server-Name: traffic_ops_golang/ - Date: Mon, 10 Dec 2018 16:13:31 GMT - Content-Length: 939 + Date: Tue, 19 May 2020 16:06:25 GMT + Content-Length: 538 - { "response": [ - { - "cachegroup": "CDN_in_a_Box_Mid", - "cachegroupId": 6, - "cdnId": 2, - "cdnName": "CDN-in-a-Box", - "domainName": "infra.ciab.test", - "guid": null, - "hostName": "mid", - "httpsPort": 443, - "id": 10, - "iloIpAddress": "", - "iloIpGateway": "", - "iloIpNetmask": "", - "iloPassword": "", - "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth0", - "ip6Address": "fc01:9400:1000:8::120", - "ip6Gateway": "fc01:9400:1000:8::1", - "ipAddress": "172.16.239.120", - "ipGateway": "172.16.239.1", - "ipNetmask": "255.255.255.0", - "lastUpdated": "2018-12-05 18:45:05+00", - "mgmtIpAddress": "", - "mgmtIpGateway": "", - "mgmtIpNetmask": "", - "offlineReason": "", - "physLocation": "Apachecon North America 2018", - "physLocationId": 1, - "profile": "ATS_MID_TIER_CACHE", - "profileDesc": "Mid Cache - Apache Traffic Server", - "profileId": 10, - "rack": "", - "revalPending": false, - "routerHostName": "", - "routerPortName": "", - "status": "REPORTED", - "statusId": 3, - "tcpPort": 80, - "type": "MID", - "typeId": 12, - "updPending": false, - "xmppId": "mid", - "xmppPasswd": "", - "ipIsService": true, - "ip6IsService": true - } - ]} + { "response": [{ + "cachegroup": "CDN_in_a_Box_Mid", + "cachegroupId": 6, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "mid", + "httpsPort": 443, + "id": 12, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2020-05-19 14:49:39+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profile": "ATS_MID_TIER_CACHE", + "profileDesc": "Mid Cache - Apache Traffic Server", + "profileId": 10, + "rack": "", + "revalPending": false, + "routerHostName": "", + "routerPortName": "", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "MID", + "typeId": 12, + "updPending": false, + "xmppId": "", + "xmppPasswd": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "172.26.0.4/16", + "gateway": "172.26.0.1", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": false, + "mtu": 1500, + "name": "eth0" + } + ] + }], + "summary": { + "count": 13 + }} + +Summary Fields +"""""""""""""" +The ``summary`` object returned by this method of this endpoint uses only the ``count`` :ref:`standard property `. ``POST`` ======== @@ -208,26 +235,42 @@ Request Structure :domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` :hostName: The (short) hostname of the server :httpsPort: An optional port number on which the server listens for incoming HTTPS connections/requests -:iloIpAddress: An optional IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpGateway: An optional IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpNetmask: An optional IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloPassword: An optional string containing the password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [1]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` -:iloUsername: An optional string containing the user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:interfaceMtu: The :abbr:`MTU (Maximum Transmission Unit)` configured on ``interfaceName`` - - .. note:: In virtually all cases this ought to be 1500. Further note that the only acceptable values are 1500 and 9000. - -:interfaceName: The name of the primary network interface used by the server -:ip6Address: An optional IPv6 address and subnet mask of ``interfaceName`` -:ip6IsService: An optional boolean value which if ``true`` indicates that the IPv6 address will be used for routing content. Defaults to ``true``. -:ip6Gateway: An optional IPv6 address of the gateway used by ``interfaceName`` -:ipAddress: The IPv4 address of ``interfaceName`` -:ipIsService: An optional boolean value which if ``true`` indicates that the IPv4 address will be used for routing content. Defaults to ``true``. -:ipGateway: The IPv4 address of the gateway used by ``interfaceName`` -:ipNetmask: The IPv4 subnet mask used by ``interfaceName`` -:mgmtIpAddress: An optional IPv4 address of some network interface on the server used for 'management' -:mgmtIpGateway: An optional IPv4 address of a gateway used by some network interface on the server used for 'management' -:mgmtIpNetmask: An optional IPv4 subnet mask used by some network interface on the server used for 'management' +:iloIpAddress: An optional IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: An optional IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: An optional IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: An optional string containing the password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` +:iloUsername: An optional string containing the user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be necessary, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be necessary, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. This unsigned integer may not be less than 1280. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + :physLocationId: An integral, unique identifier for the physical location where the server resides :profileId: The :ref:`profile-id` the :term:`Profile` that shall be used by this server :revalPending: A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation @@ -269,6 +312,26 @@ Request Structure "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth0" + } + ], "interfaceMtu": 1500, "interfaceName": "eth0", "ip6Address": "::1", @@ -287,9 +350,7 @@ Request Structure "statusId": 3, "tcpPort": 80, "typeId": 12, - "updPending": false, - "ipIsService": true, - "ip6IsService": true + "updPending": false } Response Structure @@ -306,24 +367,43 @@ Response Structure :hostName: The (short) hostname of the server :httpsPort: The port on which the server listens for incoming HTTPS connections/requests :id: An integral, unique identifier for this server -:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [1]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` -:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:interfaceMtu: The :abbr:`MTU (Maximum Transmission Unit)` configured on ``interfaceName`` -:interfaceName: The name of the primary network interface used by the server -:ip6Address: The IPv6 address and subnet mask of ``interfaceName`` -:ip6IsService: A boolean value which if ``true`` indicates that the IPv6 address will be used for routing content. -:ip6Gateway: The IPv6 address of the gateway used by ``interfaceName`` -:ipAddress: The IPv4 address of ``interfaceName`` -:ipIsService: A boolean value which if ``true`` indicates that the IPv4 address will be used for routing content. -:ipGateway: The IPv4 address of the gateway used by ``interfaceName`` -:ipNetmask: The IPv4 subnet mask used by ``interfaceName`` -:lastUpdated: The date and time at which this server description was last modified -:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' -:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' -:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be present, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be present, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + +:lastUpdated: The date and time at which this server description was last modified +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + :offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status :physLocation: The name of the :term:`Physical Location` where the server resides :physLocationId: An integral, unique identifier for the :term:`Physical Location` where the server resides @@ -355,70 +435,78 @@ Response Structure .. code-block:: http :caption: Response Example - HTTP/1.1 200 OK - Access-Control-Allow-Credentials: true - Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie - Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE - Access-Control-Allow-Origin: * + HTTP/1.1 201 Created + Content-Encoding: gzip Content-Type: application/json - Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly - Whole-Content-Sha512: mcGmmu5ONDg3jmvlkItcw6jxiT1ecmePYujZfmKiZrn5ThKjsSadeJIynaeOK0XVUjHuYHdtdynSqxr2rdzEyA== + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:34:40 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding X-Server-Name: traffic_ops_golang/ - Date: Mon, 10 Dec 2018 17:44:04 GMT - Content-Length: 850 + Date: Tue, 19 May 2020 16:34:40 GMT + Content-Length: 562 { "alerts": [ { - "text": "server was created.", + "text": "Server created", "level": "success" } ], "response": { - "cachegroup": null, + "cachegroup": "CDN_in_a_Box_Mid", "cachegroupId": 6, "cdnId": 2, - "cdnName": null, + "cdnName": "CDN-in-a-Box", "domainName": "infra.ciab.test", "guid": null, "hostName": "test", "httpsPort": 443, - "id": 13, + "id": 14, "iloIpAddress": "", "iloIpGateway": "", "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth0", - "ip6Address": "::1", - "ip6Gateway": "::2", - "ipAddress": "0.0.0.1", - "ipGateway": "0.0.0.2", - "ipNetmask": "255.255.255.0", - "lastUpdated": "2018-12-10 17:44:04+00", + "lastUpdated": "2020-05-19 16:34:40+00", "mgmtIpAddress": "", "mgmtIpGateway": "", "mgmtIpNetmask": "", "offlineReason": "", - "physLocation": null, + "physLocation": "Apachecon North America 2018", "physLocationId": 1, - "profile": null, - "profileDesc": null, + "profile": "ATS_MID_TIER_CACHE", + "profileDesc": "Mid Cache - Apache Traffic Server", "profileId": 10, "rack": null, - "revalPending": null, + "revalPending": false, "routerHostName": "", "routerPortName": "", - "status": null, + "status": "REPORTED", "statusId": 3, "tcpPort": 80, - "type": "", + "type": "MID", "typeId": 12, "updPending": false, - "xmppId": "test", + "xmppId": null, "xmppPasswd": null, - "ipIsService": true, - "ip6IsService": true + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth0" + } + ] }} -.. [1] For more information see the `Wikipedia page on Lights-Out management `_\ . +.. [#ilo] For more information see the `Wikipedia page on Lights-Out management `_\ . diff --git a/docs/source/api/v3/servers_id.rst b/docs/source/api/v3/servers_id.rst index c01f468d38..041eb6e20f 100644 --- a/docs/source/api/v3/servers_id.rst +++ b/docs/source/api/v3/servers_id.rst @@ -42,26 +42,42 @@ Request Structure :domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` :hostName: The (short) hostname of the server :httpsPort: An optional port number on which the server listens for incoming HTTPS connections/requests -:iloIpAddress: An optional IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpGateway: An optional IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloIpNetmask: An optional IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:iloPassword: An optional string containing the password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [1]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` -:iloUsername: An optional string containing the user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_ -:interfaceMtu: The :abbr:`MTU (Maximum Transmission Unit)` configured on ``interfaceName`` - - .. note:: In virtually all cases this ought to be 1500. Further note that the only acceptable values are 1500 and 9000. - -:interfaceName: The name of the primary network interface used by the server -:ip6Address: An optional IPv6 address and subnet mask of ``interfaceName`` -:ip6IsService: An optional boolean value which if ``true`` indicates that the IPv6 address will be used for routing content. Defaults to ``true``. -:ip6Gateway: An optional IPv6 address of the gateway used by ``interfaceName`` -:ipAddress: The IPv4 address of ``interfaceName`` -:ipIsService: An optional boolean value which if ``true`` indicates that the IPv4 address will be used for routing content. Defaults to ``true``. -:ipGateway: The IPv4 address of the gateway used by ``interfaceName`` -:ipNetmask: The IPv4 subnet mask used by ``interfaceName`` -:mgmtIpAddress: An optional IPv4 address of some network interface on the server used for 'management' -:mgmtIpGateway: An optional IPv4 address of a gateway used by some network interface on the server used for 'management' -:mgmtIpNetmask: An optional IPv4 subnet mask used by some network interface on the server used for 'management' +:iloIpAddress: An optional IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: An optional IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: An optional IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: An optional string containing the password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` +:iloUsername: An optional string containing the user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be necessary, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be necessary, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. This unsigned integer must not be less than 1280. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + :physLocationId: An integral, unique identifier for the physical location where the server resides :profileId: The :ref:`profile-id` the :term:`Profile` that shall be used by this server :revalPending: A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation @@ -84,7 +100,7 @@ Request Structure .. code-block:: http :caption: Request Example - PUT /api/3.0/servers/13 HTTP/1.1 + PUT /api/3.0/servers/14 HTTP/1.1 Host: trafficops.infra.ciab.test User-Agent: curl/7.47.0 Accept: */* @@ -103,6 +119,26 @@ Request Structure "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "bond0" + } + ], "interfaceMtu": 1500, "interfaceName": "eth0", "ip6Address": "::1", @@ -121,9 +157,7 @@ Request Structure "statusId": 3, "tcpPort": 80, "typeId": 12, - "updPending": true, - "ipIsService": true, - "ip6IsService": true + "updPending": false } Response Structure @@ -140,27 +174,46 @@ Response Structure :hostName: The (short) hostname of the server :httpsPort: The port on which the server listens for incoming HTTPS connections/requests :id: An integral, unique identifier for this server -:iloIpAddress: The IPv4 address of the server's Integrated Lights-Out (ILO) service\ [1]_ -:iloIpGateway: The IPv4 gateway address of the server's ILO service\ [1]_ -:iloIpNetmask: The IPv4 subnet mask of the server's ILO service\ [1]_ -:iloPassword: The password of the of the server's ILO service user\ [1]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' role(s) -:iloUsername: The user name for the server's ILO service\ [1]_ -:interfaceMtu: The Maximum Transmission Unit (MTU) to configured on ``interfaceName`` -:interfaceName: The name of the primary network interface used by the server -:ip6Address: The IPv6 address and subnet mask of ``interfaceName`` -:ip6IsService: A boolean value which if ``true`` indicates that the IPv6 address will be used for routing content. -:ip6Gateway: The IPv6 address of the gateway used by ``interfaceName`` -:ipAddress: The IPv4 address of ``interfaceName`` -:ipIsService: A boolean value which if ``true`` indicates that the IPv4 address will be used for routing content. -:ipGateway: The IPv4 address of the gateway used by ``interfaceName`` -:ipNetmask: The IPv4 subnet mask used by ``interfaceName`` -:lastUpdated: The date and time at which this server description was last modified -:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' -:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' -:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be present, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be present, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + +:lastUpdated: The date and time at which this server description was last modified +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + :offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status -:physLocation: The name of the physical location where the server resides -:physLocationId: An integral, unique identifier for the physical location where the server resides +:physLocation: The name of the :term:`Physical Location` where the server resides +:physLocationId: An integral, unique identifier for the :term:`Physical Location` where the server resides :profile: The :ref:`profile-name` of the :term:`Profile` used by this server :profileDesc: A :ref:`profile-description` of the :term:`Profile` used by this server :profileId: The :ref:`profile-id` the :term:`Profile` used by this server @@ -194,65 +247,77 @@ Response Structure Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE Access-Control-Allow-Origin: * + Content-Encoding: gzip Content-Type: application/json - Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly - Whole-Content-Sha512: 9lGAMCCC9I/bOpuBSyf3ACffjHeRuXCTuxrA/oU78uYzW5FeFTq5PHSSnsnqKG5E0vWg0Rko0CwguGeNc9IT0w== + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:46:33 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding X-Server-Name: traffic_ops_golang/ - Date: Mon, 10 Dec 2018 17:58:57 GMT - Content-Length: 848 + Date: Tue, 19 May 2020 16:46:33 GMT + Content-Length: 566 { "alerts": [ { - "text": "server was updated.", + "text": "Server updated", "level": "success" } ], "response": { - "cachegroup": null, + "cachegroup": "CDN_in_a_Box_Mid", "cachegroupId": 6, "cdnId": 2, - "cdnName": null, + "cdnName": "CDN-in-a-Box", "domainName": "infra.ciab.test", "guid": null, "hostName": "quest", "httpsPort": 443, - "id": 13, + "id": 14, "iloIpAddress": "", "iloIpGateway": "", "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth0", - "ip6Address": "::1", - "ip6Gateway": "::2", - "ipAddress": "0.0.0.1", - "ipGateway": "0.0.0.2", - "ipNetmask": "255.255.255.0", - "lastUpdated": "2018-12-10 17:58:57+00", + "lastUpdated": "2020-05-19 16:46:33+00", "mgmtIpAddress": "", "mgmtIpGateway": "", "mgmtIpNetmask": "", "offlineReason": "", - "physLocation": null, + "physLocation": "Apachecon North America 2018", "physLocationId": 1, - "profile": null, - "profileDesc": null, + "profile": "ATS_MID_TIER_CACHE", + "profileDesc": "Mid Cache - Apache Traffic Server", "profileId": 10, "rack": null, - "revalPending": null, + "revalPending": false, "routerHostName": "", "routerPortName": "", - "status": null, + "status": "REPORTED", "statusId": 3, "tcpPort": 80, - "type": "", + "type": "MID", "typeId": 12, - "updPending": true, + "updPending": false, "xmppId": null, "xmppPasswd": null, - "ipIsService": true, - "ip6IsService": true + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "bond0" + } + ] }} ``DELETE`` @@ -261,7 +326,10 @@ Allow user to delete server through api. :Auth. Required: Yes :Roles Required: "admin" or "operations" -:Response Type: ``undefined`` +:Response Type: Object + + .. versionchanged:: 3.0 + In older versions of the API, this endpoint did not return a response object. It now returns a representation of the deleted server. Request Structure ----------------- @@ -276,7 +344,7 @@ Request Structure .. code-block:: http :caption: Request Example - DELETE /api/3.0/servers/13 HTTP/1.1 + DELETE /api/3.0/servers/14 HTTP/1.1 Host: trafficops.infra.ciab.test User-Agent: curl/7.47.0 Accept: */* @@ -284,26 +352,158 @@ Request Structure Response Structure ------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belonged +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belonged +:cdnId: The integral, unique identifier of the CDN to which the server belonged +:cdnName: Name of the CDN to which the server belonged +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:guid: An identifier used to uniquely identify the server + + .. note:: This is a legacy key which only still exists for compatibility reasons - it should always be ``null`` + +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listened for incoming HTTPS connections/requests +:id: An integral, unique identifier for this server +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces that were in use by the server + + :ipAddresses: A set of objects representing IP Addresses that were assigned to this network interface + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors would have considered the entire server unhealthy - which includes *all* configured network interfaces. If this was ``null``, it has the meaning "no limit". It had no effect if ``monitor`` was not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should have been monitored by Traffic Monitor for statistics and health consideration + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information was either not available or not applicable for this interface. + :name: The name of the interface. It is the same as the network interface's device name on the server, e.g. ``eth0``. + +:lastUpdated: The date and time at which this server description was last modified +:mgmtIpAddress: The IPv4 address of some network interface on the server that was used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server that was used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server that was used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:offlineReason: A user-entered reason why the server was in ADMIN_DOWN or OFFLINE status +:physLocation: The name of the physical location where the server resided +:physLocationId: An integral, unique identifier for the physical location where the server resided +:profile: The :ref:`profile-name` of the :term:`Profile` which was used by this server +:profileDesc: A :ref:`profile-description` of the :term:`Profile` which was used by this server +:profileId: The :ref:`profile-id` the :term:`Profile` which was used by this server +:revalPending: A boolean value which, if ``true`` indicates that this server had pending content invalidation/revalidation +:rack: A string indicating "server rack" location +:routerHostName: The human-readable name of the router responsible for reaching this server +:routerPortName: The human-readable name of the port used by the router responsible for reaching this server +:status: The :term:`Status` of the server + + .. seealso:: :ref:`health-proto` + +:statusId: The integral, unique identifier of the status of this server + + .. seealso:: :ref:`health-proto` + +:tcpPort: The port on which this server listened for incoming TCP connections + + .. note:: This is typically thought of as synonymous with "HTTP port", as the port specified by ``httpsPort`` may also be used for incoming TCP connections. + +:type: The name of the :term:`Type` of this server +:typeId: The integral, unique identifier of the 'type' of this server +:updPending: A boolean value which, if ``true``, indicates that the server had updates of some kind pending, typically to be acted upon by Traffic Ops :term:`ORT` +:xmppId: An identifier to be used in XMPP communications with the server - in nearly all cases this will be the same as ``hostName`` +:xmppPasswd: The password used in XMPP communications with the server + .. code-block:: http :caption: Response Example HTTP/1.1 200 OK - Access-Control-Allow-Credentials: true - Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie - Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE - Access-Control-Allow-Origin: * + Content-Encoding: gzip Content-Type: application/json - Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly - Whole-Content-Sha512: JZdjKJYWN9w9NF6VE/rVkGUqecycKB2ABkkI4LNDmgpJLwu53bRHAA+4uWrow0zuba/4MSEhHKshutziypSxPg== + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:50:13 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding X-Server-Name: traffic_ops_golang/ - Date: Mon, 10 Dec 2018 18:23:21 GMT - Content-Length: 61 + Date: Tue, 19 May 2020 16:50:13 GMT + Content-Length: 568 { "alerts": [ { - "text": "server was deleted.", + "text": "Server deleted", "level": "success" } - ]} + ], + "response": { + "cachegroup": "CDN_in_a_Box_Mid", + "cachegroupId": 6, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "quest", + "httpsPort": 443, + "id": 14, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2020-05-19 16:46:33+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profile": "ATS_MID_TIER_CACHE", + "profileDesc": "Mid Cache - Apache Traffic Server", + "profileId": 10, + "rack": null, + "revalPending": false, + "routerHostName": "", + "routerPortName": "", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "MID", + "typeId": 12, + "updPending": false, + "xmppId": null, + "xmppPasswd": null, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": false + }, + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "bond0" + } + ] + }} -.. [1] For more information see the `Wikipedia page on Lights-Out management `_\ . +.. [#ilo] For more information see the `Wikipedia page on Lights-Out management `_\ . diff --git a/docs/source/basics/content_delivery_networks.rst b/docs/source/basics/content_delivery_networks.rst index 61b15135a6..94fd8fa993 100644 --- a/docs/source/basics/content_delivery_networks.rst +++ b/docs/source/basics/content_delivery_networks.rst @@ -18,7 +18,7 @@ Content Delivery Networks ************************* The vast majority of today's Internet traffic is media files (often video or audio) being sent from a single source (the *Content Provider*) to many thousands or even millions of destinations (the *Content Consumers*). :abbr:`CDN (Content Delivery Network)`\ s are the technology that make that one-to-many distribution efficient. A :abbr:`CDN (Content Delivery Network)` is a distributed system of servers for delivering content over HTTP(S). These servers are deployed in multiple locations with the goal of optimizing the delivery of content to the end users, while minimizing the traffic on the network. A :abbr:`CDN (Content Delivery Network)` typically consists of the following: -:term:`Cache Servers` +:term:`cache servers` The :dfn:`cache server` is a server that both proxies the requests and caches the results for reuse. Traffic Control uses `Apache Traffic Server `_ to provide :term:`cache servers`. Content Router diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index d2c85ca939..1c2553b78c 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -32,9 +32,9 @@ Glossary cache servers The main function of a CDN is to proxy requests from clients to :term:`origin servers` and cache the results. To proxy, in the CDN context, is to obtain content using HTTP from an :term:`origin server` on behalf of a client. To cache is to store the results so they can be reused when other clients are requesting the same content. There are three types of proxies in use on the Internet today: - - :term:`Reverse Proxy`: Used by Traffic Control for Edge-tier :dfn:`cache servers`. - - :term:`Forward Proxy`: Used by Traffic Control for Mid-tier :dfn:`cache servers`. - - Transparent Proxy: These are not used by Traffic Control. If you are interested you can learn more about transparent proxies on `wikipedia `_. + - :term:`reverse proxy`: Used by Traffic Control for Edge-tier :dfn:`cache servers`. + - :term:`forward proxy`: Used by Traffic Control for Mid-tier :dfn:`cache servers`. + - transparent proxy: These are not used by Traffic Control. If you are interested you can learn more about transparent proxies on `wikipedia `_. Cache Group Cache Groups @@ -133,7 +133,7 @@ Glossary Federations :dfn:`Federations` allow for other ("federated") CDNs (e.g. at a different :abbr:`ISP (Internet Service Provider)`) to add a list of DNS resolvers and an :abbr:`FQDN (Fully Qualified Domain Name)` to be used in a DNS CNAME record for a :term:`Delivery Service`. When a request is made from one of the federated CDN's clients, Traffic Router will return the CNAME record configured from the federation mapping. This allows the federated CDN to serve the content without the content provider changing the URL, or having to manage multiple URLs. For example, if the external CDN was actually another :abbr:`ATC (Apache Traffic Control)`-managed CDN, then a federation mapping to direct clients toward it should use the :abbr:`FQDN (Fully Qualified Domain Name)` of a :term:`Delivery Service` on the external CDN. - Federations only have meaning to DNS-routed :term:`Delivery Services` - HTTP-routed Delivery services should instead treat the external :abbr:`FQDN (Fully Qualified Domain Name)` as an :term:`origin` to achieve the same effect. + Federations only have meaning to DNS-routed :term:`Delivery Services` - HTTP-routed Delivery services should instead treat the external :abbr:`FQDN (Fully Qualified Domain Name)` as an :term:`Origin` to achieve the same effect. .. seealso:: Federations are currently only manageable by directly using the :ref:`to-api`. The endpoints related to federations are :ref:`to-api-federations`, :ref:`to-api-federation_resolvers`, :ref:`to-api-federations-id-deliveryservices`, :ref:`to-api-federations-id-deliveryservices-id`, :ref:`to-api-federations-id-federation_resolvers`, :ref:`to-api-federations-id-users`, and :ref:`to-api-federations-id-users-id`. @@ -157,10 +157,10 @@ Glossary #. The proxy verifies whether the response for ``http://www-origin-cache.cdn.com/foo/bar/fun.html`` is already in the cache. If it is not in the cache: - #. The proxy sends the HTTP request to the :term:`origin`. + #. The proxy sends the HTTP request to the :term:`Origin`. .. code-block:: http - :caption: The :dfn:`Forward Proxy` Requests Content from the :term:`Origin Server` + :caption: The :dfn:`Forward Proxy` Requests Content from the :term:`origin server` GET /foo/bar/fun.html HTTP/1.1 Host: www.origin.com @@ -168,7 +168,7 @@ Glossary #. The :term:`origin server` responds with the requested content. .. code-block:: http - :caption: The :term:`Origin Server`'s Response + :caption: The :term:`origin server`'s Response HTTP/1.1 200 OK Date: Sun, 14 Dec 2014 23:22:44 GMT @@ -234,10 +234,10 @@ Glossary Mid-tier caches Mid-tier cache server Mid-tier cache servers - The tier above the edge tier. The mid tier does not directly serves the end-user and is used as an additional layer between the edge and the :term:`origin`. In a Traffic Control CDN the basic function of the mid cache is that of a :term:`forward proxy`. + The tier above the edge tier. The mid tier does not directly serves the end-user and is used as an additional layer between the edge and the :term:`Origin`. In a Traffic Control CDN the basic function of the mid cache is that of a :term:`forward proxy`. - origin - origins + Origin + Origins origin server origin servers The source of content for the CDN. Usually a redundant HTTP/1.1 webserver. @@ -263,7 +263,7 @@ Glossary Profile Profiles - A :dfn:`Profile` is, most generally, a group of :term:`Parameters` that will be applied to a server. :dfn:`Profiles` are typically re-used by all :term:`Edge-Tier cache servers` within a CDN or :term:`Cache Group`. A :dfn:`Profile` will, in addition to configuration :term:`Parameters`, define the CDN to which a server belongs and the :ref:`"Type" ` of the Profile - which determines some behaviors of Traffic Control components. The allowed :ref:`"Types" ` of :dfn:`Profiles` are **not** the same as :term:`Types`, and are maintained as a PostgreSQL "Enum" in :atc-file:`traffic_ops/app/db/create_tables.sql`. + A :dfn:`Profile` is, most generally, a group of :term:`Parameters` that will be applied to a server. :dfn:`Profiles` are typically re-used by all :term:`Edge-tier cache servers` within a CDN or :term:`Cache Group`. A :dfn:`Profile` will, in addition to configuration :term:`Parameters`, define the CDN to which a server belongs and the :ref:`"Type" ` of the Profile - which determines some behaviors of Traffic Control components. The allowed :ref:`"Types" ` of :dfn:`Profiles` are **not** the same as :term:`Types`, and are maintained as a PostgreSQL "Enum" in :atc-file:`traffic_ops/app/db/create_tables.sql`. .. tip:: A :dfn:`Profile` of the wrong type assigned to a Traffic Control component *will* (in general) cause it to function incorrectly, regardless of the :term:`Parameters` assigned to it. @@ -288,11 +288,11 @@ Glossary reverse proxy reverse proxies - A :dfn:`reverse proxy` acts on behalf of the :term:`origin server` such that the client is (potentially) unaware it is not communicating directly with the :term:`origin`. All Edge-tier :term:`cache servers` in a Traffic Control CDN are :dfn:`reverse proxies`. To the end user a Traffic Control-based CDN appears as a :dfn:`reverse proxy` since it retrieves content from the :term:`origin server`, acting on behalf of that :term:`origin server`. The client requests a URL that has a hostname which resolves to the :dfn:`reverse proxy`'s IP address and, in compliance with the HTTP 1.1 specification (:rfc:`2616`), the client sends a ``Host:`` header to the :dfn:`reverse proxy` that matches the hostname in the URL. The proxy looks up this hostname in a list of mappings to find the :term:`origin` hostname; if the hostname of the ``Host:`` header is not found in the list, the proxy will send an error (usually either ``404 Not Found`` or ``503 Service Unavailable`` as appropriate) to the client. If the supplied hostname is found in this list of mappings, the proxy checks its cache, and when the content is not already present, connects to the :term:`origin` to which the requested ``Host:`` maps requests the path of the original URL, providing the :term:`origin` hostname in the ``Host`` header. The proxy then stores the URL in its cache and serves the contents to the client. When there are subsequent requests for the same URL, a caching proxy serves the content out of its cache - provided :ref:`cache-revalidation` are satisfied - thereby reducing latency and network traffic. + A :dfn:`reverse proxy` acts on behalf of the :term:`origin server` such that the client is (potentially) unaware it is not communicating directly with the :term:`Origin`. All Edge-tier :term:`cache servers` in a Traffic Control CDN are :dfn:`reverse proxies`. To the end user a Traffic Control-based CDN appears as a :dfn:`reverse proxy` since it retrieves content from the :term:`origin server`, acting on behalf of that :term:`origin server`. The client requests a URL that has a hostname which resolves to the :dfn:`reverse proxy`'s IP address and, in compliance with the HTTP 1.1 specification (:rfc:`2616`), the client sends a ``Host:`` header to the :dfn:`reverse proxy` that matches the hostname in the URL. The proxy looks up this hostname in a list of mappings to find the :term:`Origin` hostname; if the hostname of the ``Host:`` header is not found in the list, the proxy will send an error (usually either ``404 Not Found`` or ``503 Service Unavailable`` as appropriate) to the client. If the supplied hostname is found in this list of mappings, the proxy checks its cache, and when the content is not already present, connects to the :term:`Origin` to which the requested ``Host:`` maps requests the path of the original URL, providing the :term:`Origin` hostname in the ``Host`` header. The proxy then stores the URL in its cache and serves the contents to the client. When there are subsequent requests for the same URL, a caching proxy serves the content out of its cache - provided :ref:`cache-revalidation` are satisfied - thereby reducing latency and network traffic. .. seealso:: `The Apache Traffic Server documentation on reverse proxy `_. - To insert a :dfn:`reverse proxy` into a typical HTTP 1.1 request and response flow, the :dfn:`reverse proxy` needs to be told where the :term:`origin server` can be reached (and which :term:`origin` to use for a given request when it's configured to proxy requests for multiple :term:`origins`). In :abbr:`ATS (Apache Traffic Server)` this is handled by adding rules to `the remap.config configuration file `_. The content owner must inform the clients, by updating the URL, to receive the content from the cache and not from the :term:`origin server` directly. For example, clients might be instructed to request content from ``http://www-origin-cache.cdn.com`` which points to a :dfn:`reverse proxy` for the actual :term:`origin` located at ``http://www.origin.com``. + To insert a :dfn:`reverse proxy` into a typical HTTP 1.1 request and response flow, the :dfn:`reverse proxy` needs to be told where the :term:`origin server` can be reached (and which :term:`Origin` to use for a given request when it's configured to proxy requests for multiple :term:`Origins`). In :abbr:`ATS (Apache Traffic Server)` this is handled by adding rules to `the remap.config configuration file `_. The content owner must inform the clients, by updating the URL, to receive the content from the cache and not from the :term:`origin server` directly. For example, clients might be instructed to request content from ``http://www-origin-cache.cdn.com`` which points to a :dfn:`reverse proxy` for the actual :term:`Origin` located at ``http://www.origin.com``. Now, if the client requests ``/foo/bar/fun.html`` from the :dfn:`reverse proxy` the sequence of events is as follows. is given the URL ``http://www-origin-cache.cdn.com/foo/bar/fun.html`` (note the different hostname) and when attempting to obtain that URL, the following occurs: @@ -306,14 +306,14 @@ Glossary GET /foo/bar/fun.html HTTP/1.1 Host: www-origin-cache.cdn.com - #. The :dfn:`reverse proxy` finds out the URL of the true :term:`origin` - in the case of :abbr:`ATS (Apache Traffic Server)` this is done by looking up ``www-origin-cache.cdn.com`` in its remap rules - and finds that it is ``www.origin.com``. + #. The :dfn:`reverse proxy` finds out the URL of the true :term:`Origin` - in the case of :abbr:`ATS (Apache Traffic Server)` this is done by looking up ``www-origin-cache.cdn.com`` in its remap rules - and finds that it is ``www.origin.com``. #. The proxy checks its cache to see if the response for ``GET /foo/bar/fun.html HTTP/1.1`` from ``www.origin.com`` is already in the cache. #. If the response is not in the cache: - #. The proxy sends the request to the actual :term:`origin` + #. The proxy sends the request to the actual :term:`Origin` .. code-block:: http - :caption: :dfn:`Reverse Proxy` Requests Content from the :term:`Origin Server` + :caption: :dfn:`Reverse Proxy` Requests Content from the :term:`origin server` GET /foo/bar/fun.html HTTP/1.1 Host: www.origin.com @@ -321,7 +321,7 @@ Glossary #. The :term:`origin server` responds with the requested content .. code-block:: http - :caption: Response from the :term:`Origin Server` + :caption: Response from the :term:`origin server` HTTP/1.1 200 OK Date: Sun, 14 Dec 2014 23:22:44 GMT @@ -376,7 +376,7 @@ Glossary Server Capability Server Capabilities - A :dfn:`Server Capability` (not to be confused with a "Capability") expresses the capacity of a :term:`cache server` to serve a particular kind of traffic. For example, a :dfn:`Server Capability` could be created named "RAM" to be assigned to :term:`cache servers` that have RAM-disks allocated for content caching. :dfn:`Server Capabilities` can also be required by :term:`Delivery Services`, which will prevent :term:`cache servers` without that :dfn:`Server Capability` from being assigned to them. It also prevents :term:`Mid-tier Cache Servers` without said :term:`Server Capability` from being selected to serve upstream requests from those :term:`Edge-tier Cache Servers` assigned to the requiring :term:`Delivery Services`. + A :dfn:`Server Capability` (not to be confused with a "Capability") expresses the capacity of a :term:`cache server` to serve a particular kind of traffic. For example, a :dfn:`Server Capability` could be created named "RAM" to be assigned to :term:`cache servers` that have RAM-disks allocated for content caching. :dfn:`Server Capabilities` can also be required by :term:`Delivery Services`, which will prevent :term:`cache servers` without that :dfn:`Server Capability` from being assigned to them. It also prevents :term:`Mid-tier cache servers` without said :term:`Server Capability` from being selected to serve upstream requests from those :term:`Edge-tier cache servers` assigned to the requiring :term:`Delivery Services`. Snapshot Snapshots diff --git a/docs/source/overview/cache_groups.rst b/docs/source/overview/cache_groups.rst index 53efabd33d..53b0b0b3b0 100644 --- a/docs/source/overview/cache_groups.rst +++ b/docs/source/overview/cache_groups.rst @@ -22,7 +22,7 @@ A :dfn:`Cache Group` is - ostensibly - exactly what it sounds like it is: a grou The most typical :ref:`Types ` of Cache Groups are EDGE_LOC_ which contain :term:`Edge-tier cache servers` and MID_LOC_ which contain :term:`Mid-tier cache servers`. The latter are each designated as a Parent_ of one or more of the former to fill out the two-tiered caching hierarchy of an :abbr:`ATC (Apache Traffic Control)` CDN. -Consider the example CDN in :numref:`fig-cg_hierarchy`. Here some country/province/region has been divided into quarters: Northeast, Southeast, Northwest, and Southwest. The arrows in the diagram indicate the flow of requests. If a client in the Northwest, for example, were to make a request to the :term:`Delivery Service`, it would first be directed to some :term:`cache server` in the "Northwest" Edge-tier :dfn:`Cache Group`. Should the requested content not be in cache, the Edge-tier server will select a parent from the "West" :dfn:`Cache Group` and pass the request up, caching the result for future use. All Mid-tier :dfn:`Cache Groups` (usually) answer to a single :term:`origin` that provides canonical content. If requested content is not in the Mid-tier cache, then the request will be passed up to the :term:`origin` and the result cached. +Consider the example CDN in :numref:`fig-cg_hierarchy`. Here some country/province/region has been divided into quarters: Northeast, Southeast, Northwest, and Southwest. The arrows in the diagram indicate the flow of requests. If a client in the Northwest, for example, were to make a request to the :term:`Delivery Service`, it would first be directed to some :term:`cache server` in the "Northwest" Edge-tier :dfn:`Cache Group`. Should the requested content not be in cache, the Edge-tier server will select a parent from the "West" :dfn:`Cache Group` and pass the request up, caching the result for future use. All Mid-tier :dfn:`Cache Groups` (usually) answer to a single :term:`Origin` that provides canonical content. If requested content is not in the Mid-tier cache, then the request will be passed up to the :term:`Origin` and the result cached. .. _fig-cg_hierarchy: @@ -307,7 +307,7 @@ This :term:`Type` of Cache Group contains :term:`Mid-tier cache servers` ORG_LOC """"""" -This :term:`Type` of Cache Group contains :term:`origins`. The primary purpose of these is to group :term:`origins` for the purposes of "multi-site-origins", and it's suggested that if that doesn't meet your use-case that these be mostly avoided. In general, it's not strictly necessary to create :term:`origin` *servers* in ATC at all, unless you have to support "multi-site-origins". +This :term:`Type` of Cache Group contains :term:`Origins`. The primary purpose of these is to group :term:`Origins` for the purposes of "multi-site-origins", and it's suggested that if that doesn't meet your use-case that these be mostly avoided. In general, it's not strictly necessary to create :term:`Origin` *servers* in ATC at all, unless you have to support "multi-site-origins". .. seealso:: :ref:`multi-site-origin-qht` diff --git a/docs/source/overview/delivery_services.rst b/docs/source/overview/delivery_services.rst index c5ca7a3394..bc05c66421 100644 --- a/docs/source/overview/delivery_services.rst +++ b/docs/source/overview/delivery_services.rst @@ -56,7 +56,7 @@ Cache URL Expression .. deprecated:: 3.0 This feature is no longer supported by :abbr:`ATS (Apache Traffic Server)` and consequently it will be removed from Traffic Control in the future. -Manipulates the cache key of the incoming requests. Normally, the cache key is the :term:`origin` domain. This can be changed so that multiple services can share a cache key, can also be used to preserve cached content if service origin is changed. +Manipulates the cache key of the incoming requests. Normally, the cache key is the :term:`Origin` domain. This can be changed so that multiple services can share a cache key, can also be used to preserve cached content if service origin is changed. .. warning:: This field provides access to a feature that was only present in :abbr:`ATS (Apache Traffic Server)` 6.X and earlier. As :term:`cache servers` must now use :abbr:`ATS (Apache Traffic Server)` 7.1.X, this field **must** be blank unless all :term:`cache servers` can be guaranteed to use that older :abbr:`ATS (Apache Traffic Server)` version (**NOT** recommended). @@ -70,7 +70,7 @@ A CDN to which this Delivery Service belongs. Only :term:`cache servers` within Check Path ---------- -A request path on the :term:`origin server` which is used to by certain :ref:`Traffic Ops Extensions ` to indicate the "health" of the :term:`origin`. +A request path on the :term:`origin server` which is used to by certain :ref:`Traffic Ops Extensions ` to indicate the "health" of the :term:`Origin`. .. _ds-consistent-hashing-regex: @@ -521,7 +521,7 @@ The Origin Server’s base URL which includes the protocol (http or https). Exam Origin Shield ------------- -An experimental feature that allows administrators to list additional forward proxies that sit between the :term:`Mid-tier` and the :term:`origin`. In most scenarios, this is represented (and required to be input) as a pipe (``|``)-delimited string. +An experimental feature that allows administrators to list additional forward proxies that sit between the :term:`Mid-tier` and the :term:`Origin`. In most scenarios, this is represented (and required to be input) as a pipe (``|``)-delimited string. .. _ds-profile: @@ -577,9 +577,9 @@ Query String Handling Describes how query strings should be handled by the :term:`Edge-tier cache servers` when serving content for this Delivery Service. This is nearly always expressed as an integral, unique identifier for each behavior, though in Traffic Portal a more descriptive value is typically used, or at least provided in addition to the integral, unique identifier. The allowed values and their meanings are: 0 - For the purposes of caching, :term:`Edge-tier cache servers` will consider URLs unique if and only if they are unique up to and including any and all query parameters. They will also pass the query parameters in their own requests to :term:`Mid-tier cache servers` (which in turn will exhibit the same caching behavior and pass the query parameters in requests to the :term:`origin`). (Aliased as "USE" in Traffic Portal tables, and "0 - use qstring in cache key, and pass up" in Traffic Portal forms) + For the purposes of caching, :term:`Edge-tier cache servers` will consider URLs unique if and only if they are unique up to and including any and all query parameters. They will also pass the query parameters in their own requests to :term:`Mid-tier cache servers` (which in turn will exhibit the same caching behavior and pass the query parameters in requests to the :term:`Origin`). (Aliased as "USE" in Traffic Portal tables, and "0 - use qstring in cache key, and pass up" in Traffic Portal forms) 1 - For the purposes of caching, neither :term:`Edge-tier` nor :term:`Mid-tier cache servers` will consider the query parameter string when determining if a URL is stored in cache. However, the query string will still be passed in upstream requests to :term:`Mid-tier cache servers` and in turn the :term:`origin`. (Aliased as "IGNORE" in Traffic Portal tables and "1 - ignore in cache key, and pass up" in Traffic Portal forms) + For the purposes of caching, neither :term:`Edge-tier` nor :term:`Mid-tier cache servers` will consider the query parameter string when determining if a URL is stored in cache. However, the query string will still be passed in upstream requests to :term:`Mid-tier cache servers` and in turn the :term:`Origin`. (Aliased as "IGNORE" in Traffic Portal tables and "1 - ignore in cache key, and pass up" in Traffic Portal forms) 2 The query parameter string will be stripped from URLs immediately when the request is received by an :term:`Edge-tier cache server`. This means it is never considered for the purposes of caching unique URLs and will not be passed in upstream requests. (Aliased as "DROP" in Traffic Portal tables and "2 - drop at edge" in Traffic Portal forms) @@ -657,7 +657,7 @@ For example, if you have an Apache Traffic Server lua plugin which manipulates t Regex Remap Expression ---------------------- -Allows remapping of incoming requests URL using regular expressions to search and replace text. In a more literal sense, this is the raw contents of a configuration file used by the `ATS regex_remap plugin `_. At its most basic, the contents of this field should consist of ``map`` followed by a regular expression and then a "template URL" - all space-separated. The regular expression matches a client's request *path* (i.e. not a full URL - ``/path/to/content`` **not** ``https://origin.example.com/path/to/content``) and when such a match occurs, the request is transformed into a request for the template URL. The most basic usage of the template URL is to use ``$1``-``$9`` to insert the corresponding regular expression capture group. For example, a regular expression of :regexp:`^/a/(.*)` and a template URL of ``https://origin.example.com/b/$1`` maps requests for :term:`origin` content under path ``/a/`` to the same sub-paths under path ``b``. Note that since it's a full URL, this mapping can be made to another server entirely. +Allows remapping of incoming requests URL using regular expressions to search and replace text. In a more literal sense, this is the raw contents of a configuration file used by the `ATS regex_remap plugin `_. At its most basic, the contents of this field should consist of ``map`` followed by a regular expression and then a "template URL" - all space-separated. The regular expression matches a client's request *path* (i.e. not a full URL - ``/path/to/content`` **not** ``https://origin.example.com/path/to/content``) and when such a match occurs, the request is transformed into a request for the template URL. The most basic usage of the template URL is to use ``$1``-``$9`` to insert the corresponding regular expression capture group. For example, a regular expression of :regexp:`^/a/(.*)` and a template URL of ``https://origin.example.com/b/$1`` maps requests for :term:`Origin` content under path ``/a/`` to the same sub-paths under path ``b``. Note that since it's a full URL, this mapping can be made to another server entirely. .. seealso:: The `documentation for the regex_remap plugin `_ for :abbr:`ATS (Apache Traffic Server)` @@ -691,7 +691,7 @@ Required Capabilities --------------------- .. versionadded:: ATCv4 -A Delivery Service can be associated with :term:`Server Capabilities` that it requires :term:`cache servers` serving its content to have. When one or more :term:`Server Capability` is required by a Delivery Service, it will block the assignment of :term:`cache servers` to it that do not have those :term:`Server Capabilities`. Additionally, the :term:`Edge-tier Cache Servers` assigned to a Delivery Service that requires a :term:`Server Capability` will only request content they do not have cached from :term:`Mid-tier Cache Servers` which also have this :term:`Server Capability`. +A Delivery Service can be associated with :term:`Server Capabilities` that it requires :term:`cache servers` serving its content to have. When one or more :term:`Server Capability` is required by a Delivery Service, it will block the assignment of :term:`cache servers` to it that do not have those :term:`Server Capabilities`. Additionally, the :term:`Edge-tier cache servers` assigned to a Delivery Service that requires a :term:`Server Capability` will only request content they do not have cached from :term:`Mid-tier cache servers` which also have this :term:`Server Capability`. Typically, a required :term:`Server Capability` is represented merely by the name of said :term:`Server Capability`. In fact, there's nothing more to a :term:`Server Capability` than its name; it's the responsibility of CDN operators to ensure that they are assigned and required properly. There is no mechanism to detect whether or not a :term:`cache server` has a given :term:`Server Capability`, it must be assigned manually. @@ -711,7 +711,7 @@ Servers can be assigned to Delivery Services using the :ref:`tp-configure-server Signing Algorithm ----------------- -URLs/URIs may be signed using one of two algorithms before a request for the content to which they refer is sent to the :term:`origin` (which in practice can be any upstream network). At the time of this writing, this field is restricted within the Traffic Ops Database to one of two values (or ``NULL``/"None", to indicate no signing should be done). +URLs/URIs may be signed using one of two algorithms before a request for the content to which they refer is sent to the :term:`Origin` (which in practice can be any upstream network). At the time of this writing, this field is restricted within the Traffic Ops Database to one of two values (or ``NULL``/"None", to indicate no signing should be done). .. seealso:: The url_sig `README `_. @@ -866,11 +866,11 @@ CLIENT_STEERING A CLIENT_STEERING Delivery Service is exactly like STEERING except that it provides clients with methods of bypassing the weights, orders, and localizations of targets in order to choose any arbitrary target at will. When utilizing these methods, the client will either directly choose a target immediately or request a list of all available targets from Traffic Router and then choose one to which to send a subsequent request for actual content. CLIENT_STEERING also supports two additional target types: STEERING_GEO_ORDER - These targets behave exactly like STEERING_ORDER targets, but Delivery Services are grouped according to the "locations" of their :term:`origins`. Before choosing a Delivery Service to which to direct the client, Traffic Router will first create subsets of choices according to these groupings, and order them by physical distance from the client (closest to farthest). Within these subsets, the values of the targets establish a strict precedence ordering, just like STEERING_ORDER targets. + These targets behave exactly like STEERING_ORDER targets, but Delivery Services are grouped according to the "locations" of their :term:`Origins`. Before choosing a Delivery Service to which to direct the client, Traffic Router will first create subsets of choices according to these groupings, and order them by physical distance from the client (closest to farthest). Within these subsets, the values of the targets establish a strict precedence ordering, just like STEERING_ORDER targets. STEERING_GEO_WEIGHT - These targets behave exactly like STEERING_WEIGHT targets, but Delivery Services are grouped according to the "locations" of their :term:`origins`. Before choosing a Delivery Service to which to direct the client, Traffic Router will first create subsets of choices according to these groupings, and order them by physical distance from the client (closest to farthest). Within these subsets, the values of the targets establish the likelihood that any given target within the subset will be chosen for the client - effectively determining the spread of traffic across targets within that subset. + These targets behave exactly like STEERING_WEIGHT targets, but Delivery Services are grouped according to the "locations" of their :term:`Origins`. Before choosing a Delivery Service to which to direct the client, Traffic Router will first create subsets of choices according to these groupings, and order them by physical distance from the client (closest to farthest). Within these subsets, the values of the targets establish the likelihood that any given target within the subset will be chosen for the client - effectively determining the spread of traffic across targets within that subset. - .. important:: To make use of the STEERING_GEO_ORDER and/or STEERING_GEO_WEIGHT target types, it is first necessary to ensure that at least the "primary" :term:`origin` of the :term:`Delivery Service` has an associated geographic coordinate pair. This can be done either from the :ref:`tp-configure-origins` page in Traffic Portal, or using the :ref:`to-api-origins` :ref:`to-api` endpoint. + .. important:: To make use of the STEERING_GEO_ORDER and/or STEERING_GEO_WEIGHT target types, it is first necessary to ensure that at least the "primary" :term:`Origin` of the :term:`Delivery Service` has an associated geographic coordinate pair. This can be done either from the :ref:`tp-configure-origins` page in Traffic Portal, or using the :ref:`to-api-origins` :ref:`to-api` endpoint. .. note:: "Steering" is also commonly used to collectively refer to either of the kinds of Delivery Services that can participate in steering behavior (STEERING and CLIENT_STEERING). @@ -888,7 +888,7 @@ CLIENT_STEERING Use Multi-Site Origin Feature ----------------------------- -A boolean value that indicates whether or not this Delivery Service serves content for an :term:`origin` that provides content from two or more redundant servers. There are very few good reasons for this to not be ``false``. When ``true``, Traffic Ops will configure :term:`Mid-tier cache servers` to perform load-balancing and other optimizations for redundant :term:`origin servers`. +A boolean value that indicates whether or not this Delivery Service serves content for an :term:`Origin` that provides content from two or more redundant servers. There are very few good reasons for this to not be ``false``. When ``true``, Traffic Ops will configure :term:`Mid-tier cache servers` to perform load-balancing and other optimizations for redundant :term:`origin servers`. Naturally, this assumes that each redundant server is exactly identical, from request paths to actual content. If Multi-Site Origin is configured for servers that are *not* identical, the client's experience is undefined. Furthermore, the :term:`origin servers` may have differing IP addresses, but **must** serve content for a single :abbr:`FQDN (Fully Qualified Domain Name)` - as defined by the Delivery Service's `Origin Server Base URL`_. These redundant servers **must** be configured as servers (server :term:`Type` ``ORG``) in Traffic Ops - either using the :ref:`appropriate section of Traffic Portal ` or the :ref:`to-api-servers` endpoint. diff --git a/docs/source/overview/profiles_and_parameters.rst b/docs/source/overview/profiles_and_parameters.rst index 6532d73eb1..9b90d5d2f6 100644 --- a/docs/source/overview/profiles_and_parameters.rst +++ b/docs/source/overview/profiles_and_parameters.rst @@ -100,7 +100,7 @@ LOGSTASH_PROFILE .. warning:: For legacy reasons, the names of Profiles of this type *must* begin with ``LOGSTASH_``. This is **not** enforced by the :ref:`to-api` or Traffic Portal, but certain Traffic Control operations/components expect this and will fail to work otherwise! ORG_PROFILE - A Profile that may be used by either :term:`origin servers` or :term:`origins` (no, they aren't the same thing). + A Profile that may be used by either :term:`origin servers` or :term:`Origins` (no, they aren't the same thing). .. warning:: For legacy reasons, the names of Profiles of this type *must* begin with ``MSO``, or contain either ``ORG`` or ``ORIGIN`` anywhere in the name. This is **not** enforced by the :ref:`to-api` or Traffic Portal, but certain Traffic Control operations/components expect this and will fail to work otherwise! @@ -314,7 +314,7 @@ Config Files that match this pattern - where ``anything`` is zero or more charac hosting.config '''''''''''''' -This configuration file is mainly generated based on the assignments of :term:`cache servers` to :term:`Delivery Services` and the :term:`Cache Group` hierarchy, but there are a couple of Parameter :ref:`Names ` that can affect it when assigned to this Config File. When a Parameter assigned to the ``storage.config`` Config File - **NOT this Config File** - with the :ref:`parameter-name` "RAM_Drive_Prefix" *exists*, it will cause lines to be generated in this configuration file for each :term:`Delivery Service` that is of on of the :ref:`Types ` DNS_LIVE (only if the server is an :term:`Edge-Tier Cache Server`), HTTP_LIVE (only if the server is an :term:`Edge-Tier Cache Server`), DNS_LIVE_NATNL, or HTTP_LIVE_NATNL to which the :term:`cache server` to which the :ref:`Profile ` containing that Parameter belongs is assigned. Specifically, it will cause each of them to use ``volume=1`` **UNLESS** the Parameter with the :ref:`parameter-name` "Drive_Prefix" associated with Config File ``storage.config`` - again, **NOT this Config File** - *also* exists, in which case they will use ``volume=2``. +This configuration file is mainly generated based on the assignments of :term:`cache servers` to :term:`Delivery Services` and the :term:`Cache Group` hierarchy, but there are a couple of Parameter :ref:`Names ` that can affect it when assigned to this Config File. When a Parameter assigned to the ``storage.config`` Config File - **NOT this Config File** - with the :ref:`parameter-name` "RAM_Drive_Prefix" *exists*, it will cause lines to be generated in this configuration file for each :term:`Delivery Service` that is of on of the :ref:`Types ` DNS_LIVE (only if the server is an :term:`Edge-tier cache server`), HTTP_LIVE (only if the server is an :term:`Edge-tier cache server`), DNS_LIVE_NATNL, or HTTP_LIVE_NATNL to which the :term:`cache server` to which the :ref:`Profile ` containing that Parameter belongs is assigned. Specifically, it will cause each of them to use ``volume=1`` **UNLESS** the Parameter with the :ref:`parameter-name` "Drive_Prefix" associated with Config File ``storage.config`` - again, **NOT this Config File** - *also* exists, in which case they will use ``volume=2``. .. caution:: If a Parameter with Config File ``storage.config`` and :ref:`parameter-name` "RAM_Drive_Prefix" does *not* exist on a :ref:`Profile `, then the :term:`cache servers` using that :ref:`Profile ` will **be incapable of serving traffic for** :term:`Delivery Services` **of the aforementioned** :ref:`Types `, **even when a** :ref:`"location" ` **Parameter exists**. diff --git a/docs/source/overview/traffic_portal.rst b/docs/source/overview/traffic_portal.rst index 2e22bb3fe1..1ae20e6e68 100644 --- a/docs/source/overview/traffic_portal.rst +++ b/docs/source/overview/traffic_portal.rst @@ -25,6 +25,6 @@ Features - CDN Monitoring - CDN Administration - :term:`Delivery Service` Configuration -- :term:`Cache Server` Maintenance +- :term:`cache server` Maintenance .. seealso:: See :ref:`usingtrafficportal` for an overview of the Traffic Portal UI. diff --git a/docs/source/overview/traffic_router.rst b/docs/source/overview/traffic_router.rst index 697565c536..3773c08aeb 100644 --- a/docs/source/overview/traffic_router.rst +++ b/docs/source/overview/traffic_router.rst @@ -32,7 +32,7 @@ Traffic routing options are often configured at the :term:`Delivery Service` lev DNS Content Routing =================== -For a DNS :term:`Delivery Service` the client might receive a URL such as ``http://video.demo1.mycdn.ciab.test/``. When the :abbr:`LDNS (Local Domain Name Server)` is resolving this ``video.demo1.mycdn.ciab.test`` hostname to an IP address, it ends at Traffic Router because it is the authoritative DNS server for ``mycdn.ciab.test`` and the domains below it, and subsequently responds with a list of IP addresses from the eligible :term:`cache servers` based on the location of the :abbr:`LDNS (Local Domain Name Server)`. When responding, Traffic Router does not know the actual client IP address or the path that the client is going to request. The decision on what :term:`cache server` IP address (or list of :term:`cache server` IP addresses) to return is solely based on the location of the :abbr:`LDNS (Local Domain Name Server)` and the health of the :term:`cache servers`. The client then connects to port 80 (HTTP) or port 443 (HTTPS) on the :term:`cache server`, and sends the ``Host: video.demo1.mycdn.ciab.test`` header. The configuration of the :term:`cache server` includes the "remap rule" ``http://video.demo1.mycdn.ciab.test http://origin.infra.ciab.test`` to map the routed name to an :term:`origin` hostname. +For a DNS :term:`Delivery Service` the client might receive a URL such as ``http://video.demo1.mycdn.ciab.test/``. When the :abbr:`LDNS (Local Domain Name Server)` is resolving this ``video.demo1.mycdn.ciab.test`` hostname to an IP address, it ends at Traffic Router because it is the authoritative DNS server for ``mycdn.ciab.test`` and the domains below it, and subsequently responds with a list of IP addresses from the eligible :term:`cache servers` based on the location of the :abbr:`LDNS (Local Domain Name Server)`. When responding, Traffic Router does not know the actual client IP address or the path that the client is going to request. The decision on what :term:`cache server` IP address (or list of :term:`cache server` IP addresses) to return is solely based on the location of the :abbr:`LDNS (Local Domain Name Server)` and the health of the :term:`cache servers`. The client then connects to port 80 (HTTP) or port 443 (HTTPS) on the :term:`cache server`, and sends the ``Host: video.demo1.mycdn.ciab.test`` header. The configuration of the :term:`cache server` includes the "remap rule" ``http://video.demo1.mycdn.ciab.test http://origin.infra.ciab.test`` to map the routed name to an :term:`Origin` hostname. .. _http-cr: @@ -50,7 +50,7 @@ For an HTTP :term:`Delivery Service` the client might receive a URL such as ``ht Traffic Router uses an HTTP ``302 Found`` response to redirect the client to the best :term:`cache server`. .. code-block:: http - :caption: Traffic Router Redirect to Edge-tier :term:`Cache Server` + :caption: Traffic Router Redirect to Edge-tier :term:`cache server` HTTP/1.1 302 Found Location: http://edge.demo1.mycdn.ciab.test/ diff --git a/lib/go-tc/cachegroups.go b/lib/go-tc/cachegroups.go index f073d70cef..7b9d3061db 100644 --- a/lib/go-tc/cachegroups.go +++ b/lib/go-tc/cachegroups.go @@ -95,4 +95,3 @@ type CachegroupQueueUpdatesRequest struct { CDN *CDNName `json:"cdn"` CDNID *util.JSONIntStr `json:"cdnId"` } - diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go index 9d8398eab8..0904a52590 100644 --- a/lib/go-tc/servers.go +++ b/lib/go-tc/servers.go @@ -3,6 +3,7 @@ package tc import ( "database/sql/driver" "encoding/json" + "errors" "fmt" "net" "time" @@ -29,7 +30,17 @@ import ( * under the License. */ -// ServersResponse is a list of Servers as a response. +// ServersV3Response is the format of a response to a GET request for /servers. +type ServersV3Response struct { + Response []ServerNullable `json:"response"` + Summary struct { + Count uint64 `json:"count"` + } `json:"summary"` + Alerts +} + +// ServersResponse is a list of Servers as a response to an API v2 request. +// This can't change because it will break ORT. Unfortunately. type ServersResponse struct { Response []Server `json:"response"` Alerts @@ -70,23 +81,23 @@ type ServersV3DetailResponse struct { Alerts } -// ServerIpAddress is the data associated with a server's interface's IP address. -type ServerIpAddress struct { +// ServerIPAddress is the data associated with a server's interface's IP address. +type ServerIPAddress struct { Address string `json:"address" db:"address"` Gateway *string `json:"gateway" db:"gateway"` - ServiceAddress bool `json:"service_address" db:"service_address"` + ServiceAddress bool `json:"serviceAddress" db:"service_address"` } // ServerInterfaceInfo is the data associated with a server's interface. type ServerInterfaceInfo struct { - IpAddresses []ServerIpAddress `json:"ipAddresses" db:"ipAddresses"` - MaxBandwidth *int64 `json:"maxBandwidth" db:"max_bandwidth"` + IPAddresses []ServerIPAddress `json:"ipAddresses" db:"ip_addresses"` + MaxBandwidth *uint64 `json:"maxBandwidth" db:"max_bandwidth"` Monitor bool `json:"monitor" db:"monitor"` MTU *uint64 `json:"mtu" db:"mtu"` Name string `json:"name" db:"name"` } -// Value implements the database/sql/driver.Valuer interface. +// Value implements the driver.Valuer interface // marshals struct to json to pass back as a json.RawMessage func (sii *ServerInterfaceInfo) Value() (driver.Value, error) { b, err := json.Marshal(sii) @@ -115,6 +126,61 @@ type LegacyInterfaceDetails struct { IPNetmask *string `json:"ipNetmask" db:"ip_netmask"` } +// ToInterfaces converts a LegacyInterfaceDetails to a slice of +// ServerInterfaceInfo structures. No interfaces will be marked for monitoring, +// and it will generate service addresses according to the passed indicators +// for each address family. +func (lid *LegacyInterfaceDetails) ToInterfaces(ipv4IsService, ipv6IsService bool) ([]ServerInterfaceInfo, error) { + var iface ServerInterfaceInfo + if lid.InterfaceMtu == nil { + return nil, errors.New("interfaceMtu is null") + } + mtu := uint64(*lid.InterfaceMtu) + iface.MTU = &mtu + + if lid.InterfaceName == nil { + return nil, errors.New("interfaceName is null") + } + iface.Name = *lid.InterfaceName + + var ips []ServerIPAddress + if lid.IPAddress != nil && *lid.IPAddress != "" { + if lid.IPGateway != nil && *lid.IPGateway == "" { + lid.IPGateway = nil + } + + ipStr := *lid.IPAddress + if lid.IPNetmask != nil && *lid.IPNetmask != "" { + mask := net.ParseIP(*lid.IPNetmask).To4() + if mask == nil { + return nil, fmt.Errorf("Failed to parse netmask '%s'", *lid.IPNetmask) + } + cidr, _ := net.IPv4Mask(mask[0], mask[1], mask[2], mask[3]).Size() + ipStr = fmt.Sprintf("%s/%d", ipStr, cidr) + } + + ips = append(ips, ServerIPAddress{ + Address: ipStr, + Gateway: lid.IPGateway, + ServiceAddress: ipv4IsService, + }) + } + + if lid.IP6Address != nil && *lid.IP6Address != "" { + if lid.IP6Gateway != nil && *lid.IP6Gateway == "" { + lid.IP6Gateway = nil + } + ips = append(ips, ServerIPAddress{ + Address: *lid.IP6Address, + Gateway: lid.IP6Gateway, + ServiceAddress: ipv6IsService, + }) + } + + iface.IPAddresses = ips + return []ServerInterfaceInfo{iface}, nil +} + // InterfaceInfoToLegacyInterfaces converts a ServerInterfaceInfo to an // equivalent LegacyInterfaceDetails structure. It does this by creating the // IP address fields using the "service" interface's IP addresses. All others @@ -123,13 +189,8 @@ func InterfaceInfoToLegacyInterfaces(serverInterfaces []ServerInterfaceInfo) (Le var legacyDetails LegacyInterfaceDetails for _, intFace := range serverInterfaces { - if intFace.MTU != nil { - legacyDetails.InterfaceMtu = util.IntPtr(int(*intFace.MTU)) - } - - legacyDetails.InterfaceName = &intFace.Name - for _, addr := range intFace.IpAddresses { + for _, addr := range intFace.IPAddresses { if !addr.ServiceAddress { continue } @@ -137,18 +198,40 @@ func InterfaceInfoToLegacyInterfaces(serverInterfaces []ServerInterfaceInfo) (Le address := addr.Address gateway := addr.Gateway - parsedIp, mask, err := net.ParseCIDR(address) + var parsedIp net.IP + var mask *net.IPNet + var err error + parsedIp, mask, err = net.ParseCIDR(address) if err != nil { - return legacyDetails, fmt.Errorf("Failed to parse '%s' as network or CIDR string: %v", address, err) + parsedIp = net.ParseIP(address) + if parsedIp == nil { + return legacyDetails, fmt.Errorf("Failed to parse '%s' as network or CIDR string: %v", address, err) + } } if parsedIp.To4() == nil { legacyDetails.IP6Address = &address legacyDetails.IP6Gateway = gateway - } else { + } else if mask != nil { legacyDetails.IPAddress = util.StrPtr(parsedIp.String()) legacyDetails.IPGateway = gateway legacyDetails.IPNetmask = util.StrPtr(fmt.Sprintf("%d.%d.%d.%d", mask.Mask[0], mask.Mask[1], mask.Mask[2], mask.Mask[3])) + } else { + legacyDetails.IPAddress = util.StrPtr(parsedIp.String()) + legacyDetails.IPGateway = gateway + legacyDetails.IPNetmask = new(string) + } + + if intFace.MTU != nil { + legacyDetails.InterfaceMtu = util.IntPtr(int(*intFace.MTU)) + } + + legacyDetails.InterfaceName = &intFace.Name + + // we can jump out here since servers can only legally have one + // IPv4 and one IPv6 service address + if legacyDetails.IPAddress != nil && *legacyDetails.IPAddress != "" && legacyDetails.IP6Address != nil && *legacyDetails.IP6Address != "" { + return legacyDetails, nil } } } @@ -256,7 +339,9 @@ type ServerV1 struct { XMPPPasswd string `json:"xmppPasswd" db:"xmpp_passwd"` } -type ServerNullableV11 struct { +// CommonServerProperties is just the collection of properties which are +// shared by all servers across API versions. +type CommonServerProperties struct { Cachegroup *string `json:"cachegroup" db:"cachegroup"` CachegroupID *int `json:"cachegroupId" db:"cachegroup_id"` CDNID *int `json:"cdnId" db:"cdn_id"` @@ -274,13 +359,6 @@ type ServerNullableV11 struct { ILOIPNetmask *string `json:"iloIpNetmask" db:"ilo_ip_netmask"` ILOPassword *string `json:"iloPassword" db:"ilo_password"` ILOUsername *string `json:"iloUsername" db:"ilo_username"` - InterfaceMtu *int `json:"interfaceMtu" db:"interface_mtu"` - InterfaceName *string `json:"interfaceName" db:"interface_name"` - IP6Address *string `json:"ip6Address" db:"ip6_address"` - IP6Gateway *string `json:"ip6Gateway" db:"ip6_gateway"` - IPAddress *string `json:"ipAddress" db:"ip_address"` - IPGateway *string `json:"ipGateway" db:"ip_gateway"` - IPNetmask *string `json:"ipNetmask" db:"ip_netmask"` LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"` MgmtIPAddress *string `json:"mgmtIpAddress" db:"mgmt_ip_address"` MgmtIPGateway *string `json:"mgmtIpGateway" db:"mgmt_ip_gateway"` @@ -305,12 +383,48 @@ type ServerNullableV11 struct { XMPPPasswd *string `json:"xmppPasswd" db:"xmpp_passwd"` } -type ServerNullable struct { +// ServerNullableV11 is a server as it appeared in API version 1.1. +type ServerNullableV11 struct { + LegacyInterfaceDetails + CommonServerProperties +} + +// ServerNullableV2 is a server as it appeared in API v2. +type ServerNullableV2 struct { ServerNullableV11 IPIsService *bool `json:"ipIsService" db:"ip_address_is_service"` IP6IsService *bool `json:"ip6IsService" db:"ip6_address_is_service"` } +// ServerNullable represents an ATC server, as returned by the TO API. +type ServerNullable struct { + CommonServerProperties + Interfaces []ServerInterfaceInfo `json:"interfaces" db:"interfaces"` +} + +// ToServerV2 converts the server to an equivalent ServerNullableV2 structure, +// if possible. If the conversion could not be performed, an error is returned. +func (s *ServerNullable) ToServerV2() (ServerNullableV2, error) { + legacyServer := ServerNullableV2{ + ServerNullableV11: ServerNullableV11{ + CommonServerProperties: s.CommonServerProperties, + }, + IPIsService: new(bool), + IP6IsService: new(bool), + } + + var err error + legacyServer.LegacyInterfaceDetails, err = InterfaceInfoToLegacyInterfaces(s.Interfaces) + if err != nil { + return legacyServer, err + } + + *legacyServer.IPIsService = legacyServer.LegacyInterfaceDetails.IPAddress != nil && *legacyServer.LegacyInterfaceDetails.IPAddress != "" + *legacyServer.IP6IsService = legacyServer.LegacyInterfaceDetails.IP6Address != nil && *legacyServer.LegacyInterfaceDetails.IP6Address != "" + + return legacyServer, nil +} + type ServerUpdateStatus struct { HostName string `json:"host_name"` UpdatePending bool `json:"upd_pending"` diff --git a/lib/go-tc/servers_test.go b/lib/go-tc/servers_test.go new file mode 100644 index 0000000000..443c585a32 --- /dev/null +++ b/lib/go-tc/servers_test.go @@ -0,0 +1,58 @@ +package tc + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import "fmt" + +func ExampleLegacyInterfaceDetails_ToInterfaces() { + lid := LegacyInterfaceDetails{ + InterfaceMtu: new(int), + InterfaceName: new(string), + IP6Address: new(string), + IP6Gateway: new(string), + IPAddress: new(string), + IPGateway: new(string), + IPNetmask: new(string), + } + *lid.InterfaceMtu = 9000 + *lid.InterfaceName = "test" + *lid.IP6Address = "::14/64" + *lid.IP6Gateway = "::15" + *lid.IPAddress = "1.2.3.4" + *lid.IPGateway = "4.3.2.1" + *lid.IPNetmask = "255.255.255.252" + + ifaces, err := lid.ToInterfaces(true, false) + if err != nil { + fmt.Printf(err.Error()) + return + } + + for _, iface := range ifaces { + fmt.Printf("name=%s, monitor=%t\n", iface.Name, iface.Monitor) + for _, ip := range iface.IPAddresses { + fmt.Printf("\taddr=%s, gateway=%s, service address=%t\n", ip.Address, *ip.Gateway, ip.ServiceAddress) + } + } + // Output: name=test, monitor=false + // addr=1.2.3.4/30, gateway=4.3.2.1, service address=true + // addr=::14/64, gateway=::15, service address=false + // +} diff --git a/traffic_ops/client/capability.go b/traffic_ops/client/capability.go index cb544a47f6..43bae247fe 100644 --- a/traffic_ops/client/capability.go +++ b/traffic_ops/client/capability.go @@ -39,7 +39,7 @@ func (to *Session) GetCapabilities() ([]tc.Capability, ReqInf, error) { // GetCapability retrieves only the capability named 'c'. func (to *Session) GetCapability(c string) (tc.Capability, ReqInf, error) { - var v url.Values + v := url.Values{} v.Add("name", c) endpoint := API_CAPABILITIES + "?" + v.Encode() resp, remoteAddr, err := to.request(http.MethodGet, endpoint, nil) diff --git a/traffic_ops/client/server.go b/traffic_ops/client/server.go index 9f688ecbf1..6c60c3d33f 100644 --- a/traffic_ops/client/server.go +++ b/traffic_ops/client/server.go @@ -34,158 +34,142 @@ const ( API_SERVER_ASSIGN_DELIVERY_SERVICES = API_SERVER_DELIVERY_SERVICES + "?replace=%t" ) +func needAndCanFetch(id *int, name *string) bool { + return (id == nil || *id == 0) && name != nil && *name != "" +} + // CreateServer creates a Server. -func (to *Session) CreateServer(server tc.Server) (tc.Alerts, ReqInf, error) { +func (to *Session) CreateServer(server tc.ServerNullable) (tc.Alerts, ReqInf, error) { + var alerts tc.Alerts var remoteAddr net.Addr reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} - if server.CachegroupID == 0 && server.Cachegroup != "" { - cg, _, err := to.GetCacheGroupNullableByName(server.Cachegroup) + if needAndCanFetch(server.CachegroupID, server.Cachegroup) { + cg, _, err := to.GetCacheGroupNullableByName(*server.Cachegroup) if err != nil { - return tc.Alerts{}, ReqInf{}, errors.New("no cachegroup named " + server.Cachegroup + ":" + err.Error()) + return alerts, reqInf, fmt.Errorf("no cachegroup named %s: %v", *server.Cachegroup, err) } if len(cg) == 0 { - return tc.Alerts{}, ReqInf{}, errors.New("no cachegroup named " + server.Cachegroup) + return alerts, reqInf, fmt.Errorf("no cachegroup named %s", *server.Cachegroup) } if cg[0].ID == nil { - return tc.Alerts{}, ReqInf{}, errors.New("Cachegroup named " + server.Cachegroup + " has a nil ID") + return alerts, reqInf, fmt.Errorf("Cachegroup named %s has a nil ID", *server.Cachegroup) } - server.CachegroupID = *cg[0].ID + server.CachegroupID = cg[0].ID } - if server.CDNID == 0 && server.CDNName != "" { - c, _, err := to.GetCDNByName(server.CDNName) + if needAndCanFetch(server.CDNID, server.CDNName) { + c, _, err := to.GetCDNByName(*server.CDNName) if err != nil { - return tc.Alerts{}, ReqInf{}, errors.New("no CDN named " + server.CDNName + ":" + err.Error()) + return alerts, reqInf, fmt.Errorf("no CDN named %s: %v", *server.CDNName, err) } if len(c) == 0 { - return tc.Alerts{}, ReqInf{}, errors.New("no CDN named " + server.CDNName) + return alerts, reqInf, fmt.Errorf("no CDN named %s", *server.CDNName) } - server.CDNID = c[0].ID + server.CDNID = &c[0].ID } - if server.PhysLocationID == 0 && server.PhysLocation != "" { - ph, _, err := to.GetPhysLocationByName(server.PhysLocation) + if needAndCanFetch(server.PhysLocationID, server.PhysLocation) { + ph, _, err := to.GetPhysLocationByName(*server.PhysLocation) if err != nil { - return tc.Alerts{}, ReqInf{}, errors.New("no physlocation named " + server.PhysLocation + ":" + err.Error()) + return alerts, reqInf, fmt.Errorf("no physlocation named %s: %v", *server.PhysLocation, err) } if len(ph) == 0 { - return tc.Alerts{}, ReqInf{}, errors.New("no physlocation named " + server.PhysLocation) + return alerts, reqInf, fmt.Errorf("no physlocation named %s", *server.PhysLocation) } - server.PhysLocationID = ph[0].ID + server.PhysLocationID = &ph[0].ID } - if server.ProfileID == 0 && server.Profile != "" { - pr, _, err := to.GetProfileByName(server.Profile) + if needAndCanFetch(server.ProfileID, server.Profile) { + pr, _, err := to.GetProfileByName(*server.Profile) if err != nil { - return tc.Alerts{}, ReqInf{}, errors.New("no profile named " + server.Profile + ":" + err.Error()) + return alerts, reqInf, fmt.Errorf("no profile named %s: %v", *server.Profile, err) } if len(pr) == 0 { - return tc.Alerts{}, ReqInf{}, errors.New("no profile named " + server.Profile) + return alerts, reqInf, fmt.Errorf("no profile named %s", *server.Profile) } - server.ProfileID = pr[0].ID + server.ProfileID = &pr[0].ID } - if server.StatusID == 0 && server.Status != "" { - st, _, err := to.GetStatusByName(server.Status) + if needAndCanFetch(server.StatusID, server.Status) { + st, _, err := to.GetStatusByName(*server.Status) if err != nil { - return tc.Alerts{}, ReqInf{}, errors.New("no status named " + server.Status + ":" + err.Error()) + return alerts, reqInf, fmt.Errorf("no status named %s: %v", *server.Status, err) } if len(st) == 0 { - return tc.Alerts{}, ReqInf{}, errors.New("no status named " + server.Status) + return alerts, reqInf, fmt.Errorf("no status named %s", *server.Status) } - server.StatusID = st[0].ID + server.StatusID = &st[0].ID } - if server.TypeID == 0 && server.Type != "" { + if (server.TypeID == nil || *server.TypeID == 0) && server.Type != "" { ty, _, err := to.GetTypeByName(server.Type) if err != nil { - return tc.Alerts{}, ReqInf{}, errors.New("no type named " + server.Type + ":" + err.Error()) + return alerts, reqInf, fmt.Errorf("no type named %s: %v", server.Type, err) } if len(ty) == 0 { - return tc.Alerts{}, ReqInf{}, errors.New("no type named " + server.Type) + return alerts, reqInf, fmt.Errorf("no type named %s", server.Type) } - server.TypeID = ty[0].ID + server.TypeID = &ty[0].ID } + reqBody, err := json.Marshal(server) if err != nil { - return tc.Alerts{}, reqInf, err + return alerts, reqInf, err } resp, remoteAddr, err := to.request(http.MethodPost, API_SERVERS, reqBody) + reqInf.RemoteAddr = remoteAddr if err != nil { - return tc.Alerts{}, reqInf, err + return alerts, reqInf, err } defer resp.Body.Close() - var alerts tc.Alerts + err = json.NewDecoder(resp.Body).Decode(&alerts) - return alerts, reqInf, nil + return alerts, reqInf, err } // UpdateServerByID updates a Server by ID. -func (to *Session) UpdateServerByID(id int, server tc.Server) (tc.Alerts, ReqInf, error) { - +func (to *Session) UpdateServerByID(id int, server tc.ServerNullable) (tc.Alerts, ReqInf, error) { + var alerts tc.Alerts var remoteAddr net.Addr - reqBody, err := json.Marshal(server) reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + + reqBody, err := json.Marshal(server) if err != nil { - return tc.Alerts{}, reqInf, err + return alerts, reqInf, err } + route := fmt.Sprintf("%s/%d", API_SERVERS, id) resp, remoteAddr, err := to.request(http.MethodPut, route, reqBody) + reqInf.RemoteAddr = remoteAddr if err != nil { - return tc.Alerts{}, reqInf, err + return alerts, reqInf, err } defer resp.Body.Close() - var alerts tc.Alerts + err = json.NewDecoder(resp.Body).Decode(&alerts) - return alerts, reqInf, nil + return alerts, reqInf, err } // GetServers returns a list of Servers. -func (to *Session) GetServers() ([]tc.Server, ReqInf, error) { - resp, remoteAddr, err := to.request(http.MethodGet, API_SERVERS, nil) - reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} - if err != nil { - return nil, reqInf, err +// The 'params' parameter can be used to optionally pass URL "query string +// parameters" in the request. +// It returns, in order, the API response that Traffic Ops returned, a request +// info object, and any error that occurred. +func (to *Session) GetServers(params *url.Values) (tc.ServersV3Response, ReqInf, error) { + route := API_SERVERS + if params != nil { + route += "?" + params.Encode() } - defer resp.Body.Close() - var data tc.ServersResponse - err = json.NewDecoder(resp.Body).Decode(&data) - return data.Response, reqInf, nil -} + var data tc.ServersV3Response -// GetServerByID GETs a Server by the Server ID. -func (to *Session) GetServerByID(id int) ([]tc.Server, ReqInf, error) { - route := fmt.Sprintf("%s?id=%d", API_SERVERS, id) resp, remoteAddr, err := to.request(http.MethodGet, route, nil) reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} if err != nil { - return nil, reqInf, err - } - defer resp.Body.Close() - - var data tc.ServersResponse - if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { - return nil, reqInf, err - } - - return data.Response, reqInf, nil -} - -// GetServerByHostName GETs Servers by the Server hostname. -func (to *Session) GetServerByHostName(hostName string) ([]tc.Server, ReqInf, error) { - url := fmt.Sprintf("%s?hostName=%s", API_SERVERS, hostName) - resp, remoteAddr, err := to.request(http.MethodGet, url, nil) - reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} - if err != nil { - return nil, reqInf, err + return data, reqInf, err } defer resp.Body.Close() - var data tc.ServersResponse - if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { - return nil, reqInf, err - } - - return data.Response, reqInf, nil + err = json.NewDecoder(resp.Body).Decode(&data) + return data, reqInf, err } // GetServerDetailsByHostName GETs Servers by the Server hostname. @@ -223,70 +207,58 @@ func (to *Session) DeleteServerByID(id int) (tc.Alerts, ReqInf, error) { return alerts, reqInf, nil } -// GetServersByType returns all servers that match the given query parameter filters, NOT -// - as the name might imply - all servers of a specific type (though type can be specified -// in the query parameters). -func (to *Session) GetServersByType(qparams url.Values) ([]tc.Server, ReqInf, error) { - url := fmt.Sprintf("%s?%s", API_SERVERS, qparams.Encode()) - resp, remoteAddr, err := to.request(http.MethodGet, url, nil) - reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} - if err != nil { - return nil, reqInf, err - } - defer resp.Body.Close() - - var data tc.ServersResponse - if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { - return nil, reqInf, err - } - - return data.Response, reqInf, nil -} - -// GetServerFQDN returns the Fully Qualified Domain Name (FQDN) of the first server found to -// have the Host Name 'n'. -func (to *Session) GetServerFQDN(n string) (string, ReqInf, error) { +// GetServerFQDN returns the Fully Qualified Domain Name (FQDN) of the first +// server found to have the Host Name 'n'. +func (to *Session) GetServerFQDN(n string) (string, tc.Alerts, ReqInf, error) { // TODO fix to only request one server - fdn := "" - servers, reqInf, err := to.GetServers() + params := url.Values{} + params.Add("hostName", n) + + resp, reqInf, err := to.GetServers(¶ms) if err != nil { - return "Error", reqInf, err + return "", resp.Alerts, reqInf, err } - for _, server := range servers { - if server.HostName == n { - fdn = fmt.Sprintf("%s.%s", server.HostName, server.DomainName) + var fdn string + for _, server := range resp.Response { + if server.HostName != nil && server.DomainName != nil { + fdn = fmt.Sprintf("%s.%s", *server.HostName, *server.DomainName) } } + if fdn == "" { - return "Error", reqInf, fmt.Errorf("No Server %s found", n) + err = fmt.Errorf("No Server %s found", n) } - return fdn, reqInf, nil + + return fdn, resp.Alerts, reqInf, err } -// GetServersShortNameSearch returns all of the Host Names of servers that contain 'shortname'. -func (to *Session) GetServersShortNameSearch(shortname string) ([]string, ReqInf, error) { +// GetServersShortNameSearch returns all of the Host Names of servers that +// contain 'shortname'. +func (to *Session) GetServersShortNameSearch(shortname string) ([]string, tc.Alerts, ReqInf, error) { var serverlst []string - servers, reqInf, err := to.GetServers() + resp, reqInf, err := to.GetServers(nil) if err != nil { - serverlst = append(serverlst, "N/A") - return serverlst, reqInf, err + return serverlst, resp.Alerts, reqInf, err } - for _, server := range servers { - if strings.Contains(server.HostName, shortname) { - serverlst = append(serverlst, server.HostName) + + for _, server := range resp.Response { + if server.HostName != nil && strings.Contains(*server.HostName, shortname) { + serverlst = append(serverlst, *server.HostName) } } + if len(serverlst) == 0 { - serverlst = append(serverlst, "N/A") - return serverlst, reqInf, fmt.Errorf("No Servers Found") + err = errors.New("No Servers Found") } - return serverlst, reqInf, nil + + return serverlst, resp.Alerts, reqInf, err } -// AssignDeliveryServiceIDsToServerID assigns a set of Delivery Services to a single server, optionally -// replacing any and all existing assignments. 'server' should be the requested server's ID, 'dsIDs' -// should be a slice of the requested Delivery Services' IDs. If 'replace' is 'true', existing +// AssignDeliveryServiceIDsToServerID assigns a set of Delivery Services to a +// single server, optionally replacing any and all existing assignments. +// 'server' should be the requested server's ID, 'dsIDs' should be a slice of +// the requested Delivery Services' IDs. If 'replace' is 'true', existing // assignments to the server will be replaced. func (to *Session) AssignDeliveryServiceIDsToServerID(server int, dsIDs []int, replace bool) (tc.Alerts, ReqInf, error) { // datatypes here match the library tc.Server's and tc.DeliveryService's ID fields diff --git a/traffic_ops/ort/atstccfg/toreqnew/toreqnew.go b/traffic_ops/ort/atstccfg/toreqnew/toreqnew.go index 9d8f3a8ed7..56bf454634 100644 --- a/traffic_ops/ort/atstccfg/toreqnew/toreqnew.go +++ b/traffic_ops/ort/atstccfg/toreqnew/toreqnew.go @@ -39,8 +39,8 @@ import ( "github.com/apache/trafficcontrol/lib/go-log" "github.com/apache/trafficcontrol/lib/go-tc" - toclient "github.com/apache/trafficcontrol/traffic_ops/v2-client" "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/torequtil" + toclient "github.com/apache/trafficcontrol/traffic_ops/v2-client" ) type TOClient struct { diff --git a/traffic_ops/testing/api/v1/atsconfig_test.go b/traffic_ops/testing/api/v1/atsconfig_test.go index 8abea7a60a..98b96e91bd 100644 --- a/traffic_ops/testing/api/v1/atsconfig_test.go +++ b/traffic_ops/testing/api/v1/atsconfig_test.go @@ -53,12 +53,9 @@ func GetTestATSConfigs(t *testing.T) { } serverConfigs := []string{ - "ip_allow.config", "hosting.config", "packages", "chkconfig", - "remap.config", - "parent.config", } for _, serverConfig := range serverConfigs { if _, _, err = TOSession.GetATSServerConfig(server.ID, serverConfig); err != nil { @@ -108,3 +105,81 @@ func GetTestATSConfigs(t *testing.T) { } } } + +func CreateTestDeliveryServiceServers(t *testing.T) { + dses, _, err := TOSession.GetDeliveryServices() + if err != nil { + t.Errorf("cannot GET DeliveryServices: %v", err) + } + if len(dses) < 1 { + t.Error("GET DeliveryServices returned no dses, must have at least 1 to test ds-servers") + } + + servers, _, err := TOSession.GetServers() + if err != nil { + t.Errorf("cannot GET Servers: %v", err) + } + if len(servers) < 1 { + t.Error("GET Servers returned no servers, must have at least 1 to test ds-servers") + } + + for _, ds := range dses { + serverIDs := make([]int, 0, len(servers)) + for _, server := range servers { + if server.Type == "EDGE" && server.CDNName == ds.CDNName { + serverIDs = append(serverIDs, server.ID) + } + } + + if len(serverIDs) > 0 { + _, err = TOSession.CreateDeliveryServiceServers(ds.ID, serverIDs, true) + if err != nil { + t.Errorf("POST delivery service servers: %v", err) + } + } + } +} + +// DeleteTestDeliveryServiceServersCreated deletes the dss assignments created by CreateTestDeliveryServiceServers. +func DeleteTestDeliveryServiceServersCreated(t *testing.T) { + // You gotta do this because TOSession.GetDeliveryServiceServers doesn't fetch the complete response....... + dssLen := len(testData.Servers) * len(testData.DeliveryServices) + dsServers, _, err := TOSession.GetDeliveryServiceServersN(dssLen) + if err != nil { + t.Fatalf("GET delivery service servers: %v", err) + } + + for _, dss := range dsServers.Response { + if dss.DeliveryService == nil { + t.Error("Found ds-to-server assignment with nil Delivery Service") + continue + } + if dss.Server == nil { + t.Error("Found ds-to-server assignment with nil Server") + continue + } + + _, _, err := TOSession.DeleteDeliveryServiceServer(*dss.DeliveryService, *dss.Server) + if err != nil { + t.Errorf("Failed to remove assignment of server #%d to DS #%d: %v", *dss.Server, *dss.DeliveryService, err) + } + } + + dsServers, _, err = TOSession.GetDeliveryServiceServersN(dssLen) + if err != nil { + t.Fatalf("GET delivery service servers: %v", err) + } + + for _, dss := range dsServers.Response { + if dss.DeliveryService == nil { + t.Error("Found ds-to-server assignment (after supposed deletion) with nil DeliveryService") + continue + } + if dss.Server == nil { + t.Error("Found ds-to-server assignment (after supposed deletion) with nil Server") + continue + } + + t.Errorf("Found ds-to-server assignment {DSID: %d, Server: %d} after deletion", *dss.DeliveryService, *dss.Server) + } +} diff --git a/traffic_ops/testing/api/v1/ip_allow_dot_config_test.go b/traffic_ops/testing/api/v1/ip_allow_dot_config_test.go deleted file mode 100644 index 61961a37b0..0000000000 --- a/traffic_ops/testing/api/v1/ip_allow_dot_config_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package v1 - -/* - 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. -*/ - -import ( - "fmt" - "net/url" - "strings" - "testing" - - "github.com/apache/trafficcontrol/lib/go-util" - - "github.com/apache/trafficcontrol/lib/go-tc" -) - -const ipAllow = "ip_allow.config" - -var ( - expectedRules = []string{ - "src_ip=127.0.0.1 action=ip_allow method=ALL\n", - "src_ip=::1 action=ip_allow method=ALL\n", - } - midExpectedRules = []string{ - "src_ip=10.0.0.0-10.255.255.255 action=ip_allow method=ALL\n", - "src_ip=172.16.0.0-172.31.255.255 action=ip_allow method=ALL\n", - "src_ip=192.168.0.0-192.168.255.255 action=ip_allow method=ALL\n", - "src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny method=ALL\n", - "src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny method=ALL\n", - } - edgeExpectedRules = []string{ - "src_ip=0.0.0.0-255.255.255.255 action=ip_deny method=PUSH|PURGE|DELETE\n", - "src_ip=::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff action=ip_deny method=PUSH|PURGE|DELETE\n", - } - rascalServerIP = "" - rascalRule = "src_ip=%v action=ip_allow method=ALL" -) - -func TestIPAllowDotConfig(t *testing.T) { - WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() { - rascalServerIP = getServer(t, "RASCAL").IPAddress - // rascalRule = fmt.Sprintf("src_ip=%v action=ip_allow method=ALL", rascalServer.IPAddress) - GetTestIPAllowDotConfig(t) - GetTestIPAllowMidDotConfig(t) - }) -} - -func GetTestIPAllowDotConfig(t *testing.T) { - // Get edge server - s := getServer(t, "EDGE") - output, _, err := TOSession.GetATSServerConfig(s.ID, ipAllow) - if err != nil { - t.Fatalf("cannot GET server %v config %v: %v", s.HostName, ipAllow, err) - } - for _, r := range append(expectedRules, edgeExpectedRules...) { - if !strings.Contains(output, r) { - t.Errorf("expected rule %v not found in ip_allow config", r) - } - } - // Make sure edge does not contain rule for rascal server - exists, ipRange := getIPRule(output, rascalServerIP) - rascalRule := fmt.Sprintf(rascalRule, ipRange) - if exists && strings.Contains(output, rascalRule) { - t.Errorf("rascal IP was not supposed to be in an allowed rule: %v", rascalRule) - } -} - -func GetTestIPAllowMidDotConfig(t *testing.T) { - // Get mid server - s := getServer(t, "MID") - output, _, err := TOSession.GetATSServerConfig(s.ID, ipAllow) - if err != nil { - t.Errorf("cannot GET server %v config %v: %v", s.HostName, ipAllow, err) - } - for _, r := range append(expectedRules, midExpectedRules...) { - if !strings.Contains(output, r) { - t.Errorf("expected rule %v not found in ip_allow config", r) - } - } - - // Make sure mid contains an allowed rule that includes the rascal server - exists, ipRange := getIPRule(output, rascalServerIP) - rascalRule := fmt.Sprintf(rascalRule, ipRange) - if !(exists && strings.Contains(output, rascalRule)) { - t.Errorf("expected rascal to be include as allowed in mid ip allow config") - } -} - -func getServer(t *testing.T, serverType string) tc.ServerV1 { - v := url.Values{} - v.Add("type", serverType) - servers, _, err := TOSession.GetServersByType(v) - if err != nil { - t.Fatalf("cannot GET Server by type %v: %v", serverType, err) - } - if len(servers) == 0 { - t.Fatalf("cannot find any Servers by type %v", serverType) - } - return servers[0] -} - -// getIPRuleRange returns if the given IP is included in the set of rules and which ip range it is included in -func getIPRule(rules, ip string) (bool, string) { - for _, r := range strings.Split(rules, "\n")[1:] { - if !strings.Contains(r, "src_ip") { - continue - } - ipRange := r[7:strings.IndexAny(r, " ")] - if exists, _ := util.IP4InRange(ip, ipRange); exists { - return true, ipRange - } - } - return false, "" -} diff --git a/traffic_ops/testing/api/v1/parentdotconfig_test.go b/traffic_ops/testing/api/v1/parentdotconfig_test.go deleted file mode 100644 index b39a740362..0000000000 --- a/traffic_ops/testing/api/v1/parentdotconfig_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package v1 - -/* - - 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. -*/ - -import ( - "net/url" - "strconv" - "strings" - "testing" -) - -func TestParentDotConfig(t *testing.T) { - WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() { - defer DeleteTestDeliveryServiceServersCreated(t) - CreateTestDeliveryServiceServers(t) - GetTestParentDotConfig(t) - }) -} - -func GetTestParentDotConfig(t *testing.T) { - dsServers, _, err := TOSession.GetDeliveryServiceServers() - if err != nil { - t.Fatalf("GET delivery service servers: %v", err) - } - if len(dsServers.Response) == 0 { - t.Fatal("GET delivery service servers: no servers found") - } - - dss := dsServers.Response[0] - - if dss.Server == nil { - t.Fatal("GET delivery service servers: returned nil server") - } - if dss.DeliveryService == nil { - t.Fatal("GET delivery service servers: returned nil ds") - } - - ds, _, err := TOSession.GetDeliveryService(strconv.Itoa(*dss.DeliveryService)) - if err != nil { - t.Fatalf("Getting ds %+v: %v", *dss.DeliveryService, err) - } - if ds == nil { - t.Fatalf("Getting ds %+v: got nil response", *dss.DeliveryService) - } - if ds.OrgServerFQDN == "" { - t.Fatalf("Getting ds %+v: got empty ds.OrgServerFQDN", *dss.DeliveryService) - } - - originURI, err := url.Parse(ds.OrgServerFQDN) - if err != nil { - t.Fatalf("Getting ds %+v: ds.OrgServerFQDN '%v' failed to parse as a URL: %v", *dss.DeliveryService, ds.OrgServerFQDN, err) - } - originHost := originURI.Hostname() - - parentDotConfig, _, err := TOSession.GetATSServerConfig(*dss.Server, "parent.config") - if err != nil { - t.Fatalf("Getting server %v config parent.config: %v", *dss.Server, err) - } - - if !strings.Contains(parentDotConfig, originHost) { - t.Errorf("expected: parent.config to contain delivery service origin FQDN '%v' host '%v', actual:\n'''\n%+v\n'''", ds.OrgServerFQDN, originHost, parentDotConfig) - } -} - -func CreateTestDeliveryServiceServers(t *testing.T) { - dses, _, err := TOSession.GetDeliveryServices() - if err != nil { - t.Errorf("cannot GET DeliveryServices: %v", err) - } - if len(dses) < 1 { - t.Error("GET DeliveryServices returned no dses, must have at least 1 to test ds-servers") - } - - servers, _, err := TOSession.GetServers() - if err != nil { - t.Errorf("cannot GET Servers: %v", err) - } - if len(servers) < 1 { - t.Error("GET Servers returned no servers, must have at least 1 to test ds-servers") - } - - for _, ds := range dses { - serverIDs := make([]int, 0, len(servers)) - for _, server := range servers { - if server.Type == "EDGE" && server.CDNName == ds.CDNName { - serverIDs = append(serverIDs, server.ID) - } - } - - if len(serverIDs) > 0 { - _, err = TOSession.CreateDeliveryServiceServers(ds.ID, serverIDs, true) - if err != nil { - t.Errorf("POST delivery service servers: %v", err) - } - } - } -} - -// DeleteTestDeliveryServiceServersCreated deletes the dss assignments created by CreateTestDeliveryServiceServers. -func DeleteTestDeliveryServiceServersCreated(t *testing.T) { - // You gotta do this because TOSession.GetDeliveryServiceServers doesn't fetch the complete response....... - dssLen := len(testData.Servers) * len(testData.DeliveryServices) - dsServers, _, err := TOSession.GetDeliveryServiceServersN(dssLen) - if err != nil { - t.Fatalf("GET delivery service servers: %v", err) - } - - for _, dss := range dsServers.Response { - if dss.DeliveryService == nil { - t.Error("Found ds-to-server assignment with nil Delivery Service") - continue - } - if dss.Server == nil { - t.Error("Found ds-to-server assignment with nil Server") - continue - } - - _, _, err := TOSession.DeleteDeliveryServiceServer(*dss.DeliveryService, *dss.Server) - if err != nil { - t.Errorf("Failed to remove assignment of server #%d to DS #%d: %v", *dss.Server, *dss.DeliveryService, err) - } - } - - dsServers, _, err = TOSession.GetDeliveryServiceServersN(dssLen) - if err != nil { - t.Fatalf("GET delivery service servers: %v", err) - } - - for _, dss := range dsServers.Response { - if dss.DeliveryService == nil { - t.Error("Found ds-to-server assignment (after supposed deletion) with nil DeliveryService") - continue - } - if dss.Server == nil { - t.Error("Found ds-to-server assignment (after supposed deletion) with nil Server") - continue - } - - t.Errorf("Found ds-to-server assignment {DSID: %d, Server: %d} after deletion", *dss.DeliveryService, *dss.Server) - } -} diff --git a/traffic_ops/testing/api/v1/remapdotconfig_test.go b/traffic_ops/testing/api/v1/remapdotconfig_test.go deleted file mode 100644 index 4e7e229c62..0000000000 --- a/traffic_ops/testing/api/v1/remapdotconfig_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package v1 - -/* - - 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. -*/ - -import ( - "net/url" - "strconv" - "strings" - "testing" - - "github.com/apache/trafficcontrol/lib/go-tc" -) - -func TestRemapDotConfig(t *testing.T) { - WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() { - defer DeleteTestDeliveryServiceServersCreated(t) - CreateTestDeliveryServiceServers(t) - GetTestRemapDotConfig(t) - }) -} - -func GetTestRemapDotConfig(t *testing.T) { - dsServers, _, err := TOSession.GetDeliveryServiceServers() - if err != nil { - t.Fatalf("GET delivery service servers: %v", err) - } - if len(dsServers.Response) == 0 { - t.Fatal("GET delivery service servers: no servers found") - } - - var ds *tc.DeliveryService - var serverID int - for _, dsServer := range dsServers.Response { - if dsServer.Server == nil { - t.Error("Found DS-Server assignment with nil server") - continue - } - if dsServer.DeliveryService == nil { - t.Error("Found DS-Server assignment with nil Delivery Service") - continue - } - - serverID = *dsServer.Server - - ds, _, err = TOSession.GetDeliveryService(strconv.Itoa(*dsServer.DeliveryService)) - if err != nil { - t.Errorf("Getting ds %+v: %v", *dsServer.DeliveryService, err) - continue - } - if ds == nil { - t.Errorf("Getting ds %+v: got nil response", *dsServer.DeliveryService) - continue - } - if ds.OrgServerFQDN == "" { - t.Errorf("Getting ds %+v: got empty ds.OrgServerFQDN", *dsServer.DeliveryService) - continue - } - - if ds.Type != tc.DSTypeAnyMap { - break - } - } - - if ds == nil || ds.XMLID == "" { - t.Fatal("no Delivery Service found with assigned servers that isn't an ANY_MAP service, can't test remap.config") - } - - originURI, err := url.Parse(ds.OrgServerFQDN) - if err != nil { - t.Fatalf("Getting ds %+v: ds.OrgServerFQDN '%+v' failed to parse as a URL: %+v", ds.XMLID, ds.OrgServerFQDN, err) - } - originHost := originURI.Hostname() - - remapDotConfig, _, err := TOSession.GetATSServerConfig(serverID, "remap.config") - if err != nil { - t.Fatalf("Getting server %+v config remap.config: %v", serverID, err) - } - - if !strings.Contains(remapDotConfig, originHost) { - t.Errorf("expected: remap.config to contain delivery service origin FQDN '%v' host '%v', actual:\n'''\n%v\n'''", ds.OrgServerFQDN, originHost, remapDotConfig) - } - - remapDotConfigLines := strings.Split(remapDotConfig, "\n") - for i, line := range remapDotConfigLines { - line = strings.TrimSpace(line) - if len(line) > 0 && line[0] != '#' && !strings.HasPrefix(line, "map") { - t.Errorf("expected: remap.config line %v to start with 'map', actual: '%v'", i, line) - } - } -} diff --git a/traffic_ops/testing/api/v1/serverupdatestatus_test.go b/traffic_ops/testing/api/v1/serverupdatestatus_test.go index 3a2bced1ef..b326a5bd63 100644 --- a/traffic_ops/testing/api/v1/serverupdatestatus_test.go +++ b/traffic_ops/testing/api/v1/serverupdatestatus_test.go @@ -86,16 +86,16 @@ func TestServerUpdateStatus(t *testing.T) { // assert that updates were queued for the proper EDGE servers getServers() if !edge1cdn1.UpdPending { - t.Errorf("expected: child %s to have updates pending, actual: no updates pending", edge1cdn1.HostName) + t.Errorf("expected: child %s (%d) to have updates pending, actual: no updates pending", edge1cdn1.HostName, edge1cdn1.ID) } if !edge2cdn1.UpdPending { - t.Errorf("expected: child %s to have updates pending, actual: no updates pending", edge2cdn1.HostName) + t.Errorf("expected: child %s (%d) to have updates pending, actual: no updates pending", edge2cdn1.HostName, edge2cdn1.ID) } if mid1cdn1.UpdPending { - t.Errorf("expected: server %s with updated status to have no updates pending, actual: updates pending", mid1cdn1.HostName) + t.Errorf("expected: server %s (%d) with updated status to have no updates pending, actual: updates pending", mid1cdn1.HostName, mid1cdn1.ID) } if edge1cdn2.UpdPending { - t.Errorf("expected: server %s in different CDN than server with updated status to have no updates pending, actual: updates pending", edge2cdn1.HostName) + t.Errorf("expected: server %s (%d) in different CDN than server with updated status to have no updates pending, actual: updates pending", edge1cdn2.HostName, edge1cdn2.ID) } // update status of MID server to OFFLINE via status ID diff --git a/traffic_ops/testing/api/v1/todb_test.go b/traffic_ops/testing/api/v1/todb_test.go index e1b40e5fcc..19e9d3638e 100644 --- a/traffic_ops/testing/api/v1/todb_test.go +++ b/traffic_ops/testing/api/v1/todb_test.go @@ -306,7 +306,6 @@ func Teardown(db *sql.DB) error { DELETE FROM api_capability; DELETE FROM deliveryservices_required_capability; DELETE FROM server_server_capability; - DELETE FROM server_server_capability; DELETE FROM server_capability; DELETE FROM to_extension; DELETE FROM staticdnsentry; @@ -325,6 +324,8 @@ func Teardown(db *sql.DB) error { DELETE FROM deliveryservice_server; DELETE FROM deliveryservice; DELETE FROM origin; + DELETE FROM ip_address; + DELETE FROM interface; DELETE FROM server; DELETE FROM phys_location; DELETE FROM region; diff --git a/traffic_ops/testing/api/v2/todb_test.go b/traffic_ops/testing/api/v2/todb_test.go index 4c54d47d40..b8e7246385 100644 --- a/traffic_ops/testing/api/v2/todb_test.go +++ b/traffic_ops/testing/api/v2/todb_test.go @@ -306,7 +306,6 @@ func Teardown(db *sql.DB) error { DELETE FROM api_capability; DELETE FROM deliveryservices_required_capability; DELETE FROM server_server_capability; - DELETE FROM server_server_capability; DELETE FROM server_capability; DELETE FROM to_extension; DELETE FROM staticdnsentry; @@ -325,6 +324,8 @@ func Teardown(db *sql.DB) error { DELETE FROM deliveryservice_server; DELETE FROM deliveryservice; DELETE FROM origin; + DELETE FROM ip_address; + DELETE FROM interface; DELETE FROM server; DELETE FROM phys_location; DELETE FROM region; diff --git a/traffic_ops/testing/api/v3/cachegroups_test.go b/traffic_ops/testing/api/v3/cachegroups_test.go index 726ee3945f..7fe45f3e10 100644 --- a/traffic_ops/testing/api/v3/cachegroups_test.go +++ b/traffic_ops/testing/api/v3/cachegroups_test.go @@ -45,6 +45,7 @@ func CreateTestCacheGroups(t *testing.T) { resp, _, err = TOSession.CreateCacheGroupNullable(cg) if err != nil { t.Errorf("could not CREATE cachegroups: %v, request: %v", err, cg) + continue } // Testing 'join' fields during create diff --git a/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go b/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go index f245328d66..5af55dc6fc 100644 --- a/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go +++ b/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go @@ -17,6 +17,7 @@ package v3 import ( "net/http" + "net/url" "testing" ) @@ -103,16 +104,23 @@ func CreateTestCachegroupsDeliveryServices(t *testing.T) { t.Fatal("setting cachegroup delivery services returned success, but no servers set") } + params := url.Values{} for _, serverName := range resp.Response.ServerNames { - servers, _, err := TOSession.GetServerByHostName(string(serverName)) + params.Set("hostName", string(serverName)) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { t.Fatalf("getting server: %v", err) } + servers := resp.Response if len(servers) != 1 { t.Fatalf("getting servers: expected 1 got %v", len(servers)) } server := servers[0] - serverID := server.ID + if server.ID == nil { + t.Errorf("Server %s had nil ID", serverName) + continue + } + serverID := *server.ID serverDSes, _, err := TOSession.GetDeliveryServicesByServer(serverID) diff --git a/traffic_ops/testing/api/v3/crconfig_test.go b/traffic_ops/testing/api/v3/crconfig_test.go index d4e3309369..5a88c338da 100644 --- a/traffic_ops/testing/api/v3/crconfig_test.go +++ b/traffic_ops/testing/api/v3/crconfig_test.go @@ -51,14 +51,15 @@ func UpdateTestCRConfigSnapshot(t *testing.T) { } // create an ANY_MAP DS assignment to verify that it doesn't show up in the CRConfig - servers, _, err := TOSession.GetServers() + resp, _, err := TOSession.GetServers(nil) if err != nil { - t.Errorf("GetServers err expected nil, actual %+v", err) + t.Fatalf("GetServers err expected nil, actual %+v", err) } + servers := resp.Response serverID := 0 for _, server := range servers { - if server.Type == "EDGE" && server.CDNName == "cdn1" { - serverID = server.ID + if server.Type == "EDGE" && server.CDNName != nil && *server.CDNName == "cdn1" && server.ID != nil { + serverID = *server.ID break } } diff --git a/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go b/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go index 61f6fd4008..d478bf628d 100644 --- a/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go +++ b/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go @@ -17,6 +17,7 @@ package v3 import ( "fmt" + "net/url" "strings" "testing" @@ -181,16 +182,24 @@ func InvalidDeliveryServicesRequiredCapabilityAddition(t *testing.T) { } // First assign current capabilities to edge server so we can assign it to the DS - servers, _, err := TOSession.GetServerByHostName("atlanta-edge-01") + // TODO: DON'T hard-code hostnames! + params := url.Values{} + params.Add("hostName", "atlanta-edge-01") + resp, _, err := TOSession.GetServers(¶ms) if err != nil { t.Fatalf("cannot GET Server by hostname: %v", err) } + servers := resp.Response if len(servers) < 1 { t.Fatal("need at least one server to test invalid ds required capability assignment") } + if servers[0].ID == nil { + t.Fatal("server 'atlanta-edge-01' had nil ID") + } + dsID := capabilities[0].DeliveryServiceID - sID := servers[0].ID + sID := *servers[0].ID serverCaps := []tc.ServerServerCapability{} for _, cap := range capabilities { diff --git a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go index 9b0f444b01..a34ad88fe3 100644 --- a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go +++ b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go @@ -18,6 +18,7 @@ package v3 import ( "errors" "net/http" + "net/url" "strings" "testing" @@ -50,10 +51,10 @@ func AssignServersToTopologyBasedDeliveryService(t *testing.T) { if ds[0].Topology == nil { t.Fatal("expected delivery service: 'ds-top' to have a non-nil Topology, actual: nil") } - serversResp, _, err := TOSession.GetServers() - servers := []tc.Server{} - for _, s := range serversResp { - if s.CDNID == *ds[0].CDNID && s.Type == tc.CacheTypeEdge.String() { + serversResp, _, err := TOSession.GetServers(nil) + servers := []tc.ServerNullable{} + for _, s := range serversResp.Response { + if s.CDNID != nil && *s.CDNID == *ds[0].CDNID && s.Type == tc.CacheTypeEdge.String() { servers = append(servers, s) } } @@ -62,8 +63,8 @@ func AssignServersToTopologyBasedDeliveryService(t *testing.T) { } serverNames := []string{} for _, s := range servers { - if s.CDNID == *ds[0].CDNID && s.Type == tc.CacheTypeEdge.String() { - serverNames = append(serverNames, s.HostName) + if s.CDNID != nil && *s.CDNID == *ds[0].CDNID && s.Type == tc.CacheTypeEdge.String() && s.HostName != nil { + serverNames = append(serverNames, *s.HostName) } else { t.Fatalf("expected only EDGE servers in cdn '%s', actual: %v", *ds[0].CDNName, servers) } @@ -76,7 +77,7 @@ func AssignServersToTopologyBasedDeliveryService(t *testing.T) { t.Fatalf("assigning servers to topology-based delivery service - expected: 400-level status code, actual: %d", reqInf.StatusCode) } - _, reqInf, err = TOSession.CreateDeliveryServiceServers(*ds[0].ID, []int{servers[0].ID}, false) + _, reqInf, err = TOSession.CreateDeliveryServiceServers(*ds[0].ID, []int{*servers[0].ID}, false) if err == nil { t.Fatal("creating deliveryserviceserver assignment for topology-based delivery service - expected: error, actual: nil error") } @@ -120,24 +121,30 @@ func CreateTestDeliveryServiceServersWithRequiredCapabilities(t *testing.T) { for _, ctc := range testCases { t.Run(ctc.description, func(t *testing.T) { - servers, _, err := TOSession.GetServerByHostName(ctc.serverName) + params := url.Values{} + params.Add("hostName", ctc.serverName) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { t.Fatalf("cannot GET Server by hostname: %v", err) } + servers := resp.Response server := servers[0] + if server.ID == nil { + t.Fatalf("server %s had nil ID", ctc.serverName) + } _, _, err = TOSession.CreateDeliveryServicesRequiredCapability(ctc.capability) if err != nil { t.Fatalf("*POST delivery service required capability: %v", err) } - ctc.ssc.ServerID = &server.ID + ctc.ssc.ServerID = server.ID _, _, err = TOSession.CreateServerServerCapability(ctc.ssc) if err != nil { t.Fatalf("could not POST the server capability %v to server %v: %v", *ctc.ssc.ServerCapability, *ctc.ssc.Server, err) } - _, _, got := TOSession.CreateDeliveryServiceServers(*ctc.capability.DeliveryServiceID, []int{server.ID}, true) + _, _, got := TOSession.CreateDeliveryServiceServers(*ctc.capability.DeliveryServiceID, []int{*server.ID}, true) if (ctc.err == nil && got != nil) || (ctc.err != nil && !strings.Contains(got.Error(), ctc.err.Error())) { t.Fatalf("expected ctc.err to contain %v, got %v", ctc.err, got) } @@ -161,19 +168,25 @@ func CreateTestMSODSServerWithReqCap(t *testing.T) { } // Associate origin server to msods1 even though it does not have req cap - - servers, _, err := TOSession.GetServerByHostName("denver-mso-org-01") + // TODO: DON'T hard-code server hostnames! + params := url.Values{} + params.Add("hostName", "denver-mso-org-01") + resp, _, err := TOSession.GetServers(¶ms) if err != nil { t.Fatalf("GET server denver-mso-org-01: %v", err) } + servers := resp.Response if len(servers) != 1 { t.Fatal("expected 1 server with hostname denver-mso-org-01") } s := servers[0] + if s.ID == nil { + t.Fatal("server 'denver-mso-org-01' had nil ID") + } // Make sure server has no caps to ensure test correctness - sccs, _, err := TOSession.GetServerServerCapabilities(&s.ID, nil, nil) + sccs, _, err := TOSession.GetServerServerCapabilities(s.ID, nil, nil) if err != nil { t.Fatalf("GET server server capabilities for denver-mso-org-01: %v", err) } @@ -197,7 +210,7 @@ func CreateTestMSODSServerWithReqCap(t *testing.T) { t.Fatal("expected to find origin server denver-mso-org-01 to be in eligible server return even though it is missing a required capability") } - if _, _, err = TOSession.CreateDeliveryServiceServers(*dsReqCap[0].DeliveryServiceID, []int{s.ID}, true); err != nil { + if _, _, err = TOSession.CreateDeliveryServiceServers(*dsReqCap[0].DeliveryServiceID, []int{*s.ID}, true); err != nil { t.Fatalf("POST delivery service origin servers without capabilities: %v", err) } @@ -228,8 +241,14 @@ func CreateTestMSODSServerWithReqCap(t *testing.T) { func DeleteTestDeliveryServiceServers(t *testing.T) { dses, servers := getServersAndDSes(t) ds, server := dses[0], servers[0] + if server.ID == nil { + t.Fatalf("Got a server with a nil ID: %+v", server) + } + if ds.ID == nil { + t.Fatalf("Got a delivery service with a nil ID %+v", ds) + } - _, _, err := TOSession.CreateDeliveryServiceServers(*ds.ID, []int{server.ID}, true) + _, _, err := TOSession.CreateDeliveryServiceServers(*ds.ID, []int{*server.ID}, true) if err != nil { t.Errorf("POST delivery service servers: %v", err) } @@ -241,7 +260,7 @@ func DeleteTestDeliveryServiceServers(t *testing.T) { found := false for _, dss := range dsServers.Response { - if *dss.DeliveryService == *ds.ID && *dss.Server == server.ID { + if dss.DeliveryService != nil && *dss.DeliveryService == *ds.ID && dss.Server != nil && *dss.Server == *server.ID { found = true break } @@ -250,7 +269,7 @@ func DeleteTestDeliveryServiceServers(t *testing.T) { t.Error("POST delivery service servers returned success, but ds-server not in GET") } - if _, _, err := TOSession.DeleteDeliveryServiceServer(*ds.ID, server.ID); err != nil { + if _, _, err := TOSession.DeleteDeliveryServiceServer(*ds.ID, *server.ID); err != nil { t.Errorf("DELETE delivery service server: %v", err) } @@ -261,7 +280,7 @@ func DeleteTestDeliveryServiceServers(t *testing.T) { found = false for _, dss := range dsServers.Response { - if *dss.DeliveryService == *ds.ID && *dss.Server == server.ID { + if dss.DeliveryService != nil && *dss.DeliveryService == *ds.ID && dss.Server != nil && *dss.Server == *server.ID { found = true break } @@ -271,7 +290,7 @@ func DeleteTestDeliveryServiceServers(t *testing.T) { } } -func getServersAndDSes(t *testing.T) ([]tc.DeliveryServiceNullable, []tc.Server) { +func getServersAndDSes(t *testing.T) ([]tc.DeliveryServiceNullable, []tc.ServerNullable) { dses, _, err := TOSession.GetDeliveryServicesNullable() if err != nil { t.Fatalf("cannot GET DeliveryServices: %v", err) @@ -280,10 +299,11 @@ func getServersAndDSes(t *testing.T) ([]tc.DeliveryServiceNullable, []tc.Server) t.Fatal("GET DeliveryServices returned no dses, must have at least 1 to test ds-servers") } - servers, _, err := TOSession.GetServers() + resp, _, err := TOSession.GetServers(nil) if err != nil { t.Fatalf("cannot GET Servers: %v", err) } + servers := resp.Response if len(servers) < 1 { t.Fatal("GET Servers returned no dses, must have at least 1 to test ds-servers") } diff --git a/traffic_ops/testing/api/v3/servers_test.go b/traffic_ops/testing/api/v3/servers_test.go index 84b6ae9339..ff04d95724 100644 --- a/traffic_ops/testing/api/v3/servers_test.go +++ b/traffic_ops/testing/api/v3/servers_test.go @@ -16,9 +16,8 @@ package v3 */ import ( + "net/url" "testing" - - "github.com/apache/trafficcontrol/lib/go-tc" ) func TestServers(t *testing.T) { @@ -30,9 +29,12 @@ func TestServers(t *testing.T) { } func CreateTestServers(t *testing.T) { - // loop through servers, assign FKs and create for _, server := range testData.Servers { + if server.HostName == nil { + t.Errorf("found server with nil hostname: %+v", server) + continue + } resp, _, err := TOSession.CreateServer(server) t.Log("Response: ", server.HostName, " ", resp) if err != nil { @@ -43,11 +45,18 @@ func CreateTestServers(t *testing.T) { } func GetTestServers(t *testing.T) { - + params := url.Values{} for _, server := range testData.Servers { - resp, _, err := TOSession.GetServerByHostName(server.HostName) + if server.HostName == nil { + t.Errorf("found server with nil hostname: %+v", server) + continue + } + params.Set("hostName", *server.HostName) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Errorf("cannot GET Server by name: %v - %v", err, resp) + t.Errorf("cannot GET Server by name '%s': %v - %v", *server.HostName, err, resp.Alerts) + } else if resp.Summary.Count != 1 { + t.Errorf("incorrect server count, expected: 1, actual: %d", resp.Summary.Count) } } } @@ -55,7 +64,11 @@ func GetTestServers(t *testing.T) { func GetTestServersDetails(t *testing.T) { for _, server := range testData.Servers { - resp, _, err := TOSession.GetServerDetailsByHostName(server.HostName) + if server.HostName == nil { + t.Errorf("found server with nil hostname: %+v", server) + continue + } + resp, _, err := TOSession.GetServerDetailsByHostName(*server.HostName) if err != nil { t.Errorf("cannot GET Server Details by name: %v - %v", err, resp) } @@ -63,38 +76,92 @@ func GetTestServersDetails(t *testing.T) { } func UpdateTestServers(t *testing.T) { + if len(testData.Servers) < 1 { + t.Fatal("Need at least one server to test updating") + } firstServer := testData.Servers[0] - hostName := firstServer.HostName - // Retrieve the server by hostname so we can get the id for the Update - resp, _, err := TOSession.GetServerByHostName(hostName) + if firstServer.HostName == nil { + t.Fatalf("First test server had nil hostname: %+v", firstServer) + } + hostName := *firstServer.HostName + params := url.Values{} + params.Add("hostName", hostName) + + // Retrieve the server by hostname so we can get the id for the Update + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Errorf("cannot GET Server by hostname: %v - %v", firstServer.HostName, err) + t.Fatalf("cannot GET Server by hostname '%s': %v - %v", hostName, err, resp.Alerts) + } + if len(resp.Response) < 1 { + t.Fatalf("Expected at least one server to exist by hostname '%s'", hostName) + } + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server to exist by hostname '%s' - actual: %d", hostName, len(resp.Response)) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + + remoteServer := resp.Response[0] + if remoteServer.ID == nil { + t.Fatalf("Got null ID for server '%s'", hostName) + } + + infs := remoteServer.Interfaces + if len(infs) < 1 { + t.Fatalf("Expected server '%s' to have at least one network interface", hostName) } - remoteServer := resp[0] + inf := infs[0] + updatedServerInterface := "bond1" updatedServerRack := "RR 119.03" // update rack and interfaceName values on server - remoteServer.InterfaceName = updatedServerInterface - remoteServer.Rack = updatedServerRack - var alert tc.Alerts - alert, _, err = TOSession.UpdateServerByID(remoteServer.ID, remoteServer) + inf.Name = updatedServerInterface + infs[0] = inf + remoteServer.Interfaces = infs + remoteServer.Rack = &updatedServerRack + + alerts, _, err := TOSession.UpdateServerByID(*remoteServer.ID, remoteServer) if err != nil { - t.Errorf("cannot UPDATE Server by hostname: %v - %v", err, alert) + t.Fatalf("cannot UPDATE Server by ID %d (hostname '%s'): %v - %v", *remoteServer.ID, hostName, err, alerts) } // Retrieve the server to check rack and interfaceName values were updated - resp, _, err = TOSession.GetServerByID(remoteServer.ID) + resp, _, err = TOSession.GetServers(¶ms) if err != nil { t.Errorf("cannot GET Server by ID: %v - %v", remoteServer.HostName, err) } + if len(resp.Response) < 1 { + t.Fatalf("Expected at least one server to exist by hostname '%s'", hostName) + } + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server to exist by hostname '%s' - actual: %d", hostName, len(resp.Response)) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + + respServer := resp.Response[0] + infs = respServer.Interfaces + found := false + for _, inf = range infs { + if inf.Name == updatedServerInterface { + found = true + break + } + } + if !found { + t.Errorf("Expected server '%s' to have an interface named '%s' after update", hostName, updatedServerInterface) + t.Logf("Actual interfaces: %+v", infs) + } - respServer := resp[0] - if respServer.InterfaceName != updatedServerInterface || respServer.Rack != updatedServerRack { - t.Errorf("results do not match actual: %s, expected: %s", respServer.InterfaceName, updatedServerInterface) - t.Errorf("results do not match actual: %s, expected: %s", respServer.Rack, updatedServerRack) + if respServer.Rack == nil { + t.Errorf("results do not match actual: null, expected: '%s'", updatedServerRack) + } else if *respServer.Rack != updatedServerRack { + t.Errorf("results do not match actual: '%s', expected: '%s'", *respServer.Rack, updatedServerRack) + } + + if remoteServer.TypeID == nil { + t.Fatalf("Cannot test server type change update; server '%s' had nil type ID", hostName) } // Assign server to DS and then attempt to update to a different type @@ -114,48 +181,69 @@ func UpdateTestServers(t *testing.T) { t.Fatal("GET Server Types returned less then 2 types, must have at least 2 to test invalid type server update") } for _, t := range serverTypes { - if t.ID != remoteServer.TypeID { - remoteServer.TypeID = t.ID + if t.ID != *remoteServer.TypeID { + remoteServer.TypeID = &t.ID break } } // Assign server to DS - _, _, err = TOSession.CreateDeliveryServiceServers(*dses[0].ID, []int{remoteServer.ID}, true) + _, _, err = TOSession.CreateDeliveryServiceServers(*dses[0].ID, []int{*remoteServer.ID}, true) if err != nil { t.Fatalf("POST delivery service servers: %v", err) } // Attempt Update - should fail - _, _, err = TOSession.UpdateServerByID(remoteServer.ID, remoteServer) + alerts, _, err = TOSession.UpdateServerByID(*remoteServer.ID, remoteServer) if err == nil { t.Errorf("expected error when updating Server Type of a server assigned to DSes") + } else { + t.Logf("type change update alerts: %+v", alerts) } } func DeleteTestServers(t *testing.T) { + params := url.Values{} for _, server := range testData.Servers { - resp, _, err := TOSession.GetServerByHostName(server.HostName) + if server.HostName == nil { + t.Errorf("found server with nil hostname: %+v", server) + continue + } + + params.Set("hostName", *server.HostName) + + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Errorf("cannot GET Server by hostname: %v - %v", server.HostName, err) + t.Errorf("cannot GET Server by hostname '%s': %v - %v", *server.HostName, err, resp.Alerts) + continue } - if len(resp) > 0 { - respServer := resp[0] + if len(resp.Response) > 0 { + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server by hostname '%s' - actual: %d", *server.HostName, len(resp.Response)) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + respServer := resp.Response[0] + + if respServer.ID == nil { + t.Errorf("Server '%s' had nil ID", *server.HostName) + continue + } - delResp, _, err := TOSession.DeleteServerByID(respServer.ID) + delResp, _, err := TOSession.DeleteServerByID(*respServer.ID) if err != nil { - t.Errorf("cannot DELETE Server by ID: %v - %v", err, delResp) + t.Errorf("cannot DELETE Server by ID %d: %v - %v", *respServer.ID, err, delResp) + continue } // Retrieve the Server to see if it got deleted - serv, _, err := TOSession.GetServerByHostName(server.HostName) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Errorf("error deleting Server hostname: %s", err.Error()) + t.Errorf("error deleting Server hostname '%s': %v - %v", *server.HostName, err, resp.Alerts) } - if len(serv) > 0 { - t.Errorf("expected Server hostname: %s to be deleted", server.HostName) + if len(resp.Response) > 0 { + t.Errorf("expected Server hostname: %s to be deleted", *server.HostName) } } } diff --git a/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go b/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go index 8afd7ff654..dee4472324 100644 --- a/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go +++ b/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go @@ -16,6 +16,7 @@ package v3 import ( "net/http" + "net/url" "testing" "github.com/apache/trafficcontrol/lib/go-tc" @@ -30,13 +31,28 @@ func TestAssignments(t *testing.T) { } func AssignTestDeliveryService(t *testing.T) { - rs, _, err := TOSession.GetServerByHostName(testData.Servers[0].HostName) + if len(testData.Servers) < 1 { + t.Fatal("Need at least one test server to test delivery service assignment") + } + + server := testData.Servers[0] + if server.HostName == nil { + t.Fatalf("First server had nil hostname: %+v", server) + } + + params := url.Values{} + params.Add("hostName", *server.HostName) + + rs, _, err := TOSession.GetServers(¶ms) if err != nil { t.Fatalf("Failed to fetch server information: %v", err) - } else if len(rs) == 0 { + } else if len(rs.Response) == 0 { t.Fatalf("Failed to fetch server information: No results returned!") } - firstServer := rs[0] + firstServer := rs.Response[0] + if firstServer.ID == nil { + t.Fatalf("Server '%s' had nil ID", *server.HostName) + } rd, _, err := TOSession.GetDeliveryServiceByXMLIDNullable(*testData.DeliveryServices[0].XMLID) if err != nil { @@ -49,13 +65,13 @@ func AssignTestDeliveryService(t *testing.T) { if firstDS.ID == nil { t.Fatal("Fetch DS information returned unknown ID") } - alerts, _, err := TOSession.AssignDeliveryServiceIDsToServerID(firstServer.ID, []int{*firstDS.ID}, true) + alerts, _, err := TOSession.AssignDeliveryServiceIDsToServerID(*firstServer.ID, []int{*firstDS.ID}, true) if err != nil { t.Errorf("Couldn't assign DS '%+v' to server '%+v': %v (alerts: %v)", firstDS, firstServer, err, alerts) } t.Logf("alerts: %+v", alerts) - response, _, err := TOSession.GetServerIDDeliveryServices(firstServer.ID) + response, _, err := TOSession.GetServerIDDeliveryServices(*firstServer.ID) t.Logf("response: %+v", response) if err != nil { t.Fatalf("Couldn't get Delivery Services assigned to Server '%+v': %v", firstServer, err) @@ -75,24 +91,33 @@ func AssignTestDeliveryService(t *testing.T) { } func AssignIncorrectTestDeliveryService(t *testing.T) { - var server *tc.Server + var server *tc.ServerNullable for _, s := range testData.Servers { - if s.CDNName == "cdn2" { + if s.CDNName != nil && *s.CDNName == "cdn2" { server = &s break } } if server == nil { - t.Fatalf("Couldn't find a server in CDN 'cdn2'!") + t.Fatal("Couldn't find a server in CDN 'cdn2'!") } + if server.HostName == nil { + t.Fatalf("Server found with nil hostname: %+v", *server) + } + hostname := *server.HostName - rs, _, err := TOSession.GetServerByHostName(server.HostName) + params := url.Values{} + params.Add("hostName", hostname) + rs, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Fatalf("Failed to fetch server information: %v", err) - } else if len(rs) == 0 { + t.Fatalf("Failed to fetch server information: %v - %v", err, rs.Alerts) + } else if len(rs.Response) == 0 { t.Fatalf("Failed to fetch server information: No results returned!") } - server = &rs[0] + server = &rs.Response[0] + if server.ID == nil { + t.Fatalf("Server '%s' has nil ID", hostname) + } rd, _, err := TOSession.GetDeliveryServiceByXMLIDNullable(*testData.DeliveryServices[0].XMLID) if err != nil { @@ -105,12 +130,12 @@ func AssignIncorrectTestDeliveryService(t *testing.T) { if firstDS.ID == nil { t.Fatal("Fetch DS information returned unknown ID") } - alerts, _, err := TOSession.AssignDeliveryServiceIDsToServerID(server.ID, []int{*firstDS.ID}, false) + alerts, _, err := TOSession.AssignDeliveryServiceIDsToServerID(*server.ID, []int{*firstDS.ID}, false) if err == nil { t.Errorf("Expected bad assignment to fail, but it didn't! (alerts: %v)", alerts) } - response, _, err := TOSession.GetServerIDDeliveryServices(server.ID) + response, _, err := TOSession.GetServerIDDeliveryServices(*server.ID) t.Logf("response: %+v", response) if err != nil { t.Fatalf("Couldn't get Delivery Services assigned to Server '%+v': %v", *server, err) @@ -131,24 +156,29 @@ func AssignIncorrectTestDeliveryService(t *testing.T) { } func AssignTopologyBasedDeliveryService(t *testing.T) { - var server *tc.Server + var server *tc.ServerNullable for _, s := range testData.Servers { - if s.CDNName == "cdn1" && s.Type == string(tc.CacheTypeEdge) { + if s.CDNName != nil && *s.CDNName == "cdn1" && s.Type == string(tc.CacheTypeEdge) { server = &s break } } - if server == nil { + if server == nil || server.HostName == nil { t.Fatalf("Couldn't find an EDGE server in CDN 'cdn1'!") } - rs, _, err := TOSession.GetServerByHostName(server.HostName) + params := url.Values{} + params.Add("hostName", *server.HostName) + rs, _, err := TOSession.GetServers(¶ms) if err != nil { t.Fatalf("Failed to fetch server information: %v", err) - } else if len(rs) == 0 { + } else if len(rs.Response) == 0 { t.Fatalf("Failed to fetch server information: No results returned!") } - server = &rs[0] + server = &rs.Response[0] + if server.ID == nil { + t.Fatal("Server had nil ID") + } rd, _, err := TOSession.GetDeliveryServiceByXMLIDNullable("ds-top") if err != nil { @@ -161,7 +191,7 @@ func AssignTopologyBasedDeliveryService(t *testing.T) { if firstDS.ID == nil { t.Fatal("Fetch DS information returned unknown ID") } - alerts, reqInf, err := TOSession.AssignDeliveryServiceIDsToServerID(server.ID, []int{*firstDS.ID}, false) + alerts, reqInf, err := TOSession.AssignDeliveryServiceIDsToServerID(*server.ID, []int{*firstDS.ID}, false) if err == nil { t.Errorf("Expected bad assignment to fail, but it didn't! (alerts: %v)", alerts) } @@ -169,7 +199,7 @@ func AssignTopologyBasedDeliveryService(t *testing.T) { t.Fatalf("assigning Topology-based delivery service to server - expected: 400-level status code, actual: %d", reqInf.StatusCode) } - response, _, err := TOSession.GetServerIDDeliveryServices(server.ID) + response, _, err := TOSession.GetServerIDDeliveryServices(*server.ID) t.Logf("response: %+v", response) if err != nil { t.Fatalf("Couldn't get Delivery Services assigned to Server '%+v': %v", *server, err) diff --git a/traffic_ops/testing/api/v3/serverservercapability_test.go b/traffic_ops/testing/api/v3/serverservercapability_test.go index 457117e55e..c2dbe7afb5 100644 --- a/traffic_ops/testing/api/v3/serverservercapability_test.go +++ b/traffic_ops/testing/api/v3/serverservercapability_test.go @@ -16,6 +16,7 @@ package v3 */ import ( + "net/url" "testing" "github.com/apache/trafficcontrol/lib/go-tc" @@ -32,21 +33,27 @@ func CreateTestServerServerCapabilities(t *testing.T) { // Valid POSTs // loop through server ServerCapabilities, assign FKs and create + params := url.Values{} for _, ssc := range testData.ServerServerCapabilities { - servResp, _, err := TOSession.GetServerByHostName(*ssc.Server) + if ssc.Server == nil { + t.Fatalf("server-server-capability structure had nil server") + } + params.Set("hostName", *ssc.Server) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Fatalf("cannot GET Server by hostname: %v - %v", *ssc.Server, err) + t.Fatalf("cannot GET Server by hostname '%s': %v - %v", *ssc.Server, err, resp.Alerts) } + servResp := resp.Response if len(servResp) != 1 { t.Fatalf("cannot GET Server by hostname: %v. Response did not include record.", *ssc.Server) } server := servResp[0] - ssc.ServerID = &server.ID - resp, _, err := TOSession.CreateServerServerCapability(ssc) + ssc.ServerID = server.ID + createResp, _, err := TOSession.CreateServerServerCapability(ssc) if err != nil { t.Errorf("could not POST the server capability %v to server %v: %v", *ssc.ServerCapability, *ssc.Server, err) } - t.Log("Response: ", server.HostName, " ", resp) + t.Log("Response: ", *ssc.Server, " ", createResp) } // Invalid POSTs @@ -98,16 +105,19 @@ func CreateTestServerServerCapabilities(t *testing.T) { } // Attempt to assign a server capability to a non MID/EDGE server - servers, _, err := TOSession.GetServerByHostName("riak") + // TODO: DON'T hard-code server hostnames! + params.Set("hostName", "riak") + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Fatalf("cannot GET Server by hostname: %v - %v", *ssc.Server, err) + t.Fatalf("cannot GET Server by hostname 'riak': %v - %v", err, resp.Alerts) } + servers := resp.Response if len(servers) < 1 { t.Fatal("need at least one server to test invalid server type assignment") } sscInvalidType := tc.ServerServerCapability{ - ServerID: &servers[0].ID, + ServerID: servers[0].ID, ServerCapability: ssc.ServerCapability, } _, _, err = TOSession.CreateServerServerCapability(sscInvalidType) diff --git a/traffic_ops/testing/api/v3/serverupdatestatus_test.go b/traffic_ops/testing/api/v3/serverupdatestatus_test.go index 6794925d99..538226043e 100644 --- a/traffic_ops/testing/api/v3/serverupdatestatus_test.go +++ b/traffic_ops/testing/api/v3/serverupdatestatus_test.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "testing" "github.com/apache/trafficcontrol/lib/go-tc" @@ -27,15 +28,18 @@ import ( func TestServerUpdateStatus(t *testing.T) { WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { - edge1cdn1 := tc.Server{} - edge2cdn1 := tc.Server{} - mid1cdn1 := tc.Server{} - edge1cdn2 := tc.Server{} + //TODO: DON'T hard-code server hostnames! + var edge1cdn1 tc.ServerNullable + var edge2cdn1 tc.ServerNullable + var mid1cdn1 tc.ServerNullable + var edge1cdn2 tc.ServerNullable + + params := url.Values{} getServers := func() { for _, s := range []struct { name string - server *tc.Server + server *tc.ServerNullable }{ { "atlanta-edge-01", @@ -54,29 +58,45 @@ func TestServerUpdateStatus(t *testing.T) { &edge1cdn2, }, } { - resp, _, err := TOSession.GetServerByHostName(s.name) + params.Set("hostName", s.name) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Errorf("cannot GET Server by hostname: %v - %v", s.name, err) + t.Errorf("cannot GET Server by hostname '%s': %v - %v", s.name, err, resp.Alerts) + } + if len(resp.Response) < 1 { + t.Fatalf("Expected a server named '%s' to exist", s.name) + } + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server named '%s' to exist - actual: %d", s.name, len(resp.Response)) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + *s.server = resp.Response[0] + if s.server.ID == nil { + t.Fatalf("server '%s' was returned with nil ID", s.name) + } + if s.server.HostName == nil { + t.Fatalf("server '%s' was returned with nil HostName", s.name) } - *s.server = resp[0] } } getServers() // assert that servers don't have updates pending - for _, s := range []tc.Server{ + for _, s := range []tc.ServerNullable{ edge1cdn1, edge2cdn1, mid1cdn1, edge1cdn2, } { - if s.UpdPending { + if s.UpdPending == nil { + t.Error("expected UpdPending: false, actual: null") + } else if *s.UpdPending { t.Error("expected UpdPending: false, actual: true") } } // update status of MID server to OFFLINE - _, _, err := TOSession.UpdateServerStatus(mid1cdn1.ID, tc.ServerPutStatus{ + _, _, err := TOSession.UpdateServerStatus(*mid1cdn1.ID, tc.ServerPutStatus{ Status: util.JSONNameOrIDStr{Name: util.StrPtr("OFFLINE")}, OfflineReason: util.StrPtr("testing")}) if err != nil { @@ -85,17 +105,27 @@ func TestServerUpdateStatus(t *testing.T) { // assert that updates were queued for the proper EDGE servers getServers() - if !edge1cdn1.UpdPending { - t.Errorf("expected: child %s to have updates pending, actual: no updates pending", edge1cdn1.HostName) + if edge1cdn1.UpdPending == nil { + t.Errorf("expected: child %s (%d) to have updates pending, actual: property was null (or missing)", *edge1cdn1.HostName, *edge1cdn1.ID) + } else if !*edge1cdn1.UpdPending { + t.Errorf("expected: child %s (%d) to have updates pending, actual: no updates pending", *edge1cdn1.HostName, *edge1cdn1.ID) } - if !edge2cdn1.UpdPending { - t.Errorf("expected: child %s to have updates pending, actual: no updates pending", edge2cdn1.HostName) + + if edge2cdn1.UpdPending == nil { + t.Errorf("expected: child %s (%d) to have updates pending, actual: property was null (or missing)", *edge2cdn1.HostName, *edge2cdn1.ID) + } else if !*edge2cdn1.UpdPending { + t.Errorf("expected: child %s (%d) to have updates pending, actual: no updates pending", *edge2cdn1.HostName, *edge2cdn1.ID) } - if mid1cdn1.UpdPending { - t.Errorf("expected: server %s with updated status to have no updates pending, actual: updates pending", mid1cdn1.HostName) + if mid1cdn1.UpdPending == nil { + t.Errorf("expected: server %s (%d) with updated status to have no updates pending, actual: property was null (or missing)", *mid1cdn1.HostName, *mid1cdn1.ID) + } else if *mid1cdn1.UpdPending { + t.Errorf("expected: server %s (%d) with updated status to have no updates pending, actual: updates pending", *mid1cdn1.HostName, *mid1cdn1.ID) } - if edge1cdn2.UpdPending { - t.Errorf("expected: server %s in different CDN than server with updated status to have no updates pending, actual: updates pending", edge2cdn1.HostName) + + if edge1cdn2.UpdPending == nil { + t.Errorf("expected: server %s (%d) in different CDN than server with updated status to have no updates pending, actual: updates pending", *edge1cdn2.HostName, *edge1cdn2.ID) + } else if *edge1cdn2.UpdPending { + t.Errorf("expected: server %s (%d) in different CDN than server with updated status to have no updates pending, actual: updates pending", *edge1cdn2.HostName, *edge1cdn2.ID) } // update status of MID server to OFFLINE via status ID @@ -104,7 +134,7 @@ func TestServerUpdateStatus(t *testing.T) { t.Fatalf("cannot GET status by name: %v", err) } _, _, err = TOSession.UpdateServerStatus( - mid1cdn1.ID, + *mid1cdn1.ID, tc.ServerPutStatus{ Status: util.JSONNameOrIDStr{ID: util.IntPtr(status[0].ID)}, OfflineReason: util.StrPtr("testing"), @@ -129,7 +159,7 @@ func TestServerUpdateStatus(t *testing.T) { // status does not exist _, _, err = TOSession.UpdateServerStatus( - mid1cdn1.ID, + *mid1cdn1.ID, tc.ServerPutStatus{ Status: util.JSONNameOrIDStr{Name: util.StrPtr("NOT_A_REAL_STATUS")}, OfflineReason: util.StrPtr("testing"), @@ -141,7 +171,7 @@ func TestServerUpdateStatus(t *testing.T) { // offlineReason required for OFFLINE status _, _, err = TOSession.UpdateServerStatus( - mid1cdn1.ID, + *mid1cdn1.ID, tc.ServerPutStatus{ Status: util.JSONNameOrIDStr{Name: util.StrPtr("OFFLINE")}, OfflineReason: nil, @@ -153,7 +183,7 @@ func TestServerUpdateStatus(t *testing.T) { // offlineReason required for ADMIN_DOWN status _, _, err = TOSession.UpdateServerStatus( - mid1cdn1.ID, + *mid1cdn1.ID, tc.ServerPutStatus{ Status: util.JSONNameOrIDStr{Name: util.StrPtr("ADMIN_DOWN")}, OfflineReason: nil, @@ -167,6 +197,7 @@ func TestServerUpdateStatus(t *testing.T) { func TestServerQueueUpdate(t *testing.T) { WithObjs(t, []TCObj{Divisions, Regions, PhysLocations, Statuses, Types, CacheGroups, CDNs, Profiles, Servers}, func() { + // TODO: DON'T hard-code server hostnames! const serverName = "atlanta-edge-01" queueUpdateActions := map[bool]string{ @@ -174,26 +205,42 @@ func TestServerQueueUpdate(t *testing.T) { true: "queue", } - var s tc.Server - resp, _, err := TOSession.GetServerByHostName(serverName) + var s tc.ServerNullable + params := url.Values{} + params.Add("hostName", serverName) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Fatalf("failed to GET Server by hostname: %v - %v", serverName, err) + t.Fatalf("failed to GET Server by hostname '%s': %v - %v", serverName, err, resp.Alerts) } - s = resp[0] + if len(resp.Response) < 1 { + t.Fatalf("Expected a server named '%s' to exist", serverName) + } + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server named '%s' to exist", serverName) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + s = resp.Response[0] // assert that servers don't have updates pending - if got, want := s.UpdPending, false; got != want { + if s.UpdPending == nil { + t.Fatalf("Server '%s' had null (or missing) updPending property", serverName) + } + if got, want := *s.UpdPending, false; got != want { t.Fatalf("unexpected UpdPending, got: %v, want: %v", got, want) } + if s.ID == nil { + t.Fatalf("Server '%s' had nil ID", serverName) + } + for _, setVal := range [...]bool{true, false} { t.Run(fmt.Sprint(setVal), func(t *testing.T) { // queue update and check response - quResp, _, err := TOSession.SetServerQueueUpdate(s.ID, setVal) + quResp, _, err := TOSession.SetServerQueueUpdate(*s.ID, setVal) if err != nil { t.Fatalf("failed to set queue update for server with ID %v to %v: %v", s.ID, setVal, err) } - if got, want := int(quResp.Response.ServerID), s.ID; got != want { + if got, want := int(quResp.Response.ServerID), *s.ID; got != want { t.Errorf("wrong serverId in response, got: %v, want: %v", got, want) } if got, want := quResp.Response.Action, queueUpdateActions[setVal]; got != want { @@ -201,12 +248,22 @@ func TestServerQueueUpdate(t *testing.T) { } // assert that the server has updates queued - resp, _, err = TOSession.GetServerByID(s.ID) + resp, _, err = TOSession.GetServers(¶ms) if err != nil { - t.Fatalf("failed to GET Server by ID: %v - %v", s.ID, err) + t.Fatalf("failed to GET Server by hostname '%s': %v - %v", serverName, err, resp.Alerts) + } + if len(resp.Response) < 1 { + t.Fatalf("Expected a server named '%s' to exist", serverName) } - s = resp[0] - if got, want := s.UpdPending, setVal; got != want { + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server named '%s' to exist", serverName) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + s = resp.Response[0] + if s.UpdPending == nil { + t.Fatalf("Server '%s' had null (or missing) updPending property", serverName) + } + if got, want := *s.UpdPending, setVal; got != want { t.Errorf("unexpected UpdPending, got: %v, want: %v", got, want) } }) @@ -224,7 +281,9 @@ func TestServerQueueUpdate(t *testing.T) { if err != nil { t.Fatalf("failed to encode request body: %v", err) } - path := fmt.Sprintf(TestAPIBase+"/servers/%d/queue_update", s.ID) + + // TODO: don't construct URLs like this, nor use "RawRequest" + path := fmt.Sprintf(TestAPIBase+"/servers/%d/queue_update", *s.ID) httpResp, _, err := TOSession.RawRequest(http.MethodPost, path, req) if err != nil { t.Fatalf("POST request failed: %v", err) @@ -242,42 +301,65 @@ func TestSetServerUpdateStatuses(t *testing.T) { t.Fatal("cannot GET Server: no test data") } testServer := testData.Servers[0] + if testServer.HostName == nil { + t.Fatalf("First test server had nil hostname: %+v", testServer) + } + params := url.Values{} + params.Add("hostName", *testServer.HostName) testVals := func(queue *bool, reval *bool) { - existingServer, _, err := TOSession.GetServerByHostName(testServer.HostName) + resp, _, err := TOSession.GetServers(¶ms) if err != nil { - t.Errorf("cannot GET Server by name: %v - %v", err, existingServer) - } else if len(existingServer) != 1 { - t.Fatalf("GET Server expected 1, actual %v", len(existingServer)) + t.Errorf("cannot GET Server by name '%s': %v - %v", *testServer.HostName, err, resp.Alerts) + } else if len(resp.Response) != 1 { + t.Fatalf("GET Server expected 1, actual %v", len(resp.Response)) + } + + existingServer := resp.Response + + if existingServer[0].UpdPending == nil { + t.Fatalf("Server '%s' had nil UpdPending before update status change", *testServer.HostName) + } + if existingServer[0].RevalPending == nil { + t.Fatalf("Server '%s' had nil RevalPending before update status change", *testServer.HostName) } - if _, err := TOSession.SetUpdateServerStatuses(testServer.HostName, queue, reval); err != nil { + if _, err := TOSession.SetUpdateServerStatuses(*testServer.HostName, queue, reval); err != nil { t.Fatalf("UpdateServerStatuses error expected: nil, actual: %v", err) } - newServer, _, err := TOSession.GetServerByHostName(testServer.HostName) + resp, _, err = TOSession.GetServers(¶ms) if err != nil { - t.Errorf("cannot GET Server by name: %v - %v", err, existingServer) - } else if len(newServer) != 1 { - t.Fatalf("GET Server expected 1, actual %v", len(newServer)) + t.Errorf("cannot GET Server by name '%s': %v - %v", *testServer.HostName, err, resp.Alerts) + } else if len(resp.Response) != 1 { + t.Fatalf("GET Server expected 1, actual %v", len(resp.Response)) + } + + newServer := resp.Response + + if newServer[0].UpdPending == nil { + t.Fatalf("Server '%s' had nil UpdPending after update status change", *testServer.HostName) + } + if newServer[0].RevalPending == nil { + t.Fatalf("Server '%s' had nil RevalPending after update status change", *testServer.HostName) } if queue != nil { - if newServer[0].UpdPending != *queue { - t.Errorf("set queue update pending to %v, but then got server %v", *queue, newServer[0].UpdPending) + if *newServer[0].UpdPending != *queue { + t.Errorf("set queue update pending to %v, but then got server %v", *queue, *newServer[0].UpdPending) } } else { - if newServer[0].UpdPending != existingServer[0].UpdPending { - t.Errorf("set queue update pending with nil (don't update), but then got server %v which didn't match pre-update value %v", newServer[0].UpdPending, existingServer[0].UpdPending) + if *newServer[0].UpdPending != *existingServer[0].UpdPending { + t.Errorf("set queue update pending with nil (don't update), but then got server %v which didn't match pre-update value %v", *newServer[0].UpdPending, *existingServer[0].UpdPending) } } if reval != nil { - if newServer[0].RevalPending != *reval { - t.Errorf("set reval update pending to %v, but then got server %v", *reval, newServer[0].RevalPending) + if *newServer[0].RevalPending != *reval { + t.Errorf("set reval update pending to %v, but then got server %v", *reval, *newServer[0].RevalPending) } } else { - if newServer[0].RevalPending != existingServer[0].RevalPending { - t.Errorf("set reval update pending with nil (don't update), but then got server %v which didn't match pre-update value %v", newServer[0].RevalPending, existingServer[0].RevalPending) + if *newServer[0].RevalPending != *existingServer[0].RevalPending { + t.Errorf("set reval update pending with nil (don't update), but then got server %v which didn't match pre-update value %v", *newServer[0].RevalPending, *existingServer[0].RevalPending) } } } @@ -290,7 +372,7 @@ func TestSetServerUpdateStatuses(t *testing.T) { testVals(util.BoolPtr(true), nil) testVals(util.BoolPtr(false), nil) - if _, err := TOSession.SetUpdateServerStatuses(testServer.HostName, nil, nil); err == nil { + if _, err := TOSession.SetUpdateServerStatuses(*testServer.HostName, nil, nil); err == nil { t.Errorf("UpdateServerStatuses with (nil,nil) expected error, actual nil") } }) diff --git a/traffic_ops/testing/api/v3/tc-fixtures.json b/traffic_ops/testing/api/v3/tc-fixtures.json index b1007a3baf..b2117cf76d 100644 --- a/traffic_ops/testing/api/v3/tc-fixtures.json +++ b/traffic_ops/testing/api/v3/tc-fixtures.json @@ -1629,14 +1629,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:8::1/64", - "ip6Gateway": "2345:1234:12:8::1", - "ipAddress": "127.0.0.21", - "ipGateway": "127.0.0.21", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.21/30", + "gateway": "127.0.0.21", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::1/64", + "gateway": "2345:1234:12:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1667,14 +1679,21 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth0", - "ip6Address": "", - "ip6Gateway": "", - "ipAddress": "0.0.0.0", - "ipGateway": "0.0.0.0", - "ipIsService": true, - "ipNetmask": "0.0.0.0", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "0.0.0.0/0", + "gateway": "0.0.0.0", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1705,14 +1724,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth1", - "ip6Address": "2345:1234:12:8::2/64", - "ip6Gateway": "2345:1234:12:8::2", - "ipAddress": "127.0.0.11", - "ipGateway": "127.0.0.11", - "ipIsService": true, - "ipNetmask": "255.255.252.0", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.11/22", + "gateway": "127.0.0.11", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::2/64", + "gateway": "2345:1234:12:8::2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth1" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1743,14 +1774,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:8::3/64", - "ip6Gateway": "2345:1234:12:8::3", - "ipAddress": "127.0.0.12", - "ipGateway": "127.0.0.1", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.12/30", + "gateway": "127.0.0.1", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::3/64", + "gateway": "2345:1234:12:8::3", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1781,14 +1824,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:2::4/64", - "ip6Gateway": "2345:1234:12:8::4", - "ipAddress": "127.0.0.13", - "ipGateway": "127.0.0.1", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:2::4/64", + "gateway": "2345:1234:12:2::4", + "serviceAddress": false + }, + { + "address": "127.0.0.13/30", + "gateway": "127.0.0.1", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1819,14 +1874,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:8::5/64", - "ip6Gateway": "2345:1234:12:8::5", - "ipAddress": "127.0.0.14", - "ipGateway": "127.0.0.1", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:8::5/64", + "gateway": "2345:1234:12:8::5", + "serviceAddress": false + }, + { + "address": "127.0.0.14/30", + "gateway": "127.0.0.1", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1857,13 +1924,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:d::6/64", - "ip6Gateway": "2345:1234:12:d::6", - "ipAddress": "127.0.0.15", - "ipGateway": "127.0.0.7", - "ipIsService": true, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:d::6/64", + "gateway": "2345:1234:12:d::6", + "serviceAddress": false + }, + { + "address": "127.0.0.15/30", + "gateway": "127.0.0.7", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "ipNetmask": "255.255.255.252", "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", @@ -1895,14 +1975,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:d::7/64", - "ip6Gateway": "2345:1234:12:d::7", - "ipAddress": "127.0.0.16", - "ipGateway": "127.0.0.7", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:d::7/64", + "gateway": "2345:1234:12:d::7", + "serviceAddress": false + }, + { + "address": "127.0.0.16/30", + "gateway": "127.0.0.7", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1933,14 +2025,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:d::8/64", - "ip6Gateway": "2345:1234:12:d::8", - "ipAddress": "127.0.0.17", - "ipGateway": "127.0.0.17", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:d::8/64", + "gateway": "2345:1234:12:d::8", + "serviceAddress": false + }, + { + "address": "127.0.0.17/30", + "gateway": "127.0.0.17", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -1971,14 +2075,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:d::9/64", - "ip6Gateway": "2345:1234:12:d::9", - "ipAddress": "127.0.0.18", - "ipGateway": "127.0.0.18", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.18/30", + "gateway": "127.0.0.18", + "serviceAddress": true + }, + { + "address": "2345:1234:12:d::9/64", + "gateway": "2345:1234:12:d::9", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -2009,14 +2125,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:9::10/64", - "ip6Gateway": "2345:1234:12:9::10", - "ipAddress": "127.0.0.2", - "ipGateway": "127.0.0.2", - "ipIsService": true, - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.2/30", + "gateway": "127.0.0.2", + "serviceAddress": true + }, + { + "address": "2345:1234:12:9::10/64", + "gateway": "2345:1234:12:9::10", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -2047,14 +2175,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:b::11/64", - "ip6Gateway": "2345:1234:12:b::11", - "ipAddress": "127.0.0.4", - "ipIsService": true, - "ipGateway": "127.0.0.4", - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.4/30", + "gateway": "127.0.0.4", + "serviceAddress": true + }, + { + "address": "2345:1234:12:b::11/64", + "gateway": "2345:1234:12:b::11", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -2085,14 +2225,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:b::13/64", - "ip6Gateway": "2345:1234:12:b::13", - "ipAddress": "127.0.0.31", - "ipIsService": true, - "ipGateway": "127.0.0.4", - "ipNetmask": "255.255.255.0", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.31/24", + "gateway": "127.0.0.4", + "serviceAddress": true + }, + { + "address": "2345:1234:12:b::13/64", + "gateway": "2345:1234:12:b::13", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "mgmtIpAddress": "", "mgmtIpGateway": "", "mgmtIpNetmask": "", @@ -2122,14 +2274,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 1500, - "interfaceName": "eth1", - "ip6Address": "2345:1234:12:b::12/64", - "ip6Gateway": "2345:1234:12:b::12", - "ipAddress": "127.0.0.1", - "ipIsService": true, - "ipGateway": "127.0.0.1", - "ipNetmask": "255.255.252.0", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.1/22", + "gateway": "127.0.0.1", + "serviceAddress": true + }, + { + "address": "2345:1234:12:b::12/64", + "gateway": "2345:1234:12:b::12", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth1" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -2160,14 +2324,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "2345:1234:12:8::20/64", - "ip6Gateway": "2345:1234:12:8::20", - "ipAddress": "127.0.0.1", - "ipIsService": true, - "ipGateway": "127.0.0.1", - "ipNetmask": "255.255.255.252", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.1/30", + "gateway": "127.0.0.1", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::20/64", + "gateway": "2345:1234:12:8::20", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "lastUpdated": "2018-03-28T17:30:00.220351+00:00", "mgmtIpAddress": "", "mgmtIpGateway": "", @@ -2198,14 +2374,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "::13/64", - "ip6Gateway": "2345:1234:12:b::13", - "ipAddress": "127.0.0.100", - "ipIsService": true, - "ipGateway": "127.0.0.4", - "ipNetmask": "255.255.255.0", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::13/64", + "gateway": "2345:1234:12:b::13", + "serviceAddress": false + }, + { + "address": "127.0.0.100/24", + "gateway": "127.0.0.4", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "mgmtIpAddress": "", "mgmtIpGateway": "", "mgmtIpNetmask": "", @@ -2235,14 +2423,26 @@ "iloIpNetmask": "", "iloPassword": "", "iloUsername": "", - "interfaceMtu": 9000, - "interfaceName": "bond0", - "ip6Address": "::14/64", - "ip6Gateway": "2345:1234:12:b::13", - "ipAddress": "127.0.0.101", - "ipIsService": true, - "ipGateway": "127.0.0.4", - "ipNetmask": "255.255.255.0", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::14/64", + "gateway": "2345:1234:12:b::13", + "serviceAddress": false + }, + { + "address": "127.0.0.101/24", + "gateway": "127.0.0.4", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0" + } + ], "mgmtIpAddress": "", "mgmtIpGateway": "", "mgmtIpNetmask": "", diff --git a/traffic_ops/testing/api/v3/todb_test.go b/traffic_ops/testing/api/v3/todb_test.go index d0511d1755..abec74acfe 100644 --- a/traffic_ops/testing/api/v3/todb_test.go +++ b/traffic_ops/testing/api/v3/todb_test.go @@ -306,7 +306,6 @@ func Teardown(db *sql.DB) error { DELETE FROM api_capability; DELETE FROM deliveryservices_required_capability; DELETE FROM server_server_capability; - DELETE FROM server_server_capability; DELETE FROM server_capability; DELETE FROM to_extension; DELETE FROM staticdnsentry; @@ -325,6 +324,8 @@ func Teardown(db *sql.DB) error { DELETE FROM deliveryservice_server; DELETE FROM deliveryservice; DELETE FROM origin; + DELETE FROM ip_address; + DELETE FROM interface; DELETE FROM server; DELETE FROM phys_location; DELETE FROM region; diff --git a/traffic_ops/testing/api/v3/traffic_control_test.go b/traffic_ops/testing/api/v3/traffic_control_test.go index 4c400f2d6d..e090021943 100644 --- a/traffic_ops/testing/api/v3/traffic_control_test.go +++ b/traffic_ops/testing/api/v3/traffic_control_test.go @@ -42,7 +42,7 @@ type TrafficControl struct { PhysLocations []tc.PhysLocation `json:"physLocations"` Regions []tc.Region `json:"regions"` Roles []tc.Role `json:"roles"` - Servers []tc.Server `json:"servers"` + Servers []tc.ServerNullable `json:"servers"` ServerServerCapabilities []tc.ServerServerCapability `json:"serverServerCapabilities"` ServerCapabilities []tc.ServerCapability `json:"serverCapabilities"` Statuses []tc.StatusNullable `json:"statuses"` diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go index af138f13e7..6c28d7d2b8 100644 --- a/traffic_ops/traffic_ops_golang/api/api.go +++ b/traffic_ops/traffic_ops_golang/api/api.go @@ -71,12 +71,21 @@ WHERE type in ( SELECT id AND status=(SELECT id FROM status WHERE name='ONLINE') ` +type APIResponse struct { + Response interface{} `json:"response"` +} + +type APIResponseWithSummary struct { + Response interface{} `json:"response"` + Summary struct { + Count uint64 `json:"count"` + } `json:"summary"` +} + // WriteResp takes any object, serializes it as JSON, and writes that to w. Any errors are logged and written to w via tc.GetHandleErrorsFunc. // This is a helper for the common case; not using this in unusual cases is perfectly acceptable. func WriteResp(w http.ResponseWriter, r *http.Request, v interface{}) { - resp := struct { - Response interface{} `json:"response"` - }{v} + resp := APIResponse{v} WriteRespRaw(w, r, resp) } @@ -98,6 +107,18 @@ func WriteRespRaw(w http.ResponseWriter, r *http.Request, v interface{}) { w.Write(append(bts, '\n')) } +// WriteRespWithSummary writes a JSON-encoded representation of an arbitrary +// object to the provided writer, and cleans up the corresponding request +// object. It also provides a "summary" section to the response object that +// contains the given "count". +func WriteRespWithSummary(w http.ResponseWriter, r *http.Request, v interface{}, count uint64) { + var resp APIResponseWithSummary + resp.Response = v + resp.Summary.Count = count + + WriteRespRaw(w, r, resp) +} + // WriteRespVals is like WriteResp, but also takes a map of root-level values to write. The API most commonly needs these for meta-parameters, like size, limit, and orderby. // This is a helper for the common case; not using this in unusual cases is perfectly acceptable. func WriteRespVals(w http.ResponseWriter, r *http.Request, v interface{}, vals map[string]interface{}) { diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/chkconfig.go b/traffic_ops/traffic_ops_golang/ats/atsserver/chkconfig.go index db58f24d8c..b445781faf 100644 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/chkconfig.go +++ b/traffic_ops/traffic_ops_golang/ats/atsserver/chkconfig.go @@ -25,7 +25,9 @@ import ( "github.com/apache/trafficcontrol/lib/go-atscfg" "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats" ) func GetChkconfig(w http.ResponseWriter, r *http.Request) { @@ -45,7 +47,7 @@ func GetChkconfig(w http.ResponseWriter, r *http.Request) { return } - params, err := GetServerParams(inf.Tx.Tx, serverName, atscfg.ChkconfigParamConfigFile) + params, err := ats.GetServerParams(inf.Tx.Tx, serverName, atscfg.ChkconfigParamConfigFile) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server '"+string(serverName)+"' + chkconfig parameters: "+err.Error())) return diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/hostingdotconfig.go b/traffic_ops/traffic_ops_golang/ats/atsserver/hostingdotconfig.go index e3525be324..c441687afd 100644 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/hostingdotconfig.go +++ b/traffic_ops/traffic_ops_golang/ats/atsserver/hostingdotconfig.go @@ -55,7 +55,7 @@ func GetHostingDotConfig(w http.ResponseWriter, r *http.Request) { return } - multiParams, err := GetServerParams(inf.Tx.Tx, serverName, atscfg.HostingConfigParamConfigFile) + multiParams, err := ats.GetServerParams(inf.Tx.Tx, serverName, atscfg.HostingConfigParamConfigFile) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server '"+string(serverName)+"' + hosting parameters: "+err.Error())) return diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go b/traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go deleted file mode 100644 index 520200b135..0000000000 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/ipallowdotconfig.go +++ /dev/null @@ -1,148 +0,0 @@ -package atsserver - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -import ( - "database/sql" - "errors" - "net/http" - "strings" - - "github.com/apache/trafficcontrol/lib/go-atscfg" - "github.com/apache/trafficcontrol/lib/go-tc" - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats" -) - -func GetIPAllowDotConfig(w http.ResponseWriter, r *http.Request) { - inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id-or-host"}, nil) - if userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) - return - } - defer inf.Close() - - serverName, serverType, ok, err := GetServerNameAndTypeFromNameOrID(inf.Tx.Tx, inf.Params["id-or-host"]) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server name from ID: "+err.Error())) - return - } else if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("not found"), nil) - return - } - - toToolName, toURL, err := ats.GetToolNameAndURL(inf.Tx.Tx) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting tool name and url: "+err.Error())) - return - } - - params, err := GetServerParams(inf.Tx.Tx, serverName, atscfg.IPAllowConfigFileName) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server '"+string(serverName)+"' + ip_allow parameters: "+err.Error())) - return - } - - childServers := map[tc.CacheName]atscfg.IPAllowServer{} - if strings.HasPrefix(string(serverType), tc.MidTypePrefix) { - if childServers, err = GetChildServers(inf.Tx.Tx, serverName); err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting child servers from mid '"+string(serverName)+"': "+err.Error())) - return - } - } - - txt := atscfg.MakeIPAllowDotConfig(serverName, serverType, toToolName, toURL, params, childServers) - - w.Header().Set("Content-Type", "text/plain") - w.Write([]byte(txt)) -} - -// GetChildServers returns the child servers of the given Mid serverName. This should not be called with an Edge server. -func GetChildServers(tx *sql.Tx, serverName tc.CacheName) (map[tc.CacheName]atscfg.IPAllowServer, error) { - qry := ` -SELECT - s.host_name, - s.ip_address, - COALESCE(s.ip6_address, '') -FROM - server s - JOIN type tp on tp.id = s.type - JOIN cachegroup cg on cg.id = s.cachegroup -WHERE - (tp.name = '` + tc.MonitorTypeName + `' OR ( tp.name LIKE '` + tc.EdgeTypePrefix + `%') - AND cg.id IN ( - SELECT - cg2.id - FROM - server s2 - JOIN cachegroup cg2 ON (cg2.parent_cachegroup_id = s2.cachegroup OR cg2.secondary_parent_cachegroup_id = s2.cachegroup) - WHERE - s2.host_name = $1 ) - ) -` - rows, err := tx.Query(qry, serverName) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - - servers := map[tc.CacheName]atscfg.IPAllowServer{} - for rows.Next() { - svName := tc.CacheName("") - sv := atscfg.IPAllowServer{} - if err := rows.Scan(&svName, &sv.IPAddress, &sv.IP6Address); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - servers[svName] = sv - } - return servers, nil -} - -func GetServerParams(tx *sql.Tx, serverName tc.CacheName, configFile string) (map[string][]string, error) { - qry := ` -SELECT - pa.name, - pa.value -FROM - parameter pa - JOIN profile_parameter pp ON pp.parameter = pa.id - JOIN profile pr ON pr.id = pp.profile - JOIN server s ON s.profile = pr.id -WHERE - s.host_name = $1 - AND pa.config_file = $2 -` - rows, err := tx.Query(qry, serverName, configFile) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - - params := map[string][]string{} - for rows.Next() { - name := "" - val := "" - if err := rows.Scan(&name, &val); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - params[name] = append(params[name], val) - } - return params, nil -} diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/meta.go b/traffic_ops/traffic_ops_golang/ats/atsserver/meta.go index 26e741de2c..d6d94c3908 100644 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/meta.go +++ b/traffic_ops/traffic_ops_golang/ats/atsserver/meta.go @@ -25,6 +25,8 @@ import ( "github.com/apache/trafficcontrol/lib/go-atscfg" "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats" ) @@ -82,7 +84,7 @@ func GetConfigMetaData(w http.ResponseWriter, r *http.Request) { return } - txt := atscfg.MakeMetaConfig(serverName, server, tmParams.URL, tmParams.ReverseProxyURL, locationParams, uriSignedDSes, scopeParams, dsNames) + txt := atscfg.MakeMetaConfig(tc.CacheName(serverName), server, tmParams.URL, tmParams.ReverseProxyURL, locationParams, uriSignedDSes, scopeParams, dsNames) w.Header().Set(rfc.ContentType, rfc.ApplicationJSON) w.Write([]byte(txt)) } diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/packages.go b/traffic_ops/traffic_ops_golang/ats/atsserver/packages.go index 65b4025a48..7da7e4283b 100644 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/packages.go +++ b/traffic_ops/traffic_ops_golang/ats/atsserver/packages.go @@ -26,6 +26,7 @@ import ( "github.com/apache/trafficcontrol/lib/go-atscfg" "github.com/apache/trafficcontrol/lib/go-rfc" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats" ) func GetPackages(w http.ResponseWriter, r *http.Request) { @@ -45,7 +46,7 @@ func GetPackages(w http.ResponseWriter, r *http.Request) { return } - params, err := GetServerParams(inf.Tx.Tx, serverName, atscfg.PackagesParamConfigFile) + params, err := ats.GetServerParams(inf.Tx.Tx, serverName, atscfg.PackagesParamConfigFile) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server '"+string(serverName)+"' + package parameters: "+err.Error())) return diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/parentdotconfig.go b/traffic_ops/traffic_ops_golang/ats/atsserver/parentdotconfig.go deleted file mode 100644 index c3223f11b1..0000000000 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/parentdotconfig.go +++ /dev/null @@ -1,832 +0,0 @@ -package atsserver - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -import ( - "database/sql" - "errors" - "net/http" - "strconv" - "strings" - - "github.com/apache/trafficcontrol/lib/go-atscfg" - "github.com/apache/trafficcontrol/lib/go-log" - "github.com/apache/trafficcontrol/lib/go-tc" - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats" - - "github.com/lib/pq" -) - -func GetParentDotConfig(w http.ResponseWriter, r *http.Request) { - inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"server-name-or-id"}, nil) - if userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) - return - } - defer inf.Close() - - idOrHost := strings.TrimSuffix(inf.Params["server-name-or-id"], ".json") - hostName := "" - isHost := false - id, err := strconv.Atoi(idOrHost) - if err != nil { - isHost = true - hostName = idOrHost - } - - serverInfo, ok, err := &atscfg.ServerInfo{}, false, error(nil) - if isHost { - serverInfo, ok, err = getServerInfoByHost(inf.Tx.Tx, hostName) - } else { - serverInfo, ok, err = getServerInfoByID(inf.Tx.Tx, id) - } - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server info: "+err.Error())) - return - } - if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("server not found"), nil) - return - } - - atsMajorVer, err := GetATSMajorVersion(inf.Tx.Tx, int(serverInfo.ProfileID)) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting ATS major version: "+err.Error())) - return - } - - toolName, toURL, err := ats.GetToolNameAndURL(inf.Tx.Tx) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting toolname and TO url parameters: "+err.Error())) - return - } - - parentConfigDSes := []atscfg.ParentConfigDSTopLevel{} - if serverInfo.IsTopLevelCache() { - parentConfigDSes, err = getParentConfigDSTopLevel(inf.Tx.Tx, serverInfo.CDN) - } else { - parentConfigDSes, err = getParentConfigDS(inf.Tx.Tx, serverInfo.ID) - } - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server params: "+err.Error())) - return - } - - serverParams, err := getParentConfigServerProfileParams(inf.Tx.Tx, serverInfo.ID) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server params: "+err.Error())) - return - } - - parentInfos, err := getParentInfo(inf.Tx.Tx, serverInfo) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server parent info: "+err.Error())) - return - } - - text := atscfg.MakeParentDotConfig(serverInfo, atsMajorVer, toolName, toURL, parentConfigDSes, serverParams, parentInfos) - - w.Header().Set("Content-Type", "text/plain") - w.Write([]byte(text)) -} - -type ParentConfigDSSortByName []atscfg.ParentConfigDS - -func (s ParentConfigDSSortByName) Len() int { return len(([]atscfg.ParentConfigDS)(s)) } -func (s ParentConfigDSSortByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s ParentConfigDSSortByName) Less(i, j int) bool { - // TODO make this match the Perl sort "foreach my $ds ( sort @{ $data->{dslist} } )" ? - return strings.Compare(string(s[i].Name), string(s[j].Name)) < 0 -} - -// getServerInfo returns the necessary info about the server, whether the server exists, and any error. -func getServerInfoByID(tx *sql.Tx, id int) (*atscfg.ServerInfo, bool, error) { - return getServerInfo(tx, ServerInfoQuery()+`WHERE s.id = $1`, []interface{}{id}) -} - -// getServerInfo returns the necessary info about the server, whether the server exists, and any error. -func getServerInfoByHost(tx *sql.Tx, host string) (*atscfg.ServerInfo, bool, error) { - return getServerInfo(tx, ServerInfoQuery()+` WHERE s.host_name = $1 `, []interface{}{host}) -} - -// getServerInfo returns the necessary info about the server, whether the server exists, and any error. -func getServerInfo(tx *sql.Tx, qry string, qryParams []interface{}) (*atscfg.ServerInfo, bool, error) { - s := atscfg.ServerInfo{} - if err := tx.QueryRow(qry, qryParams...).Scan(&s.CDN, &s.CDNID, &s.ID, &s.HostName, &s.DomainName, &s.IP, &s.ProfileID, &s.ProfileName, &s.Port, &s.Type, &s.CacheGroupID, &s.ParentCacheGroupID, &s.SecondaryParentCacheGroupID, &s.ParentCacheGroupType, &s.SecondaryParentCacheGroupType); err != nil { - if err == sql.ErrNoRows { - return nil, false, nil - } - return nil, false, errors.New("querying server info: " + err.Error()) - } - return &s, true, nil -} - -func ServerInfoQuery() string { - return ` -SELECT - c.name as cdn, - s.cdn_id, - s.id, - s.host_name, - c.domain_name, - s.ip_address, - s.profile AS profile_id, - p.name AS profile_name, - s.tcp_port, - t.name as type, - s.cachegroup, - COALESCE(cg.parent_cachegroup_id, ` + strconv.Itoa(atscfg.InvalidID) + `) as parent_cachegroup_id, - COALESCE(cg.secondary_parent_cachegroup_id, ` + strconv.Itoa(atscfg.InvalidID) + `) as secondary_parent_cachegroup_id, - COALESCE(parentt.name, '') as parent_cachegroup_type, - COALESCE(sparentt.name, '') as secondary_parent_cachegroup_type -FROM - server s - JOIN cdn c ON s.cdn_id = c.id - JOIN type t ON s.type = t.id - JOIN profile p ON p.id = s.profile - JOIN cachegroup cg on s.cachegroup = cg.id - LEFT JOIN type parentt on parentt.id = (select type from cachegroup where id = cg.parent_cachegroup_id) - LEFT JOIN type sparentt on sparentt.id = (select type from cachegroup where id = cg.secondary_parent_cachegroup_id) -` -} - -// GetATSMajorVersion returns the major version of the given profile's package trafficserver parameter. -// If no parameter exists, this does not return an error, but rather logs a warning and uses DefaultATSVersion. -func GetATSMajorVersion(tx *sql.Tx, serverProfileID int) (int, error) { - atsVersion, _, err := ats.GetProfileParamValue(tx, serverProfileID, "package", "trafficserver") - if err != nil { - return 0, errors.New("getting profile param value: " + err.Error()) - } - if len(atsVersion) == 0 { - atsVersion = atscfg.DefaultATSVersion - log.Warnln("Parameter package.trafficserver missing for profile " + strconv.Itoa(int(serverProfileID)) + ". Assuming version " + atsVersion) - } - return atscfg.GetATSMajorVersionFromATSVersion(atsVersion) -} - -type ParentConfigDS struct { - Name tc.DeliveryServiceName - QStringIgnore tc.QStringIgnore - OriginFQDN string - MultiSiteOrigin bool - OriginShield string - Type tc.DSType - - QStringHandling string -} - -type ParentConfigDSTopLevel struct { - ParentConfigDS - MSOAlgorithm string - MSOParentRetry string - MSOUnavailableServerRetryResponses string - MSOMaxSimpleRetries string - MSOMaxUnavailableServerRetries string -} - -func ParentConfigDSQuerySelect() string { - return ` -SELECT - ds.xml_id, - COALESCE(ds.qstring_ignore, ` + tc.QStringIgnoreUseInCacheKeyAndPassUp.String() + `), - COALESCE((SELECT o.protocol::text || '://' || o.fqdn || rtrim(concat(':', o.port::text), ':') - FROM origin o - WHERE o.deliveryservice = ds.id - AND o.is_primary), '') as org_server_fqdn, - COALESCE(ds.multi_site_origin, false), - COALESCE(ds.origin_shield, ''), - ARRAY(SELECT required_capability FROM deliveryservices_required_capability dsrc WHERE dsrc.deliveryservice_id = ds.id), - dt.name AS ds_type -` -} - -const ParentConfigDSQueryFromTopLevel = ` -FROM - deliveryservice ds - JOIN type as dt ON ds.type = dt.id - JOIN cdn ON cdn.id = ds.cdn_id -` // TODO Perl does 'JOIN deliveryservice_regex dsr ON dsr.deliveryservice = ds.id JOIN regex r ON dsr.regex = r.id JOIN type as rt ON r.type = rt.id' and orders by, but doesn't use; ensure it isn't necessary - -const ParentConfigDSQueryFrom = ParentConfigDSQueryFromTopLevel + ` -` - -const ParentConfigDSQueryOrder = ` -ORDER BY ds.id -` // TODO: perl does 'ORDER BY ds.id, rt.name, dsr.set_number' - but doesn't actually use regexes - ensure it isn't necessary - -const ParentConfigDSQueryWhere = ` -WHERE ds.id in (SELECT DISTINCT(dss.deliveryservice) FROM deliveryservice_server dss where dss.server = $1) -` - -const ParentConfigDSQueryWhereTopLevel = ` -WHERE - cdn.name = $1 - AND ds.id in (SELECT deliveryservice_server.deliveryservice FROM deliveryservice_server) - AND ds.active = true -` - -func ParentConfigDSQuery() string { - return ParentConfigDSQuerySelect() + - ParentConfigDSQueryFrom + - ParentConfigDSQueryWhere + - ParentConfigDSQueryOrder -} - -func ParentConfigDSQueryTopLevel() string { - return ParentConfigDSQuerySelect() + - ParentConfigDSQueryFromTopLevel + - ParentConfigDSQueryWhereTopLevel + - ParentConfigDSQueryOrder -} - -func getParentConfigDSTopLevel(tx *sql.Tx, cdnName tc.CDNName) ([]atscfg.ParentConfigDSTopLevel, error) { - dses, err := getParentConfigDSRaw(tx, ParentConfigDSQueryTopLevel(), []interface{}{cdnName}) - if err != nil { - return nil, errors.New("getting top level raw parent config ds: " + err.Error()) - } - topDSes := []atscfg.ParentConfigDSTopLevel{} - for _, ds := range dses { - topDSes = append(topDSes, ds) - } - - dsesWithParams, err := getParentConfigDSParamsTopLevel(tx, topDSes) - if err != nil { - return nil, errors.New("getting top level ds params: " + err.Error()) - } - - return dsesWithParams, nil -} - -func getParentConfigDS(tx *sql.Tx, serverID int) ([]atscfg.ParentConfigDSTopLevel, error) { - dses, err := getParentConfigDSRaw(tx, ParentConfigDSQuery(), []interface{}{serverID}) - if err != nil { - return nil, errors.New("getting raw parent config ds: " + err.Error()) - } - - dsesWithParams, err := getParentConfigDSParams(tx, dses) - if err != nil { - return nil, errors.New("getting ds params: " + err.Error()) - } - return dsesWithParams, nil -} - -func getParentConfigServerProfileParams(tx *sql.Tx, serverID int) (map[string]string, error) { - qry := ` -SELECT - pa.name, - pa.value -FROM - parameter pa - JOIN profile_parameter pp ON pp.parameter = pa.id - JOIN profile pr ON pr.id = pp.profile - JOIN server s on s.profile = pr.id -WHERE - s.id = $1 - AND pa.config_file = 'parent.config' - AND pa.name IN ( - '` + atscfg.ParentConfigParamQStringHandling + `', - '` + atscfg.ParentConfigParamAlgorithm + `', - '` + atscfg.ParentConfigParamQString + `' - ) -` - rows, err := tx.Query(qry, serverID) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - params := map[string]string{} - for rows.Next() { - name := "" - val := "" - if err := rows.Scan(&name, &val); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - params[name] = val - } - return params, nil -} - -// getParentConfigDSRaw returns a ParentConfigDSTopLevel, but all fields in addition to ParentConfigDS will be defaulted. This is because a ParentConfigDSTopLevel is returned to share the same interface, but it doesn't actually have top level data. -func getParentConfigDSRaw(tx *sql.Tx, qry string, qryParams []interface{}) ([]atscfg.ParentConfigDSTopLevel, error) { - rows, err := tx.Query(qry, qryParams...) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - dses := []atscfg.ParentConfigDSTopLevel{} - for rows.Next() { - d := atscfg.ParentConfigDS{RequiredCapabilities: map[atscfg.ServerCapability]struct{}{}} - requiredCaps := []string{} - if err := rows.Scan(&d.Name, &d.QStringIgnore, &d.OriginFQDN, &d.MultiSiteOrigin, &d.OriginShield, pq.Array(&requiredCaps), &d.Type); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - if d.OriginFQDN == "" { - // TODO skip ANY_MAP DSes? Why? Did Perl, I didn't see it? - log.Errorf("parent.config generation: getting parent config ds: server %+v has no origin, skipping!\n", d.Name) - continue - } - d.Type = tc.DSTypeFromString(string(d.Type)) - for _, cap := range requiredCaps { - - d.RequiredCapabilities[atscfg.ServerCapability(cap)] = struct{}{} - } - dses = append(dses, atscfg.ParentConfigDSTopLevel{ParentConfigDS: d}) - } - - return dses, nil -} - -func parentConfigDSesToNamesTopLevel(dses []atscfg.ParentConfigDSTopLevel) []string { - names := []string{} - for _, ds := range dses { - names = append(names, string(ds.Name)) - } - return names -} - -const ParentConfigDSParamsQuerySelect = ` -SELECT - ds.xml_id, - pa.name, - pa.value -` -const ParentConfigDSParamsQueryFrom = ` -FROM - parameter pa - JOIN profile_parameter pp ON pp.parameter = pa.id - JOIN profile pr ON pr.id = pp.profile - JOIN deliveryservice ds on ds.profile = pr.id -` -const ParentConfigDSParamsQueryWhere = ` -WHERE - pa.config_file = 'parent.config' - AND ds.xml_id = ANY($1) - AND pa.name IN ( - '` + atscfg.ParentConfigParamQStringHandling + `' - ) -` - -var ParentConfigDSParamsQueryWhereTopLevel = ` -WHERE - pa.config_file = 'parent.config' - AND ds.xml_id = ANY($1) - AND pa.name IN ( - '` + atscfg.ParentConfigParamQStringHandling + `', - '` + atscfg.ParentConfigParamMSOAlgorithm + `', - '` + atscfg.ParentConfigParamMSOParentRetry + `', - '` + atscfg.ParentConfigParamUnavailableServerRetryResponses + `', - '` + atscfg.ParentConfigParamMaxSimpleRetries + `', - '` + atscfg.ParentConfigParamMaxUnavailableServerRetries + `' - ) -` - -const ParentConfigDSParamsQuery = ParentConfigDSParamsQuerySelect + ParentConfigDSParamsQueryFrom + ParentConfigDSParamsQueryWhere - -var ParentConfigDSParamsQueryTopLevel = ParentConfigDSParamsQuerySelect + ParentConfigDSParamsQueryFrom + ParentConfigDSParamsQueryWhereTopLevel - -func getParentConfigDSParams(tx *sql.Tx, dses []atscfg.ParentConfigDSTopLevel) ([]atscfg.ParentConfigDSTopLevel, error) { - params, err := getParentConfigDSParamsRaw(tx, ParentConfigDSParamsQuery, parentConfigDSesToNamesTopLevel(dses)) - if err != nil { - return nil, err - } - for i, ds := range dses { - dsParams, ok := params[ds.Name] - if !ok { - continue - } - if v, ok := dsParams[atscfg.ParentConfigParamQStringHandling]; ok { - ds.QStringHandling = v - dses[i] = ds - } - } - return dses, nil -} - -func getParentConfigDSParamsTopLevel(tx *sql.Tx, dses []atscfg.ParentConfigDSTopLevel) ([]atscfg.ParentConfigDSTopLevel, error) { - params, err := getParentConfigDSParamsRaw(tx, ParentConfigDSParamsQueryTopLevel, parentConfigDSesToNamesTopLevel(dses)) - if err != nil { - return nil, err - } - for i, ds := range dses { - dsParams := params[ds.Name] // it's acceptable for this to not exist, if there are no params for the DS. If so, we still need to continue below, to set all the defaults. - if v, ok := dsParams[atscfg.ParentConfigParamQStringHandling]; ok { - ds.QStringHandling = v - } - if v, ok := dsParams[atscfg.ParentConfigParamMSOAlgorithm]; ok && strings.TrimSpace(v) != "" { - ds.MSOAlgorithm = v - } else { - ds.MSOAlgorithm = atscfg.ParentConfigDSParamDefaultMSOAlgorithm - } - if v, ok := dsParams[atscfg.ParentConfigParamMSOParentRetry]; ok { - ds.MSOParentRetry = v - } else { - ds.MSOParentRetry = atscfg.ParentConfigDSParamDefaultMSOParentRetry - } - if v, ok := dsParams[atscfg.ParentConfigParamUnavailableServerRetryResponses]; ok { - ds.MSOUnavailableServerRetryResponses = v - } else { - ds.MSOUnavailableServerRetryResponses = atscfg.ParentConfigDSParamDefaultMSOUnavailableServerRetryResponses - } - if v, ok := dsParams[atscfg.ParentConfigParamMaxSimpleRetries]; ok { - ds.MSOMaxSimpleRetries = v - } else { - ds.MSOMaxSimpleRetries = atscfg.ParentConfigDSParamDefaultMaxSimpleRetries - } - if v, ok := dsParams[atscfg.ParentConfigParamMaxUnavailableServerRetries]; ok { - ds.MSOMaxUnavailableServerRetries = v - } else { - ds.MSOMaxUnavailableServerRetries = atscfg.ParentConfigDSParamDefaultMaxUnavailableServerRetries - } - dses[i] = ds - } - return dses, nil -} - -func getParentConfigDSParamsRaw(tx *sql.Tx, qry string, dsNames []string) (map[tc.DeliveryServiceName]map[string]string, error) { - rows, err := tx.Query(qry, pq.Array(dsNames)) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - - params := map[tc.DeliveryServiceName]map[string]string{} - for rows.Next() { - dsName := tc.DeliveryServiceName("") - pName := "" - pVal := "" - if err := rows.Scan(&dsName, &pName, &pVal); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - if _, ok := params[dsName]; !ok { - params[dsName] = map[string]string{} - } - params[dsName][pName] = pVal - } - return params, nil -} - -func getParentInfo(tx *sql.Tx, server *atscfg.ServerInfo) (map[atscfg.OriginHost][]atscfg.ParentInfo, error) { - parentInfos := map[atscfg.OriginHost][]atscfg.ParentInfo{} - - serverDomain, ok, err := getCDNDomainByProfileID(tx, server.ProfileID) - if err != nil { - return nil, errors.New("getting CDN domain from profile ID: " + err.Error()) - } else if !ok || serverDomain == "" { - return parentInfos, nil // TODO warn? Perl doesn't. - } - - profileCaches, originServers, err := getServerParentCacheGroupProfiles(tx, server) - if err != nil { - return nil, errors.New("getting server parent cachegroup profiles: " + err.Error()) - } - - return atscfg.MakeParentInfo(server, serverDomain, profileCaches, originServers), nil -} - -// getServerParentCacheGroupProfiles gets the profile information for servers belonging to the parent cachegroup, and secondary parent cachegroup, of the cachegroup of each server. -func getServerParentCacheGroupProfiles(tx *sql.Tx, server *atscfg.ServerInfo) (map[atscfg.ProfileID]atscfg.ProfileCache, map[atscfg.OriginHost][]atscfg.CGServer, error) { - // TODO make this more efficient - should be a single query - this was transliterated from Perl - it's extremely inefficient. - - profileCaches := map[atscfg.ProfileID]atscfg.ProfileCache{} - originServers := map[atscfg.OriginHost][]atscfg.CGServer{} // "deliveryServices" in Perl - - qry := "" - if server.IsTopLevelCache() { - // multisite origins take all the org groups in to account - qry = ` -WITH parent_cachegroup_ids AS ( - SELECT cg.id as v - FROM cachegroup cg - JOIN type on type.id = cg.type - WHERE type.name = '` + tc.CacheGroupOriginTypeName + `' -) -` - } else { - qry = ` -WITH server_cachegroup_ids AS ( - SELECT cachegroup as v FROM server WHERE id = $2 -), -parent_cachegroup_ids AS ( - SELECT parent_cachegroup_id as v - FROM cachegroup WHERE id IN (SELECT v from server_cachegroup_ids) - UNION ALL - SELECT secondary_parent_cachegroup_id as v - FROM cachegroup WHERE id IN (SELECT v from server_cachegroup_ids) -) -` - } - - qry += ` -SELECT - s.id, - s.host_name, - s.ip_address, - s.tcp_port, - s.cachegroup, - s.status, - s.type, - s.profile, - s.cdn_id, - stype.name as type_name, - ARRAY(SELECT server_capability FROM server_server_capability ssc WHERE ssc.server = s.id), - s.domain_name -FROM - server s - JOIN type stype ON s.type = stype.id - JOIN cachegroup cg ON cg.id = s.cachegroup - JOIN cdn on s.cdn_id = cdn.id - JOIN status st ON st.id = s.status -WHERE - cg.id IN (SELECT v FROM parent_cachegroup_ids) - AND (stype.name = '` + tc.OriginTypeName + `' OR stype.name LIKE '` + tc.EdgeTypePrefix + `%' OR stype.name LIKE '` + tc.MidTypePrefix + `%') - AND (st.name = '` + string(tc.CacheStatusReported) + `' OR st.name = '` + string(tc.CacheStatusOnline) + `') - AND cdn.name = $1 -` - - // TODO move qry, qryParams to separate funcs/consts - qryParams := []interface{}{} - if server.IsTopLevelCache() { - qryParams = []interface{}{server.CDN} - } else { - qryParams = []interface{}{server.CDN, server.ID} - } - - rows, err := tx.Query(qry, qryParams...) - if err != nil { - return nil, nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - - cgServerIDs := []int{} - cgServers := []atscfg.CGServer{} - for rows.Next() { - s := atscfg.CGServer{Capabilities: map[atscfg.ServerCapability]struct{}{}} - caps := []string{} - if err := rows.Scan(&s.ServerID, &s.ServerHost, &s.ServerIP, &s.ServerPort, &s.CacheGroupID, &s.Status, &s.Type, &s.ProfileID, &s.CDN, &s.TypeName, pq.Array(&caps), &s.Domain); err != nil { - return nil, nil, errors.New("scanning: " + err.Error()) - } - for _, cap := range caps { - s.Capabilities[atscfg.ServerCapability(cap)] = struct{}{} - } - cgServers = append(cgServers, s) - cgServerIDs = append(cgServerIDs, int(s.ServerID)) - } - - serverCapabilities, err := ats.GetServerCapabilitiesByID(tx, cgServerIDs) - if err != nil { - return nil, nil, errors.New("getting server capabilities: " + err.Error()) - } - - cgServerDSes, err := getServerDSes(tx, cgServerIDs) - if err != nil { - return nil, nil, errors.New("getting cachegroup server deliveryservices: " + err.Error()) - } - - profileParams, err := getParentConfigServerCacheProfileParams(tx, cgServerIDs) // TODO change to take cg IDs directly? - if err != nil { - return nil, nil, errors.New("getting cachegroup server profile params: " + err.Error()) - } - - allDSMap := map[atscfg.DeliveryServiceID]struct{}{} - for _, dses := range cgServerDSes { - for _, ds := range dses { - allDSMap[ds] = struct{}{} - } - } - allDSes := []int{} - for ds, _ := range allDSMap { - allDSes = append(allDSes, int(ds)) - } - - dsRequiredCapabilities, err := ats.GetDeliveryServiceRequiredCapabilities(tx, allDSes) - if err != nil { - return nil, nil, errors.New("getting DS required capabilities: " + err.Error()) - } - - dsOrigins, err := getDSOrigins(tx, allDSes) - if err != nil { - return nil, nil, errors.New("getting deliveryservice origins: " + err.Error()) - } - - for _, cgServer := range cgServers { - if cgServer.TypeName == tc.OriginTypeName { - dses := cgServerDSes[cgServer.ServerID] - for _, ds := range dses { - orgURI := dsOrigins[ds] - if atscfg.HasRequiredCapabilities(serverCapabilities[int(cgServer.ServerID)], dsRequiredCapabilities[int(ds)]) { - originServers[atscfg.OriginHost(orgURI.Host)] = append(originServers[atscfg.OriginHost(orgURI.Host)], cgServer) - } - } - } else { - originServers[atscfg.DeliveryServicesAllParentsKey] = append(originServers[atscfg.DeliveryServicesAllParentsKey], cgServer) - } - - if _, profileCachesHasProfile := profileCaches[cgServer.ProfileID]; !profileCachesHasProfile { - defaultProfileCache := atscfg.DefaultProfileCache() - if profileCache, profileParamsHasProfile := profileParams[cgServer.ProfileID]; !profileParamsHasProfile { - log.Warnf("cachegroup has server with profile %+v but that profile has no parameters", cgServer.ProfileID) - profileCaches[cgServer.ProfileID] = defaultProfileCache - } else { - profileCaches[cgServer.ProfileID] = profileCache - } - } - } - return profileCaches, originServers, nil -} - -func getServerDSes(tx *sql.Tx, serverIDs []int) (map[atscfg.ServerID][]atscfg.DeliveryServiceID, error) { - sds := map[atscfg.ServerID][]atscfg.DeliveryServiceID{} - if len(serverIDs) == 0 { - return sds, nil - } - qry := ` -SELECT - dss.server, - dss.deliveryservice -FROM - deliveryservice_server dss -WHERE - dss.server = ANY($1) -` - rows, err := tx.Query(qry, pq.Array(serverIDs)) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - - for rows.Next() { - sID := atscfg.ServerID(0) - dsID := atscfg.DeliveryServiceID(0) - if err := rows.Scan(&sID, &dsID); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - sds[sID] = append(sds[sID], dsID) - } - return sds, nil -} - -func getDSOrigins(tx *sql.Tx, dsIDs []int) (map[atscfg.DeliveryServiceID]*atscfg.OriginURI, error) { - origins := map[atscfg.DeliveryServiceID]*atscfg.OriginURI{} - if len(dsIDs) == 0 { - return origins, nil - } - qry := ` -SELECT - ds.id, - o.protocol::text, - o.fqdn, - COALESCE(o.port::text, '') -FROM - deliveryservice ds - JOIN origin o ON o.deliveryservice = ds.id -WHERE - ds.id = ANY($1) - AND o.is_primary -` - rows, err := tx.Query(qry, pq.Array(dsIDs)) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - - for rows.Next() { - id := atscfg.DeliveryServiceID(0) - uri := &atscfg.OriginURI{} - if err := rows.Scan(&id, &uri.Scheme, &uri.Host, &uri.Port); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - if uri.Port == "" { - if uri.Scheme == "http" { - uri.Port = "80" - } else if uri.Scheme == "https" { - uri.Port = "443" - } else { - log.Warnf("parent.config generation: origin had unknown scheme '" + uri.Scheme + "' and no port; leaving port empty") - } - } - origins[id] = uri - } - return origins, nil -} - -func getParentConfigServerCacheProfileParams(tx *sql.Tx, serverIDs []int) (map[atscfg.ProfileID]atscfg.ProfileCache, error) { - qry := ` -SELECT - pr.id, - pa.name, - pa.value -FROM - parameter pa - JOIN profile_parameter pp ON pp.parameter = pa.id - JOIN profile pr ON pr.id = pp.profile - JOIN server s on s.profile = pr.id -WHERE - s.id = ANY($1) - AND pa.config_file = 'parent.config' - AND pa.name IN ( - '` + atscfg.ParentConfigCacheParamWeight + `', - '` + atscfg.ParentConfigCacheParamPort + `', - '` + atscfg.ParentConfigCacheParamUseIP + `', - '` + atscfg.ParentConfigCacheParamRank + `', - '` + atscfg.ParentConfigCacheParamNotAParent + `' - ) -` - rows, err := tx.Query(qry, pq.Array(serverIDs)) - if err != nil { - return nil, errors.New("querying: " + err.Error()) - } - defer rows.Close() - - type Param struct { - ProfileID atscfg.ProfileID - Name string - Val string - } - - params := []Param{} - for rows.Next() { - p := Param{} - if err := rows.Scan(&p.ProfileID, &p.Name, &p.Val); err != nil { - return nil, errors.New("scanning: " + err.Error()) - } - params = append(params, p) - } - - sParams := map[atscfg.ProfileID]atscfg.ProfileCache{} // TODO change to map of pointers? Does efficiency matter? - for _, param := range params { - profileCache, ok := sParams[param.ProfileID] - if !ok { - profileCache = atscfg.DefaultProfileCache() - } - switch param.Name { - case atscfg.ParentConfigCacheParamWeight: - // f, err := strconv.ParseFloat(param.Val, 64) - // if err != nil { - // log.Errorln("parent.config generation: weight param is not a float, skipping! : " + err.Error()) - // } else { - // profileCache.Weight = f - // } - // TODO validate float? - profileCache.Weight = param.Val - case atscfg.ParentConfigCacheParamPort: - i, err := strconv.ParseInt(param.Val, 10, 64) - if err != nil { - log.Errorln("parent.config generation: port param is not an integer, skipping! : " + err.Error()) - } else { - profileCache.Port = int(i) - } - case atscfg.ParentConfigCacheParamUseIP: - profileCache.UseIP = param.Val == "1" - case atscfg.ParentConfigCacheParamRank: - i, err := strconv.ParseInt(param.Val, 10, 64) - if err != nil { - log.Errorln("parent.config generation: rank param is not an integer, skipping! : " + err.Error()) - } else { - profileCache.Rank = int(i) - } - - case atscfg.ParentConfigCacheParamNotAParent: - profileCache.NotAParent = param.Val != "false" - default: - return nil, errors.New("query returned unexpected param: " + param.Name) - } - sParams[param.ProfileID] = profileCache - } - return sParams, nil -} - -type ParentConfigServerParams struct { - QString string - Algorithm string - QStringHandling bool -} - -func getCDNDomainByProfileID(tx *sql.Tx, profileID atscfg.ProfileID) (string, bool, error) { - qry := `SELECT domain_name from cdn where id = (select cdn from profile where id = $1)` - val := "" - if err := tx.QueryRow(qry, profileID).Scan(&val); err != nil { - if err == sql.ErrNoRows { - return "", false, nil - } - return "", false, errors.New("querying: " + err.Error()) - } - return val, true, nil -} diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/remapdotconfig.go b/traffic_ops/traffic_ops_golang/ats/atsserver/remapdotconfig.go deleted file mode 100644 index 9ce112b388..0000000000 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/remapdotconfig.go +++ /dev/null @@ -1,93 +0,0 @@ -package atsserver - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -import ( - "errors" - "io" - "net/http" - - "github.com/apache/trafficcontrol/lib/go-atscfg" - "github.com/apache/trafficcontrol/lib/go-rfc" - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ats" -) - -func GetServerConfigRemap(w http.ResponseWriter, r *http.Request) { - inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"server-name-or-id"}, nil) - if userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) - return - } - defer inf.Close() - - serverName, userErr, sysErr, errCode := ats.GetServerNameFromNameOrID(inf.Tx.Tx, inf.Params["server-name-or-id"]) - if userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) - return - } - - toToolName, toURL, err := ats.GetToolNameAndURL(inf.Tx.Tx) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting tool name and url: "+err.Error())) - return - } - - atsMajorVersion, err := ats.GetATSMajorVersionFromServerName(inf.Tx.Tx, serverName) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting ATS major version: "+err.Error())) - return - } - - cacheURLConfigParams, err := ats.GetServerProfileParamData(inf.Tx.Tx, serverName, "cacheurl.config") - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting cacheurl.config params: "+err.Error())) - return - } - - serverInfo, ok, err := ats.GetServerInfoByHost(inf.Tx.Tx, serverName) - if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("server not found"), nil) - return - } - - remapDSData, err := ats.GetRemapDSData(inf.Tx.Tx, serverInfo) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("Getting remap ds data: "+err.Error())) - return - } - - dsProfilesCacheKeyConfigParams, err := ats.GetProfilesParamData(inf.Tx.Tx, atscfg.DSProfileIDs(remapDSData), "cachekey.config") - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("Getting profiles param data for cachekey: "+err.Error())) - return - } - - serverPackageParamData, err := ats.GetServerParamData(inf.Tx.Tx, int(serverInfo.ProfileID), "package", serverInfo.HostName, serverInfo.DomainName) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("Getting server package param data: "+err.Error())) - return - } - - txt := atscfg.MakeRemapDotConfig(serverName, toToolName, toURL, atsMajorVersion, cacheURLConfigParams, dsProfilesCacheKeyConfigParams, serverPackageParamData, serverInfo, remapDSData) - - w.Header().Set(rfc.ContentType, rfc.ContentTypeTextPlain) - io.WriteString(w, txt) -} diff --git a/traffic_ops/traffic_ops_golang/ats/atsserver/unknown.go b/traffic_ops/traffic_ops_golang/ats/atsserver/unknown.go index 0a578cf58d..e77582b2ea 100644 --- a/traffic_ops/traffic_ops_golang/ats/atsserver/unknown.go +++ b/traffic_ops/traffic_ops_golang/ats/atsserver/unknown.go @@ -52,7 +52,7 @@ func GetUnknown(w http.ResponseWriter, r *http.Request) { return } - params, err := GetServerParams(inf.Tx.Tx, serverName, inf.Params["file"]) + params, err := ats.GetServerParams(inf.Tx.Tx, serverName, inf.Params["file"]) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server '"+string(serverName)+"' + package parameters: "+err.Error())) return diff --git a/traffic_ops/traffic_ops_golang/ats/db.go b/traffic_ops/traffic_ops_golang/ats/db.go index 3df87878dd..95062ef632 100644 --- a/traffic_ops/traffic_ops_golang/ats/db.go +++ b/traffic_ops/traffic_ops_golang/ats/db.go @@ -37,6 +37,38 @@ import ( // RemapDotConfigIncludeInactiveDeliveryServices is whether delivery services with 'active' false are included in the remap.config. const RemapDotConfigIncludeInactiveDeliveryServices = true +func GetServerParams(tx *sql.Tx, serverName tc.CacheName, configFile string) (map[string][]string, error) { + qry := ` + SELECT + pa.name, + pa.value + FROM + parameter pa + JOIN profile_parameter pp ON pp.parameter = pa.id + JOIN profile pr ON pr.id = pp.profile + JOIN server s ON s.profile = pr.id + WHERE + s.host_name = $1 + AND pa.config_file = $2 + ` + rows, err := tx.Query(qry, serverName, configFile) + if err != nil { + return nil, errors.New("querying: " + err.Error()) + } + defer rows.Close() + + params := map[string][]string{} + for rows.Next() { + name := "" + val := "" + if err := rows.Scan(&name, &val); err != nil { + return nil, errors.New("scanning: " + err.Error()) + } + params[name] = append(params[name], val) + } + return params, nil +} + // GetProfilesParamData returns a map[profileID][paramName]paramVal func GetProfilesParamData(tx *sql.Tx, profileIDs []int, configFile string) (map[int]map[string]string, error) { qry := ` @@ -541,7 +573,7 @@ func GetServerNameFromID(tx *sql.Tx, id int) (tc.CacheName, bool, error) { // GetServerNameFromNameOrID returns the server name from a parameter which may be the name or ID. // This also checks and verifies the existence of the given server, and returns an appropriate user error if it doesn't exist. // Returns the name, any user error, any system error, and any error code. -func GetServerNameFromNameOrID(tx *sql.Tx, serverNameOrID string) (tc.CacheName, error, error, int) { +func GetServerNameFromNameOrID(tx *sql.Tx, serverNameOrID string) (string, error, error, int) { if serverID, err := strconv.Atoi(serverNameOrID); err == nil { serverName, ok, err := dbhelpers.GetServerNameFromID(tx, serverID) if err != nil { @@ -549,37 +581,51 @@ func GetServerNameFromNameOrID(tx *sql.Tx, serverNameOrID string) (tc.CacheName, } else if !ok { return "", errors.New("server not found"), nil, http.StatusNotFound } - return tc.CacheName(serverName), nil, nil, http.StatusOK + return serverName, nil, nil, http.StatusOK } - serverName := tc.CacheName(serverNameOrID) - if _, ok, err := dbhelpers.GetServerIDFromName(string(serverName), tx); err != nil { - return "", nil, fmt.Errorf("checking server name '%v' existence: %v", serverName, err), http.StatusInternalServerError + if _, ok, err := dbhelpers.GetServerIDFromName(serverNameOrID, tx); err != nil { + return "", nil, fmt.Errorf("checking server name '%v' existence: %v", serverNameOrID, err), http.StatusInternalServerError } else if !ok { return "", errors.New("server not found"), nil, http.StatusNotFound } - return serverName, nil, nil, http.StatusOK + return serverNameOrID, nil, nil, http.StatusOK } // GetServerInfoByID returns the necessary info about the server, whether the server exists, and any error. func GetServerInfoByID(tx *sql.Tx, id int) (*atscfg.ServerInfo, bool, error) { - return getServerInfo(tx, ServerInfoQuery()+`WHERE s.id = $1`, []interface{}{id}) + return GetServerInfo(tx, ServerInfoQuery()+`WHERE s.id = $1`, []interface{}{id}) } // GetServerInfoByHost returns the necessary info about the server, whether the server exists, and any error. -func GetServerInfoByHost(tx *sql.Tx, host tc.CacheName) (*atscfg.ServerInfo, bool, error) { - return getServerInfo(tx, ServerInfoQuery()+` WHERE s.host_name = $1 `, []interface{}{host}) +func GetServerInfoByHost(tx *sql.Tx, host string) (*atscfg.ServerInfo, bool, error) { + return GetServerInfo(tx, ServerInfoQuery()+` WHERE s.host_name = $1 `, []interface{}{host}) } // getServerInfo returns the necessary info about the server, whether the server exists, and any error. -func getServerInfo(tx *sql.Tx, qry string, qryParams []interface{}) (*atscfg.ServerInfo, bool, error) { +func GetServerInfo(tx *sql.Tx, qry string, qryParams []interface{}) (*atscfg.ServerInfo, bool, error) { s := atscfg.ServerInfo{} - if err := tx.QueryRow(qry, qryParams...).Scan(&s.CDN, &s.CDNID, &s.ID, &s.HostName, &s.DomainName, &s.IP, &s.ProfileID, &s.ProfileName, &s.Port, &s.HTTPSPort, &s.Type, &s.CacheGroupID, &s.ParentCacheGroupID, &s.SecondaryParentCacheGroupID, &s.ParentCacheGroupType, &s.SecondaryParentCacheGroupType); err != nil { + if err := tx.QueryRow(qry, qryParams...).Scan(&s.CDN, &s.CDNID, &s.ID, &s.HostName, &s.DomainName, &s.ProfileID, &s.ProfileName, &s.Port, &s.HTTPSPort, &s.Type, &s.CacheGroupID, &s.ParentCacheGroupID, &s.SecondaryParentCacheGroupID, &s.ParentCacheGroupType, &s.SecondaryParentCacheGroupType); err != nil { if err == sql.ErrNoRows { return nil, false, nil } return nil, false, errors.New("querying server info: " + err.Error()) } + + infs, err := dbhelpers.GetServerInterfaces(s.ID, tx) + if err != nil { + return nil, false, fmt.Errorf("querying server info interfaces: %v", err) + } + + legacyInfo, err := tc.InterfaceInfoToLegacyInterfaces(infs) + if err != nil { + return nil, false, fmt.Errorf("converting server info interfaces to legacy: %v", err) + } + + if legacyInfo.IPAddress != nil { + s.IP = *legacyInfo.IPAddress + } + return &s, true, nil } @@ -591,7 +637,6 @@ SELECT s.id, s.host_name, c.domain_name, - s.ip_address, s.profile AS profile_id, p.name AS profile_name, s.tcp_port, diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go index 0ce7be9c88..8ee6c28ead 100644 --- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go +++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go @@ -519,6 +519,52 @@ WHERE s.id = $1 return row, true, nil } +// GetServerInterfaces, given the ID of a server, returns all of its network +// interfaces, or an error if one occurs during retrieval. +func GetServerInterfaces(id int, tx *sql.Tx) ([]tc.ServerInterfaceInfo, error) { + q := ` + SELECT ( + json_build_object ( + 'ipAddresses', + ARRAY ( + SELECT ( + json_build_object ( + 'address', ip_address.address, + 'gateway', ip_address.gateway, + 'service_address', ip_address.service_address + ) + ) + FROM ip_address + WHERE ip_address.interface = interface.name + AND ip_address.server = $1 + ), + 'max_bandwidth', interface.max_bandwidth, + 'monitor', interface.monitor, + 'mtu', interface.mtu, + 'name', interface.name + ) + ) + FROM interface + WHERE interface.server = $1 + ` + rows, err := tx.Query(q, id) + if err != nil { + return nil, err + } + defer rows.Close() + + infs := []tc.ServerInterfaceInfo{} + for rows.Next() { + var inf tc.ServerInterfaceInfo + if err = rows.Scan(&inf); err != nil { + return nil, err + } + infs = append(infs, inf) + } + + return infs, nil +} + // GetStatusByID returns a Status struct, a bool for whether or not a status of the given ID exists, and an error (if one occurs). func GetStatusByID(id int, tx *sql.Tx) (tc.StatusNullable, bool, error) { q := ` diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index 1763f719df..4ab9f0a2a1 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -319,10 +319,10 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{3, 0}, http.MethodPost, `servers/{id-or-name}/update$`, server.UpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 143813233, noPerlBypass}, //Server: CRUD - {api.Version{3, 0}, http.MethodGet, `servers/?$`, api.ReadHandler(&server.TOServer{}), auth.PrivLevelReadOnly, Authenticated, nil, 27209592853, noPerlBypass}, - {api.Version{3, 0}, http.MethodPut, `servers/{id}$`, api.UpdateHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 2586341033, noPerlBypass}, - {api.Version{3, 0}, http.MethodPost, `servers/?$`, api.CreateHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 22255580613, noPerlBypass}, - {api.Version{3, 0}, http.MethodDelete, `servers/{id}$`, api.DeleteHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 2923222333, noPerlBypass}, + {api.Version{3, 0}, http.MethodGet, `servers/?$`, server.Read, auth.PrivLevelReadOnly, Authenticated, nil, 27209592853, noPerlBypass}, + {api.Version{3, 0}, http.MethodPut, `servers/{id}$`, server.Update, auth.PrivLevelOperations, Authenticated, nil, 2586341033, noPerlBypass}, + {api.Version{3, 0}, http.MethodPost, `servers/?$`, server.Create, auth.PrivLevelOperations, Authenticated, nil, 22255580613, noPerlBypass}, + {api.Version{3, 0}, http.MethodDelete, `servers/{id}$`, server.Delete, auth.PrivLevelOperations, Authenticated, nil, 2923222333, noPerlBypass}, //Server Capability {api.Version{3, 0}, http.MethodGet, `server_capabilities$`, api.ReadHandler(&servercapability.TOServerCapability{}), auth.PrivLevelReadOnly, Authenticated, nil, 2104073913, noPerlBypass}, @@ -683,10 +683,10 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{2, 0}, http.MethodPost, `servers/{id-or-name}/update$`, server.UpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 14381323, noPerlBypass}, //Server: CRUD - {api.Version{2, 0}, http.MethodGet, `servers/?$`, api.ReadHandler(&server.TOServer{}), auth.PrivLevelReadOnly, Authenticated, nil, 2720959285, noPerlBypass}, - {api.Version{2, 0}, http.MethodPut, `servers/{id}$`, api.UpdateHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 258634103, noPerlBypass}, - {api.Version{2, 0}, http.MethodPost, `servers/?$`, api.CreateHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 2225558061, noPerlBypass}, - {api.Version{2, 0}, http.MethodDelete, `servers/{id}$`, api.DeleteHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 292322233, noPerlBypass}, + {api.Version{2, 0}, http.MethodGet, `servers/?$`, server.Read, auth.PrivLevelReadOnly, Authenticated, nil, 2720959285, noPerlBypass}, + {api.Version{2, 0}, http.MethodPut, `servers/{id}$`, server.Update, auth.PrivLevelOperations, Authenticated, nil, 258634103, noPerlBypass}, + {api.Version{2, 0}, http.MethodPost, `servers/?$`, server.Create, auth.PrivLevelOperations, Authenticated, nil, 2225558061, noPerlBypass}, + {api.Version{2, 0}, http.MethodDelete, `servers/{id}$`, server.Delete, auth.PrivLevelOperations, Authenticated, nil, 292322233, noPerlBypass}, //Server Capability {api.Version{2, 0}, http.MethodGet, `server_capabilities$`, api.ReadHandler(&servercapability.TOServerCapability{}), auth.PrivLevelReadOnly, Authenticated, nil, 210407391, noPerlBypass}, @@ -1080,11 +1080,11 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{1, 3}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 438451599, noPerlBypass}, //Server: CRUD - {api.Version{1, 1}, http.MethodGet, `servers/?(\.json)?$`, api.ReadHandler(&server.TOServer{}), auth.PrivLevelReadOnly, Authenticated, nil, 1720959285, noPerlBypass}, - {api.Version{1, 1}, http.MethodGet, `servers/{id}$`, api.DeprecatedReadHandler(&server.TOServer{}, util.StrPtr("GET /servers with query parameter id")), auth.PrivLevelReadOnly, Authenticated, nil, 1543122028, noPerlBypass}, - {api.Version{1, 1}, http.MethodPut, `servers/{id}$`, api.UpdateHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 958634103, noPerlBypass}, - {api.Version{1, 1}, http.MethodPost, `servers/?$`, api.CreateHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 2025558061, noPerlBypass}, - {api.Version{1, 1}, http.MethodDelete, `servers/{id}$`, api.DeleteHandler(&server.TOServer{}), auth.PrivLevelOperations, Authenticated, nil, 192322233, noPerlBypass}, + {api.Version{1, 1}, http.MethodGet, `servers/?(\.json)?$`, server.Read, auth.PrivLevelReadOnly, Authenticated, nil, 1720959285, noPerlBypass}, + {api.Version{1, 1}, http.MethodGet, `servers/{id}$`, server.ReadID, auth.PrivLevelReadOnly, Authenticated, nil, 1543122028, noPerlBypass}, + {api.Version{1, 1}, http.MethodPut, `servers/{id}$`, server.Update, auth.PrivLevelOperations, Authenticated, nil, 958634103, noPerlBypass}, + {api.Version{1, 1}, http.MethodPost, `servers/?$`, server.Create, auth.PrivLevelOperations, Authenticated, nil, 2025558061, noPerlBypass}, + {api.Version{1, 1}, http.MethodDelete, `servers/{id}$`, server.Delete, auth.PrivLevelOperations, Authenticated, nil, 192322233, noPerlBypass}, //Server Capability {api.Version{1, 4}, http.MethodGet, `server_capabilities$`, api.ReadHandler(&servercapability.TOServerCapability{}), auth.PrivLevelReadOnly, Authenticated, nil, 610407391, noPerlBypass}, @@ -1251,11 +1251,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{1, 1}, http.MethodGet, `profiles/{profile-name-or-id}/configfiles/ats/volume\.config/?$`, atsprofile.GetVolume, auth.PrivLevelOperations, Authenticated, nil, 792704719, perlBypass}, {api.Version{1, 1}, http.MethodGet, `profiles/{profile-name-or-id}/configfiles/ats/{file}/?$`, atsprofile.GetUnknown, auth.PrivLevelOperations, Authenticated, nil, 1651257268, perlBypass}, - {api.Version{1, 1}, http.MethodGet, `servers/{server-name-or-id}/configfiles/ats/parent\.config/?(\.json)?$`, atsserver.GetParentDotConfig, auth.PrivLevelOperations, Authenticated, nil, 645056066, perlBypass}, - {api.Version{1, 1}, http.MethodGet, `servers/{server-name-or-id}/configfiles/ats/remap\.config/?(\.json)?$`, atsserver.GetServerConfigRemap, auth.PrivLevelOperations, Authenticated, nil, 2038454899, perlBypass}, - {api.Version{1, 1}, http.MethodGet, `servers/{id-or-host}/configfiles/ats/cache\.config/?(\.json)?$`, atsserver.GetCacheDotConfig, auth.PrivLevelOperations, Authenticated, nil, 34686861, perlBypass}, - {api.Version{1, 1}, http.MethodGet, `servers/{id-or-host}/configfiles/ats/ip_allow\.config/?(\.json)?$`, atsserver.GetIPAllowDotConfig, auth.PrivLevelOperations, Authenticated, nil, 724651786, perlBypass}, {api.Version{1, 1}, http.MethodGet, `servers/{id-or-host}/configfiles/ats/hosting\.config/?(\.json)?$`, atsserver.GetHostingDotConfig, auth.PrivLevelOperations, Authenticated, nil, 1387459113, perlBypass}, {api.Version{1, 1}, http.MethodGet, `servers/{id-or-host}/configfiles/ats/packages/?(\.json)?$`, atsserver.GetPackages, auth.PrivLevelOperations, Authenticated, nil, 245024839, perlBypass}, {api.Version{1, 1}, http.MethodGet, `servers/{id-or-host}/configfiles/ats/chkconfig/?(\.json)?$`, atsserver.GetChkconfig, auth.PrivLevelOperations, Authenticated, nil, 1012457987, perlBypass}, diff --git a/traffic_ops/traffic_ops_golang/server/detail.go b/traffic_ops/traffic_ops_golang/server/detail.go index 4faea0b411..a80f83c1e2 100644 --- a/traffic_ops/traffic_ops_golang/server/detail.go +++ b/traffic_ops/traffic_ops_golang/server/detail.go @@ -141,40 +141,40 @@ func GetDetailParamHandler(w http.ResponseWriter, r *http.Request) { func getDetailServers(tx *sql.Tx, user *auth.CurrentUser, hostName string, physLocationID int, orderBy string, limit int, reqVersion api.Version) ([]tc.ServerDetailV30, error) { allowedOrderByCols := map[string]string{ "": "", - "cachegroup": "s.cachegroup", + "cachegroup": "server.cachegroup", "cdn_name": "cdn.name", - "domain_name": "s.domain_name", - "guid": "s.guid", - "host_name": "s.host_name", - "https_port": "s.https_port", - "id": "s.id", - "ilo_ip_address": "s.ilo_ip_address", - "ilo_ip_gateway": "s.ilo_ip_gateway", - "ilo_ip_netmask": "s.ilo_ip_netmask", - "ilo_password": "s.ilo_password", - "ilo_username": "s.ilo_username", + "domain_name": "server.domain_name", + "guid": "server.guid", + "host_name": "server.host_name", + "https_port": "server.https_port", + "id": "server.id", + "ilo_ip_address": "server.ilo_ip_address", + "ilo_ip_gateway": "server.ilo_ip_gateway", + "ilo_ip_netmask": "server.ilo_ip_netmask", + "ilo_password": "server.ilo_password", + "ilo_username": "server.ilo_username", "interface_mtu": "interface_mtu", - "interface_name": "s.interface_name", - "ip6_address": "s.ip6_address", - "ip6_gateway": "s.ip6_gateway", - "ip_address": "s.ip_address", - "ip_gateway": "s.ip_gateway", - "ip_netmask": "s.ip_netmask", - "mgmt_ip_address": "s.mgmt_ip_address", - "mgmt_ip_gateway": "s.mgmt_ip_gateway", - "mgmt_ip_netmask": "s.mgmt_ip_netmask", - "offline_reason": "s.offline_reason", + "interface_name": "server.interface_name", + "ip6_address": "server.ip6_address", + "ip6_gateway": "server.ip6_gateway", + "ip_address": "server.ip_address", + "ip_gateway": "server.ip_gateway", + "ip_netmask": "server.ip_netmask", + "mgmt_ip_address": "server.mgmt_ip_address", + "mgmt_ip_gateway": "server.mgmt_ip_gateway", + "mgmt_ip_netmask": "server.mgmt_ip_netmask", + "offline_reason": "server.offline_reason", "phys_location": "pl.name", "profile": "p.name", "profile_desc": "p.description", - "rack": "s.rack", - "router_host_name": "s.router_host_name", - "router_port_name": "s.router_port_name", + "rack": "server.rack", + "router_host_name": "server.router_host_name", + "router_port_name": "server.router_port_name", "status": "st.name", - "tcp_port": "s.tcp_port", + "tcp_port": "server.tcp_port", "server_type": "t.name", - "xmpp_id": "s.xmpp_id", - "xmpp_passwd": "s.xmpp_passwd", + "xmpp_id": "server.xmpp_id", + "xmpp_passwd": "server.xmpp_passwd", } orderBy, ok := allowedOrderByCols[orderBy] if !ok { @@ -182,62 +182,43 @@ func getDetailServers(tx *sql.Tx, user *auth.CurrentUser, hostName string, physL } const JumboFrameBPS = 9000 q := ` -SELECT +SELECT cg.name AS cachegroup, cdn.name AS cdn_name, - ARRAY(select deliveryservice from deliveryservice_server where server = s.id), - s.domain_name, - s.guid, - s.host_name, - s.https_port, - s.id, - s.ilo_ip_address, - s.ilo_ip_gateway, - s.ilo_ip_netmask, - s.ilo_password, - s.ilo_username, - ARRAY ( - SELECT ( json_build_object ( - 'ipAddresses', ARRAY ( - SELECT ( json_build_object ( - 'address', ip_address.address, - 'gateway', ip_address.gateway, - 'service_address', ip_address.service_address - )) - FROM ip_address - WHERE ip_address.interface = interface.name - AND ip_address.server = s.id - ), - 'max_bandwidth', interface.max_bandwidth, - 'monitor', interface.monitor, - 'mtu', COALESCE (interface.mtu, 9000), - 'name', interface.name - )) - FROM interface - WHERE interface.server = s.id - ) AS interfaces, - s.mgmt_ip_address, - s.mgmt_ip_gateway, - s.mgmt_ip_netmask, - s.offline_reason, + ARRAY(select deliveryservice from deliveryservice_server where server = server.id), + server.domain_name, + server.guid, + server.host_name, + server.https_port, + server.id, + server.ilo_ip_address, + server.ilo_ip_gateway, + server.ilo_ip_netmask, + server.ilo_password, + server.ilo_username, + ` + InterfacesArray + ` AS interfaces, + server.mgmt_ip_address, + server.mgmt_ip_gateway, + server.mgmt_ip_netmask, + server.offline_reason, pl.name as phys_location, p.name as profile, p.description as profile_desc, - s.rack, - s.router_host_name, - s.router_port_name, + server.rack, + server.router_host_name, + server.router_port_name, st.name as status, - s.tcp_port, + server.tcp_port, t.name as server_type, - s.xmpp_id, - s.xmpp_passwd -FROM server AS s -JOIN cachegroup cg ON s.cachegroup = cg.id -JOIN cdn ON s.cdn_id = cdn.id -JOIN phys_location pl ON s.phys_location = pl.id -JOIN profile p ON s.profile = p.id -JOIN status st ON s.status = st.id -JOIN type t ON s.type = t.id + server.xmpp_id, + server.xmpp_passwd +FROM server +JOIN cachegroup cg ON server.cachegroup = cg.id +JOIN cdn ON server.cdn_id = cdn.id +JOIN phys_location pl ON server.phys_location = pl.id +JOIN profile p ON server.profile = p.id +JOIN status st ON server.status = st.id +JOIN type t ON server.type = t.id ` limitStr := "" if limit != 0 { @@ -250,13 +231,13 @@ JOIN type t ON s.type = t.id rows := (*sql.Rows)(nil) err := error(nil) if hostName != "" && physLocationID != -1 { - q += ` WHERE s.host_name = $1::text AND s.phys_location = $2::bigint` + orderByStr + limitStr + q += ` WHERE server.host_name = $1::text AND server.phys_location = $2::bigint` + orderByStr + limitStr rows, err = tx.Query(q, hostName, physLocationID) } else if hostName != "" { - q += ` WHERE s.host_name = $1::text` + orderByStr + limitStr + q += ` WHERE server.host_name = $1::text` + orderByStr + limitStr rows, err = tx.Query(q, hostName) } else if physLocationID != -1 { - q += ` WHERE s.phys_location = $1::int` + orderByStr + limitStr + q += ` WHERE server.phys_location = $1::int` + orderByStr + limitStr rows, err = tx.Query(q, physLocationID) } else { q += orderByStr + limitStr diff --git a/traffic_ops/traffic_ops_golang/server/detail_test.go b/traffic_ops/traffic_ops_golang/server/detail_test.go index d88b4b577b..3b937f8d8d 100644 --- a/traffic_ops/traffic_ops_golang/server/detail_test.go +++ b/traffic_ops/traffic_ops_golang/server/detail_test.go @@ -82,7 +82,7 @@ func TestGetDetailServers(t *testing.T) { sd.ILOIPNetmask, sd.ILOPassword, sd.ILOUsername, - []byte(`{"{\"ipAddresses\" : [{\"address\" : \"127.0.0.0\", \"gateway\" : null, \"service_address\" : true}], \"max_bandwidth\" : null, \"monitor\" : true, \"mtu\" : 1500, \"name\" : \"eth0\"}"}`), + []byte(`{"{\"ipAddresses\" : [{\"address\" : \"127.0.0.0\", \"gateway\" : null, \"serviceAddress\" : true}], \"max_bandwidth\" : null, \"monitor\" : true, \"mtu\" : 1500, \"name\" : \"eth0\"}"}`), sd.MgmtIPAddress, sd.MgmtIPGateway, sd.MgmtIPNetmask, @@ -126,16 +126,16 @@ func TestGetDetailServers(t *testing.T) { t.Fatalf("servers.read expected len(srvInts) == 1, actual = %v", len(srvInts)) } - if len(srvInts[0].IpAddresses) != 1 { - t.Fatalf("servers.read expected len(srvInts[0].IpAddresses) == 1, actual = %v", len(srvInts[0].IpAddresses)) + if len(srvInts[0].IPAddresses) != 1 { + t.Fatalf("servers.read expected len(srvInts[0].IpAddresses) == 1, actual = %v", len(srvInts[0].IPAddresses)) } if len(actualSrvs[0].HardwareInfo) != 3 { t.Fatalf("servers.read expected len(actualSrvs[0].HardwareInfo) == 3, actual = %v", len(actualSrvs[0].HardwareInfo)) } - if !srvInts[0].IpAddresses[0].ServiceAddress { - t.Fatalf("srvInts[0].IpAddresses[0].ServiceAddress expected to be true, actual = %v", srvInts[0].IpAddresses[0].ServiceAddress) + if !srvInts[0].IPAddresses[0].ServiceAddress { + t.Fatalf("srvInts[0].IpAddresses[0].ServiceAddress expected to be true, actual = %v", srvInts[0].IPAddresses[0].ServiceAddress) } } diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go index 1ed79680d4..8d72737016 100644 --- a/traffic_ops/traffic_ops_golang/server/servers.go +++ b/traffic_ops/traffic_ops_golang/server/servers.go @@ -23,8 +23,10 @@ package server import ( "database/sql" + "encoding/json" "errors" "fmt" + "net" "net/http" "strconv" "strings" @@ -33,95 +35,273 @@ import ( "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/lib/go-tc/tovalidate" "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/routing/middleware" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant" - validation "github.com/go-ozzo/ozzo-validation" + + "github.com/go-ozzo/ozzo-validation" "github.com/go-ozzo/ozzo-validation/is" "github.com/jmoiron/sqlx" "github.com/lib/pq" ) -// TOServer combines data about a server with metadata from an API request and -// provides methods that implement several interfaces from the api package. -type TOServer struct { - api.APIInfoImpl `json:"-"` - tc.ServerNullable -} - -func (s *TOServer) SetLastUpdated(t tc.TimeNoMod) { s.LastUpdated = &t } -func (*TOServer) InsertQuery() string { return insertQuery() } -func (*TOServer) UpdateQuery() string { return updateQuery() } -func (*TOServer) DeleteQuery() string { return deleteQuery() } - -func (TOServer) GetKeyFieldsInfo() []api.KeyFieldInfo { - return []api.KeyFieldInfo{{"id", api.GetIntKey}} -} +const serverCountQuery = ` +SELECT COUNT(s.id) +FROM server AS s +` -func (s TOServer) GetKeys() (map[string]interface{}, bool) { - if s.ID == nil { - return map[string]interface{}{"id": 0}, false - } - return map[string]interface{}{"id": *s.ID}, true -} +const selectQuery = ` +SELECT + cg.name AS cachegroup, + s.cachegroup AS cachegroup_id, + s.cdn_id, + cdn.name AS cdn_name, + s.domain_name, + s.guid, + s.host_name, + s.https_port, + s.id, + s.ilo_ip_address, + s.ilo_ip_gateway, + s.ilo_ip_netmask, + s.ilo_password, + s.ilo_username, + s.last_updated, + s.mgmt_ip_address, + s.mgmt_ip_gateway, + s.mgmt_ip_netmask, + s.offline_reason, + pl.name AS phys_location, + s.phys_location AS phys_location_id, + p.name AS profile, + p.description AS profile_desc, + s.profile AS profile_id, + s.rack, + s.reval_pending, + s.router_host_name, + s.router_port_name, + st.name AS status, + s.status AS status_id, + s.tcp_port, + t.name AS server_type, + s.type AS server_type_id, + s.upd_pending AS upd_pending, + s.xmpp_id, + s.xmpp_passwd +FROM server AS s +JOIN cachegroup cg ON s.cachegroup = cg.id +JOIN cdn cdn ON s.cdn_id = cdn.id +JOIN phys_location pl ON s.phys_location = pl.id +JOIN profile p ON s.profile = p.id +JOIN status st ON s.status = st.id +JOIN type t ON s.type = t.id +` +const InterfacesArray = ` +ARRAY ( SELECT ( + json_build_object ( + 'ipAddresses', + ARRAY ( + SELECT ( + json_build_object ( + 'address', ip_address.address, + 'gateway', ip_address.gateway, + 'serviceAddress', ip_address.service_address + ) + ) + FROM ip_address + WHERE ip_address.interface = interface.name + AND ip_address.server = server.id + ), + 'maxBandwidth', interface.max_bandwidth, + 'monitor', interface.monitor, + 'mtu', interface.mtu, + 'name', interface.name + ) + ) + FROM interface + WHERE interface.server = server.id +)` +const SelectInterfacesQuery = ` +SELECT ( ` + InterfacesArray + ` + ) AS interfaces, +server.id +FROM server +WHERE server.id = ANY ($1) +` -func (s *TOServer) SetKeys(keys map[string]interface{}) { - i, _ := keys["id"].(int) //this utilizes the non panicking type assertion, if the thrown away ok variable is false i will be the zero of the type, 0 here. - s.ID = &i -} +const insertQuery = ` +INSERT INTO server ( + cachegroup, + cdn_id, + domain_name, + host_name, + https_port, + ilo_ip_address, + ilo_ip_netmask, + ilo_ip_gateway, + ilo_username, + ilo_password, + interface_name, + mgmt_ip_address, + mgmt_ip_netmask, + mgmt_ip_gateway, + offline_reason, + phys_location, + profile, + rack, + router_host_name, + router_port_name, + status, + tcp_port, + type, + upd_pending, + xmpp_id, + xmpp_passwd +) VALUES ( + :cachegroup_id, + :cdn_id, + :domain_name, + :host_name, + :https_port, + :ilo_ip_address, + :ilo_ip_netmask, + :ilo_ip_gateway, + :ilo_username, + :ilo_password, + :interface_name, + :mgmt_ip_address, + :mgmt_ip_netmask, + :mgmt_ip_gateway, + :offline_reason, + :phys_location_id, + :profile_id, + :rack, + :router_host_name, + :router_port_name, + :status_id, + :tcp_port, + :server_type_id, + :upd_pending, + :xmpp_id, + :xmpp_passwd +) RETURNING + (SELECT name FROM cachegroup WHERE cachegroup.id=server.cachegroup) AS cachegroup, + cachegroup AS cachegroup_id, + cdn_id, + (SELECT name FROM cdn WHERE cdn.id=server.cdn_id) AS cdn_name, + domain_name, + guid, + host_name, + https_port, + id, + ilo_ip_address, + ilo_ip_gateway, + ilo_ip_netmask, + ilo_password, + ilo_username, + last_updated, + mgmt_ip_address, + mgmt_ip_gateway, + mgmt_ip_netmask, + offline_reason, + (SELECT name FROM phys_location WHERE phys_location.id=server.phys_location) AS phys_location, + phys_location AS phys_location_id, + profile AS profile_id, + (SELECT description FROM profile WHERE profile.id=server.profile) AS profile_desc, + (SELECT name FROM profile WHERE profile.id=server.profile) AS profile, + rack, + reval_pending, + router_host_name, + router_port_name, + (SELECT name FROM status WHERE status.id=server.status) AS status, + status AS status_id, + tcp_port, + (SELECT name FROM type WHERE type.id=server.type) AS server_type, + type AS server_type_id, + upd_pending +` -func (s *TOServer) GetAuditName() string { - if s.DomainName != nil { - return *s.DomainName - } - if s.ID != nil { - return strconv.Itoa(*s.ID) - } - return "unknown" -} +const updateQuery = ` +UPDATE server SET + cachegroup=:cachegroup_id, + cdn_id=:cdn_id, + domain_name=:domain_name, + host_name=:host_name, + https_port=:https_port, + ilo_ip_address=:ilo_ip_address, + ilo_ip_netmask=:ilo_ip_netmask, + ilo_ip_gateway=:ilo_ip_gateway, + ilo_username=:ilo_username, + ilo_password=:ilo_password, + mgmt_ip_address=:mgmt_ip_address, + mgmt_ip_netmask=:mgmt_ip_netmask, + mgmt_ip_gateway=:mgmt_ip_gateway, + offline_reason=:offline_reason, + phys_location=:phys_location_id, + profile=:profile_id, + rack=:rack, + router_host_name=:router_host_name, + router_port_name=:router_port_name, + status=:status_id, + tcp_port=:tcp_port, + type=:server_type_id, + upd_pending=:upd_pending, + xmpp_id=:xmpp_id, + xmpp_passwd=:xmpp_passwd +WHERE id=:id +RETURNING + (SELECT name FROM cachegroup WHERE cachegroup.id=server.cachegroup) AS cachegroup, + cachegroup AS cachegroup_id, + cdn_id, + (SELECT name FROM cdn WHERE cdn.id=server.cdn_id) AS cdn_name, + domain_name, + guid, + host_name, + https_port, + id, + ilo_ip_address, + ilo_ip_gateway, + ilo_ip_netmask, + ilo_password, + ilo_username, + last_updated, + mgmt_ip_address, + mgmt_ip_gateway, + mgmt_ip_netmask, + offline_reason, + (SELECT name FROM phys_location WHERE phys_location.id=server.phys_location) AS phys_location, + phys_location AS phys_location_id, + profile AS profile_id, + (SELECT description FROM profile WHERE profile.id=server.profile) AS profile_desc, + (SELECT name FROM profile WHERE profile.id=server.profile) AS profile, + rack, + reval_pending, + router_host_name, + router_port_name, + (SELECT name FROM status WHERE status.id=server.status) AS status, + status AS status_id, + tcp_port, + (SELECT name FROM type WHERE type.id=server.type) AS server_type, + type AS server_type_id, + upd_pending +` -func (s *TOServer) GetType() string { - return "server" -} +const deleteServerQuery = `DELETE FROM server WHERE id=$1` +const deleteInterfacesQuery = `DELETE FROM interface WHERE server=$1` +const deleteIPsQuery = `DELETE FROM ip_address WHERE server = $1` -func (s *TOServer) Sanitize() { - if s.IP6Address != nil && *s.IP6Address == "" { - s.IP6Address = nil - } -} +func validateCommon(s *tc.CommonServerProperties, tx *sql.Tx) []error { -func (s *TOServer) Validate() error { - s.Sanitize() - version := s.APIInfo().Version noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces") - errs := []error{} - if (s.IPAddress == nil || *s.IPAddress == "") && (s.IP6Address == nil || *s.IP6Address == "") { - errs = append(errs, tc.NeedsAtLeastOneIPError) - } - - if s.IPIsService != nil && *s.IPIsService && (s.IPAddress == nil || *s.IPAddress == "") { - errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError) - } - - if s.IP6IsService != nil && *s.IP6IsService && (s.IP6Address == nil || *s.IP6Address == "") { - errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError) - } - - if version.Major >= 2 { - if (s.IPIsService == nil || !*s.IPIsService) && (s.IP6IsService == nil || !*s.IP6IsService) { - errs = append(errs, tc.NeedsAtLeastOneServiceAddressError) - } - } - - validateErrs := validation.Errors{ + errs := tovalidate.ToErrors(validation.Errors{ "cachegroupId": validation.Validate(s.CachegroupID, validation.NotNil), "cdnId": validation.Validate(s.CDNID, validation.NotNil), "domainName": validation.Validate(s.DomainName, validation.NotNil, noSpaces), "hostName": validation.Validate(s.HostName, validation.NotNil, noSpaces), - "interfaceMtu": validation.Validate(s.InterfaceMtu, validation.NotNil), - "interfaceName": validation.Validate(s.InterfaceName, validation.NotNil), "physLocationId": validation.Validate(s.PhysLocationID, validation.NotNil), "profileId": validation.Validate(s.ProfileID, validation.NotNil), "statusId": validation.Validate(s.StatusID, validation.NotNil), @@ -129,7 +309,55 @@ func (s *TOServer) Validate() error { "updPending": validation.Validate(s.UpdPending, validation.NotNil), "httpsPort": validation.Validate(s.HTTPSPort, validation.By(tovalidate.IsValidPortNumber)), "tcpPort": validation.Validate(s.TCPPort, validation.By(tovalidate.IsValidPortNumber)), + }) + + if len(errs) > 0 { + return errs } + + if s.XMPPID == nil || *s.XMPPID == "" { + hostName := *s.HostName + s.XMPPID = &hostName + } + + if _, err := tc.ValidateTypeID(tx, s.TypeID, "server"); err != nil { + errs = append(errs, err) + } + + var cdnID int + if err := tx.QueryRow("SELECT cdn from profile WHERE id=$1", s.ProfileID).Scan(&cdnID); err != nil { + log.Errorf("could not execute select cdnID from profile: %s\n", err) + if err == sql.ErrNoRows { + errs = append(errs, errors.New("associated profile must have a cdn associated")) + } else { + errs = append(errs, tc.DBError) + } + return errs + } + + log.Infof("got cdn id: %d from profile and cdn id: %d from server", cdnID, *s.CDNID) + if cdnID != *s.CDNID { + errs = append(errs, fmt.Errorf("CDN id '%d' for profile '%d' does not match Server CDN '%d'", cdnID, *s.ProfileID, *s.CDNID)) + } + + return errs +} + +func validateV1(s *tc.ServerNullableV11, tx *sql.Tx) error { + if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 { + s.IP6Address = nil + } + + errs := []error{} + if (s.IPAddress == nil || *s.IPAddress == "") && s.IP6Address == nil { + errs = append(errs, tc.NeedsAtLeastOneIPError) + } + + validateErrs := validation.Errors{ + "interfaceMtu": validation.Validate(s.InterfaceMtu, validation.NotNil), + "interfaceName": validation.Validate(s.InterfaceName, validation.NotNil), + } + if s.IPAddress != nil && *s.IPAddress != "" { validateErrs["ipAddress"] = validation.Validate(s.IPAddress, is.IPv4) validateErrs["ipNetmask"] = validation.Validate(s.IPNetmask, validation.NotNil) @@ -139,94 +367,212 @@ func (s *TOServer) Validate() error { validateErrs["ip6Address"] = validation.Validate(s.IP6Address, validation.By(tovalidate.IsValidIPv6CIDROrAddress)) } errs = append(errs, tovalidate.ToErrors(validateErrs)...) - if len(errs) > 0 { - return util.JoinErrs(errs) - } + errs = append(errs, validateCommon(&s.CommonServerProperties, tx)...) + + return util.JoinErrs(errs) +} - if _, err := tc.ValidateTypeID(s.ReqInfo.Tx.Tx, s.TypeID, "server"); err != nil { +func validateV2(s *tc.ServerNullableV2, tx *sql.Tx) error { + var errs []error + + if err := validateV1(&s.ServerNullableV11, tx); err != nil { return err } - rows, err := s.ReqInfo.Tx.Tx.Query("select cdn from profile where id=$1", s.ProfileID) - if err != nil { - log.Error.Printf("could not execute select cdnID from profile: %s\n", err) - errs = append(errs, tc.DBError) - return util.JoinErrs(errs) + // default boolean value is false + if s.IPIsService == nil { + s.IPIsService = new(bool) } - defer rows.Close() - var cdnID int - for rows.Next() { - if err := rows.Scan(&cdnID); err != nil { - log.Error.Printf("could not scan cdnID from profile: %s\n", err) - errs = append(errs, errors.New("associated profile must have a cdn associated")) - return util.JoinErrs(errs) - } + if s.IP6IsService == nil { + s.IP6IsService = new(bool) } - log.Infof("got cdn id: %d from profile and cdn id: %d from server", cdnID, *s.CDNID) - if cdnID != *s.CDNID { - errs = append(errs, errors.New(fmt.Sprintf("CDN id '%d' for profile '%d' does not match Server CDN '%d'", cdnID, *s.ProfileID, *s.CDNID))) + + if !*s.IPIsService && !*s.IP6IsService { + errs = append(errs, tc.NeedsAtLeastOneServiceAddressError) } - return util.JoinErrs(errs) -} -// ChangeLogMessage implements the api.ChangeLogger interface for a custom log message -func (s TOServer) ChangeLogMessage(action string) (string, error) { + if *s.IPIsService && s.IPAddress == nil { + errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError) + } - var status string - if s.Status != nil { - status = *s.Status + if *s.IP6IsService && s.IP6Address == nil { + errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError) } + return util.JoinErrs(errs) +} - var hostName string - if s.HostName != nil { - hostName = *s.HostName +func validateMTU(mtu interface{}) error { + m, ok := mtu.(*uint64) + if !ok { + return errors.New("must be an unsigned integer with 64-bit precision") + } + if m == nil { + return nil } - var domainName string - if s.DomainName != nil { - domainName = *s.DomainName + if *m < 1280 { + return errors.New("must be at least 1280") } + return nil +} + +func validateV3(s *tc.ServerNullable, tx *sql.Tx) (string, error) { - var serverID string - if s.ID != nil { - serverID = strconv.Itoa(*s.ID) + if len(s.Interfaces) == 0 { + return "", errors.New("a server must have at least one interface") } + var errs []error + var serviceAddrV4Found bool + var serviceAddrV6Found bool + var serviceInterface string + for _, iface := range s.Interfaces { + + ruleName := fmt.Sprintf("interface '%s' ", iface.Name) + errs = append(errs, tovalidate.ToErrors(validation.Errors{ + ruleName + "name": validation.Validate(iface.Name, validation.Required), + ruleName + "mtu": validation.Validate(iface.MaxBandwidth, validation.By(validateMTU)), + ruleName + "ipAddresses": validation.Validate(iface.IPAddresses, validation.Required), + })...) - message := action + ` ` + status + ` server: { "hostName":"` + hostName + `", "domainName":"` + domainName + `", id:` + serverID + ` }` + for _, addr := range iface.IPAddresses { + ruleName += fmt.Sprintf("address '%s'", addr.Address) - return message, nil + var parsedIP net.IP + var err error + if parsedIP, _, err = net.ParseCIDR(addr.Address); err != nil { + if parsedIP = net.ParseIP(addr.Address); parsedIP == nil { + errs = append(errs, fmt.Errorf("%s: address: %v", ruleName, err)) + continue + } + } + + if addr.Gateway != nil { + if gateway := net.ParseIP(*addr.Gateway); gateway == nil { + errs = append(errs, fmt.Errorf("%s: gateway: could not parse '%s' as a network gateway", ruleName, *addr.Gateway)) + } else if (gateway.To4() == nil && parsedIP.To4() != nil) || (gateway.To4() != nil && parsedIP.To4() == nil) { + errs = append(errs, errors.New(ruleName+": address family mismatch between address and gateway")) + } + } + + if addr.ServiceAddress { + if serviceInterface != "" && serviceInterface != iface.Name { + errs = append(errs, fmt.Errorf("interfaces: both %s and %s interfaces contain service addresses - only one service-address-containing-interface is allowed", serviceInterface, iface.Name)) + } + serviceInterface = iface.Name + if parsedIP.To4() != nil { + if serviceAddrV4Found { + errs = append(errs, fmt.Errorf("interfaces: address '%s' of interface '%s' is marked as a service address, but an IPv4 service address appears earlier in the list", addr.Address, iface.Name)) + } + serviceAddrV4Found = true + } else { + if serviceAddrV6Found { + errs = append(errs, fmt.Errorf("interfaces: address '%s' of interface '%s' is marked as a service address, but an IPv6 service address appears earlier in the list", addr.Address, iface.Name)) + } + serviceAddrV6Found = true + } + } + } + } + + errs = append(errs, validateCommon(&s.CommonServerProperties, tx)...) + return serviceInterface, util.JoinErrs(errs) } -func (s *TOServer) Read() ([]interface{}, error, error, int) { - version := s.APIInfo().Version - if version == nil { - return nil, nil, errors.New("TOServer.Read called with nil API version"), http.StatusInternalServerError +func Read(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + tx := inf.Tx.Tx + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return } + defer inf.Close() - returnable := []interface{}{} + // Middleware should've already handled this, so idk why this is a pointer at all tbh + version := inf.Version + if version == nil { + middleware.NotImplementedHandler().ServeHTTP(w, r) + return + } - servers, userErr, sysErr, errCode := getServers(s.ReqInfo.Params, s.ReqInfo.Tx, s.ReqInfo.User) + servers := []tc.ServerNullable{} + var serverCount uint64 + servers, serverCount, userErr, sysErr, errCode = getServers(inf.Params, inf.Tx, inf.User) if userErr != nil || sysErr != nil { - return nil, userErr, sysErr, errCode + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return } + if version.Major >= 3 { + api.WriteRespWithSummary(w, r, servers, serverCount) + return + } + + if version.Major <= 1 { + legacyServers := make([]tc.ServerNullableV11, 0, len(servers)) + for _, server := range servers { + legacyServer, err := server.ToServerV2() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to legacy format: %v", err)) + return + } + legacyServers = append(legacyServers, legacyServer.ServerNullableV11) + } + api.WriteResp(w, r, legacyServers) + return + } + + legacyServers := make([]tc.ServerNullableV2, 0, len(servers)) for _, server := range servers { - switch { - // NOTE: it's required to handle minor version cases in a descending >= manner - case version.Major >= 2: - returnable = append(returnable, server) - case version.Major == 1 && version.Minor >= 1: - returnable = append(returnable, server.ServerNullableV11) - default: - return nil, nil, fmt.Errorf("TOServer.Read called with invalid API version: %d.%d", version.Major, version.Minor), http.StatusInternalServerError + legacyServer, err := server.ToServerV2() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to legacy format: %v", err)) + return } + legacyServers = append(legacyServers, legacyServer) + } + api.WriteResp(w, r, legacyServers) +} + +func ReadID(w http.ResponseWriter, r *http.Request) { + alternative := "GET /servers with query parameter id" + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"id"}) + tx := inf.Tx.Tx + if userErr != nil || sysErr != nil { + api.HandleDeprecatedErr(w, r, tx, errCode, userErr, sysErr, &alternative) + return + } + defer inf.Close() + + servers := []tc.ServerNullable{} + servers, _, userErr, sysErr, errCode = getServers(inf.Params, inf.Tx, inf.User) + + if len(servers) > 1 { + api.HandleDeprecatedErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("ID '%d' matched more than one server (%d total)", inf.IntParams["id"], len(servers)), &alternative) + return + } + + deprecationAlerts := api.CreateDeprecationAlerts(&alternative) + + // No need to bother converting if there's no data + if len(servers) < 1 { + api.WriteAlertsObj(w, r, http.StatusOK, deprecationAlerts, servers) } - return returnable, nil, nil, http.StatusOK + legacyServers := make([]tc.ServerNullableV11, 0, len(servers)) + for _, server := range servers { + legacyServer, err := server.ToServerV2() + if err != nil { + api.HandleDeprecatedErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to legacy format: %v", err), &alternative) + return + } + legacyServers = append(legacyServers, legacyServer.ServerNullableV11) + } + api.WriteAlertsObj(w, r, http.StatusOK, deprecationAlerts, legacyServers) } -func getServers(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) ([]tc.ServerNullable, error, error, int) { +func getServers(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) ([]tc.ServerNullable, uint64, error, error, int) { + // Query Parameters to Database Query column mappings // see the fields mapped in the SQL query queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{ @@ -248,23 +594,23 @@ func getServers(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) ( // don't allow query on ds outside user's tenant dsID, err := strconv.Atoi(dsIDStr) if err != nil { - return nil, errors.New("dsId must be an integer"), nil, http.StatusNotFound + return nil, 0, errors.New("dsId must be an integer"), nil, http.StatusNotFound } userErr, sysErr, _ := tenant.CheckID(tx.Tx, user, dsID) if userErr != nil || sysErr != nil { - return nil, errors.New("Forbidden"), sysErr, http.StatusForbidden + return nil, 0, errors.New("forbidden"), sysErr, http.StatusForbidden } // only if dsId is part of params: add join on deliveryservice_server table queryAddition = ` -FULL OUTER JOIN deliveryservice_server dss ON dss.server = s.id -` + FULL OUTER JOIN deliveryservice_server dss ON dss.server = s.id + ` // depending on ds type, also need to add mids dsType, exists, err := dbhelpers.GetDeliveryServiceType(dsID, tx.Tx) if err != nil { - return nil, nil, err, http.StatusInternalServerError + return nil, 0, nil, err, http.StatusInternalServerError } if !exists { - return nil, fmt.Errorf("a deliveryservice with id %v was not found", dsID), nil, http.StatusBadRequest + return nil, 0, fmt.Errorf("a deliveryservice with id %v was not found", dsID), nil, http.StatusBadRequest } usesMids = dsType.UsesMidCache() log.Debugf("Servers for ds %d; uses mids? %v\n", dsID, usesMids) @@ -272,47 +618,148 @@ FULL OUTER JOIN deliveryservice_server dss ON dss.server = s.id where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols) if len(errs) > 0 { - return nil, util.JoinErrs(errs), nil, http.StatusBadRequest + return nil, 0, util.JoinErrs(errs), nil, http.StatusBadRequest + } + + // TODO there's probably a cleaner way to do this by preparing a NamedStmt first and using its QueryRow method + var serverCount uint64 + countRows, err := tx.NamedQuery(serverCountQuery+where, queryValues) + if err != nil { + return nil, 0, nil, fmt.Errorf("failed to get servers count: %v", err), http.StatusInternalServerError + } + defer countRows.Close() + rowsAffected := 0 + for countRows.Next() { + if err = countRows.Scan(&serverCount); err != nil { + return nil, 0, nil, fmt.Errorf("failed to read servers count: %v", err), http.StatusInternalServerError + } + rowsAffected++ + } + if rowsAffected != 1 { + return nil, 0, nil, fmt.Errorf("incorrect rows returned for server count, want: 1 got: %v", rowsAffected), http.StatusInternalServerError } - query := selectQuery() + queryAddition + where + orderBy + pagination + query := selectQuery + queryAddition + where + orderBy + pagination log.Debugln("Query is ", query) rows, err := tx.NamedQuery(query, queryValues) if err != nil { - return nil, nil, errors.New("querying: " + err.Error()), http.StatusInternalServerError + return nil, serverCount, nil, errors.New("querying: " + err.Error()), http.StatusInternalServerError } defer rows.Close() - servers := []tc.ServerNullable{} - HiddenField := "********" + servers := make(map[int]tc.ServerNullable) + ids := []int{} for rows.Next() { var s tc.ServerNullable if err = rows.StructScan(&s); err != nil { - return nil, nil, errors.New("getting servers: " + err.Error()), http.StatusInternalServerError + return nil, serverCount, nil, errors.New("getting servers: " + err.Error()), http.StatusInternalServerError } if user.PrivLevel < auth.PrivLevelOperations { s.ILOPassword = &HiddenField s.XMPPPasswd = &HiddenField } - servers = append(servers, s) + + if s.ID == nil { + return nil, serverCount, nil, errors.New("found server with nil ID"), http.StatusInternalServerError + } + if _, ok := servers[*s.ID]; ok { + return nil, serverCount, nil, fmt.Errorf("found more than one server with ID #%d", *s.ID), http.StatusInternalServerError + } + servers[*s.ID] = s + ids = append(ids, *s.ID) } // if ds requested uses mid-tier caches, add those to the list as well if usesMids { - mids, userErr, sysErr, errCode := getMidServers(servers, tx) + midIDs, userErr, sysErr, errCode := getMidServers(ids, servers, tx) log.Debugf("getting mids: %v, %v, %s\n", userErr, sysErr, http.StatusText(errCode)) if userErr != nil || sysErr != nil { - return nil, userErr, sysErr, errCode + return nil, serverCount, userErr, sysErr, errCode + } + ids = append(ids, midIDs...) + } + + if len(ids) < 1 { + return []tc.ServerNullable{}, serverCount, nil, nil, http.StatusOK + } + + query, args, err := sqlx.In(`SELECT max_bandwidth, monitor, mtu, name, server FROM interface WHERE server IN (?)`, ids) + if err != nil { + return nil, serverCount, nil, fmt.Errorf("building interfaces query: %v", err), http.StatusInternalServerError + } + query = tx.Rebind(query) + interfaces := map[int]map[string]tc.ServerInterfaceInfo{} + interfaceRows, err := tx.Queryx(query, args...) + if err != nil { + return nil, serverCount, nil, fmt.Errorf("querying for interfaces: %v", err), http.StatusInternalServerError + } + defer interfaceRows.Close() + + for interfaceRows.Next() { + iface := tc.ServerInterfaceInfo{ + IPAddresses: []tc.ServerIPAddress{}, + } + var server int + + if err = interfaceRows.Scan(&iface.MaxBandwidth, &iface.Monitor, &iface.MTU, &iface.Name, &server); err != nil { + return nil, serverCount, nil, fmt.Errorf("getting server interfaces: %v", err), http.StatusInternalServerError + } + + if _, ok := servers[server]; !ok { + continue + } + + if _, ok := interfaces[server]; !ok { + interfaces[server] = map[string]tc.ServerInterfaceInfo{} + } + interfaces[server][iface.Name] = iface + } + + query, args, err = sqlx.In(`SELECT address, gateway, service_address, server, interface FROM ip_address WHERE server IN (?)`, ids) + if err != nil { + return nil, serverCount, nil, fmt.Errorf("building IP addresses query: %v", err), http.StatusInternalServerError + } + query = tx.Rebind(query) + ipRows, err := tx.Tx.Query(query, args...) + if err != nil { + return nil, serverCount, nil, fmt.Errorf("querying for IP addresses: %v", err), http.StatusInternalServerError + } + defer ipRows.Close() + + for ipRows.Next() { + var ip tc.ServerIPAddress + var server int + var iface string + + if err = ipRows.Scan(&ip.Address, &ip.Gateway, &ip.ServiceAddress, &server, &iface); err != nil { + return nil, serverCount, nil, fmt.Errorf("getting server IP addresses: %v", err), http.StatusInternalServerError + } + + if _, ok := interfaces[server]; !ok { + continue + } + if i, ok := interfaces[server][iface]; !ok { + log.Warnf("IP addresses query returned addresses for an interface that was not found in interfaces query: %s", iface) + } else { + i.IPAddresses = append(i.IPAddresses, ip) + interfaces[server][iface] = i + } + } + + returnable := make([]tc.ServerNullable, 0, len(servers)) + for _, server := range servers { + for _, iface := range interfaces[*server.ID] { + server.Interfaces = append(server.Interfaces, iface) } - servers = append(servers, mids...) + returnable = append(returnable, server) } - return servers, nil, nil, http.StatusOK + return returnable, serverCount, nil, nil, http.StatusOK } // getMidServers gets the mids used by the servers in this DS. @@ -323,303 +770,533 @@ FULL OUTER JOIN deliveryservice_server dss ON dss.server = s.id // pulling the cachegroups of the edges and finding those cachegroups parent // cachegroup... then we see which servers have cachegroup in parent cachegroup // list...that's how we find mids for the ds :) -func getMidServers(servers []tc.ServerNullable, tx *sqlx.Tx) ([]tc.ServerNullable, error, error, int) { - if len(servers) == 0 { +func getMidServers(edgeIDs []int, servers map[int]tc.ServerNullable, tx *sqlx.Tx) ([]int, error, error, int) { + if len(edgeIDs) == 0 { return nil, nil, nil, http.StatusOK } - var ids []string - for _, s := range servers { - ids = append(ids, strconv.Itoa(*s.ID)) - } - edgeIDs := strings.Join(ids, ",") // TODO: include secondary parent? - q := selectQuery() + ` -WHERE t.name = 'MID' AND s.cachegroup IN ( -SELECT cg.parent_cachegroup_id FROM cachegroup AS cg -WHERE cg.id IN ( -SELECT s.cachegroup FROM server AS s -WHERE s.id IN (` + edgeIDs + `))) -` - rows, err := tx.Queryx(q) + q := selectQuery + ` + WHERE t.name = 'MID' AND s.cachegroup IN ( + SELECT cg.parent_cachegroup_id FROM cachegroup AS cg + WHERE cg.id IN ( + SELECT s.cachegroup FROM server AS s + WHERE s.id IN (?))) + ` + + query, args, err := sqlx.In(q, edgeIDs) + if err != nil { + return nil, nil, fmt.Errorf("constructing mid servers query: %v", err), http.StatusInternalServerError + } + query = tx.Rebind(query) + + rows, err := tx.Queryx(query, args...) if err != nil { return nil, err, nil, http.StatusBadRequest } defer rows.Close() - var mids []tc.ServerNullable + ids := []int{} for rows.Next() { var s tc.ServerNullable if err := rows.StructScan(&s); err != nil { log.Error.Printf("could not scan mid servers: %s\n", err) return nil, nil, err, http.StatusInternalServerError } - mids = append(mids, s) - } - return mids, nil, nil, http.StatusOK -} + if s.ID == nil { + return nil, nil, errors.New("found server with nil ID"), http.StatusInternalServerError + } -func (s *TOServer) Update() (error, error, int) { - if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 { - s.IP6Address = nil + // This may mean that the server was caught by other query parameters, + // so not technically an error, unlike earlier in 'getServers'. + if _, ok := servers[*s.ID]; ok { + continue + } + + servers[*s.ID] = s + ids = append(ids, *s.ID) } - // see if type changed - typeID := -1 - // see if cdn changed - cdnId := -1 - if err := s.APIInfo().Tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1", s.ID).Scan(&typeID, &cdnId); err != nil { + return ids, nil, nil, http.StatusOK +} + +func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx) (error, error, int) { + // see if cdn or type changed + var cdnID int + var typeID int + if err := tx.QueryRow("SELECT type, cdn_id FROM server WHERE id = $1", *server.ID).Scan(&typeID, &cdnID); err != nil { if err == sql.ErrNoRows { - return errors.New("no server found with this id"), nil, http.StatusNotFound + return errors.New("no server found with this ID"), nil, http.StatusNotFound } return nil, fmt.Errorf("getting current server type: %v", err), http.StatusInternalServerError } - dsIDs := []int64{} - if err := s.APIInfo().Tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM deliveryservice_server WHERE server = $1)", s.ID).Scan(pq.Array(&dsIDs)); err != nil && err != sql.ErrNoRows { + var dsIDs []int64 + if err := tx.QueryRowx("SELECT ARRAY(SELECT deliveryservice FROM deliveryservice_server WHERE server = $1)", server.ID).Scan(pq.Array(&dsIDs)); err != nil && err != sql.ErrNoRows { return nil, fmt.Errorf("getting server assigned delivery services: %v", err), http.StatusInternalServerError } + // If type is changing ensure it isn't assigned to any DSes. + if typeID != *server.TypeID { + if len(dsIDs) != 0 { + return errors.New("server type can not be updated when it is currently assigned to Delivery Services"), nil, http.StatusConflict + } + } // Check to see if the user is trying to change the CDN of a server, which is already linked with a DS - if cdnId != *s.CDNID && len(dsIDs) != 0 { + if cdnID != *server.CDNID && len(dsIDs) != 0 { return errors.New("server cdn can not be updated when it is currently assigned to delivery services"), nil, http.StatusConflict } - // If type is changing ensure it isn't assigned to any DSes. - if typeID != *s.TypeID { - if len(dsIDs) != 0 { - return errors.New("server type can not be updated when it is currently assigned to delivery services"), nil, http.StatusConflict + + return nil, nil, http.StatusOK +} + +func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx) (error, error, int) { + ifaceQry := ` + INSERT INTO interface ( + max_bandwidth, + monitor, + mtu, + name, + server + ) VALUES + ` + ipQry := ` + INSERT INTO ip_address ( + address, + gateway, + interface, + server, + service_address + ) VALUES + ` + + ifaceQueryParts := make([]string, 0, len(interfaces)) + ipQueryParts := make([]string, 0, len(interfaces)) + ifaceArgs := make([]interface{}, 0, len(interfaces)) + ipArgs := make([]interface{}, 0, len(interfaces)) + for i, iface := range interfaces { + argStart := i * 5 + ifaceQueryParts = append(ifaceQueryParts, fmt.Sprintf("($%d, $%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4, argStart+5)) + ifaceArgs = append(ifaceArgs, iface.MaxBandwidth, iface.Monitor, iface.MTU, iface.Name, id) + for _, ip := range iface.IPAddresses { + argStart = len(ipArgs) + ipQueryParts = append(ipQueryParts, fmt.Sprintf("($%d, $%d, $%d, $%d, $%d)", argStart+1, argStart+2, argStart+3, argStart+4, argStart+5)) + ipArgs = append(ipArgs, ip.Address, ip.Gateway, iface.Name, id, ip.ServiceAddress) } } - current := TOServer{} - err := s.ReqInfo.Tx.QueryRowx(selectV20UpdatesQuery()+` WHERE sv.id=$1`, strconv.Itoa(*s.ID)).StructScan(¤t) + ifaceQry += strings.Join(ifaceQueryParts, ",") + log.Debugf("Inserting interfaces for new server, query is: %s", ifaceQry) + + _, err := tx.Exec(ifaceQry, ifaceArgs...) if err != nil { return api.ParseDBError(err) } - defaultIsService := true - if s.IPIsService == nil { - if current.IPIsService != nil { - s.IPIsService = current.IPIsService - } else { - s.IPIsService = &defaultIsService + + ipQry += strings.Join(ipQueryParts, ",") + log.Debugf("Inserting IP addresses for new server, query is: %s", ipQry) + + _, err = tx.Exec(ipQry, ipArgs...) + if err != nil { + return api.ParseDBError(err) + } + + return nil, nil, http.StatusOK +} + +func deleteInterfaces(id int, tx *sql.Tx) (error, error, int) { + if _, err := tx.Exec(deleteIPsQuery, id); err != nil && err != sql.ErrNoRows { + return api.ParseDBError(err) + } + + if _, err := tx.Exec(deleteInterfacesQuery, id); err != nil && err != sql.ErrNoRows { + return api.ParseDBError(err) + } + + return nil, nil, http.StatusOK +} + +func Update(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"}) + tx := inf.Tx.Tx + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + var server tc.ServerNullableV2 + var interfaces []tc.ServerInterfaceInfo + if inf.Version.Major >= 3 { + var newServer tc.ServerNullable + if err := json.NewDecoder(r.Body).Decode(&newServer); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + serviceInterface, err := validateV3(&newServer, tx) + if err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + server, err = newServer.ToServerV2() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("converting v3 server to v2 for update: %v", err)) + return + } + server.InterfaceName = util.StrPtr(serviceInterface) + interfaces = newServer.Interfaces + } else if inf.Version.Major == 2 { + if err := json.NewDecoder(r.Body).Decode(&server); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + err := validateV2(&server, tx) + if err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + interfaces, err = server.LegacyInterfaceDetails.ToInterfaces(*server.IPIsService, *server.IP6IsService) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("converting server legacy interfaces to interface array: %v", err)) + return + } + } else { + var legacyServer tc.ServerNullableV11 + if err := json.NewDecoder(r.Body).Decode(&legacyServer); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + err := validateV1(&legacyServer, tx) + if err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + interfaces, err = legacyServer.LegacyInterfaceDetails.ToInterfaces(true, true) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("converting server legacy interfaces to interface array: %v", err)) + return + } + server = tc.ServerNullableV2{ + ServerNullableV11: legacyServer, + IPIsService: util.BoolPtr(true), + IP6IsService: util.BoolPtr(true), } } - if s.IP6IsService == nil { - if current.IP6IsService != nil { - s.IP6IsService = current.IP6IsService - } else { - s.IP6IsService = &defaultIsService + + server.ID = new(int) + *server.ID = inf.IntParams["id"] + + if userErr, sysErr, errCode = checkTypeChangeSafety(server.CommonServerProperties, inf.Tx); userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + rows, err := inf.Tx.NamedQuery(updateQuery, server) + if err != nil { + userErr, sysErr, errCode = api.ParseDBError(err) + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + defer rows.Close() + + rowsAffected := 0 + for rows.Next() { + if err := rows.StructScan(&server); err != nil { + api.HandleErr(w, r, tx, http.StatusNotFound, nil, fmt.Errorf("scanning lastUpdated from server insert: %v", err)) + return } + rowsAffected++ + } + + if rowsAffected < 1 { + api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no server found with this id"), nil) + return + } + if rowsAffected > 1 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("update for server #%d affected too many rows (%d)", *server.ID, rowsAffected)) + return + } + + if userErr, sysErr, errCode = deleteInterfaces(inf.IntParams["id"], tx); userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + if userErr, sysErr, errCode = createInterfaces(inf.IntParams["id"], interfaces, tx); userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return } - return api.GenericUpdate(s) + if inf.Version.Major >= 3 { + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated", tc.ServerNullable{CommonServerProperties: server.CommonServerProperties, Interfaces: interfaces}) + } else if inf.Version.Minor <= 1 { + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated", server.ServerNullableV11) + } else { + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated", server) + } + + changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: updated", *server.HostName, *server.DomainName, *server.ID) + api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx) } -func (s *TOServer) Create() (error, error, int) { - // TODO put in Validate() - if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 { - s.IP6Address = nil +func createV1(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) { + var server tc.ServerNullableV11 + + tx := inf.Tx.Tx + + if err := json.NewDecoder(r.Body).Decode(&server); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return } - if s.XMPPID == nil || *s.XMPPID == "" { - hostName := *s.HostName - s.XMPPID = &hostName + + if err := validateV1(&server, tx); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return } - // default the is service field to true if omitted and to upgrade version < 1.4 - defaultIsService := true - if s.IPIsService == nil { - s.IPIsService = &defaultIsService + resultRows, err := inf.Tx.NamedQuery(insertQuery, server) + if err != nil { + userErr, sysErr, errCode := api.ParseDBError(err) + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return } - if s.IP6IsService == nil { - s.IP6IsService = &defaultIsService + defer resultRows.Close() + + rowsAffected := 0 + for resultRows.Next() { + rowsAffected++ + if err := resultRows.StructScan(&server.CommonServerProperties); err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("server create scanning: %v", err)) + return + } + } + if rowsAffected == 0 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("server create: no server was inserted, no id was returned")) + return + } else if rowsAffected > 1 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("too many ids returned from server insert")) } - return api.GenericCreate(s) -} + ifaces, err := server.LegacyInterfaceDetails.ToInterfaces(true, true) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + return + } + + if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces, tx); err != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } -func (s *TOServer) Delete() (error, error, int) { return api.GenericDelete(s) } + alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created") + api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server) -func selectV20UpdatesQuery() string { - return `SELECT -sv.ip_address_is_service, -sv.ip6_address_is_service -FROM - server sv` + changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created", *server.HostName, *server.DomainName, *server.ID) + api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx) } -func selectQuery() string { - const JumboFrameBPS = 9000 - return `SELECT -cg.name as cachegroup, -s.cachegroup as cachegroup_id, -s.cdn_id, -cdn.name as cdn_name, -s.domain_name, -s.guid, -s.host_name, -s.https_port, -s.id, -s.ilo_ip_address, -s.ilo_ip_gateway, -s.ilo_ip_netmask, -s.ilo_password, -s.ilo_username, -COALESCE(s.interface_mtu, ` + strconv.Itoa(JumboFrameBPS) + `) as interface_mtu, -s.interface_name, -s.ip6_address, -s.ip6_address_is_service, -s.ip6_gateway, -s.ip_address, -s.ip_address_is_service, -s.ip_gateway, -s.ip_netmask, -s.last_updated, -s.mgmt_ip_address, -s.mgmt_ip_gateway, -s.mgmt_ip_netmask, -s.offline_reason, -pl.name as phys_location, -s.phys_location as phys_location_id, -p.name as profile, -p.description as profile_desc, -s.profile as profile_id, -s.rack, -s.reval_pending, -s.router_host_name, -s.router_port_name, -st.name as status, -s.status as status_id, -s.tcp_port, -t.name as server_type, -s.type as server_type_id, -s.upd_pending as upd_pending, -s.xmpp_id, -s.xmpp_passwd -FROM - server s -JOIN cachegroup cg ON s.cachegroup = cg.id -JOIN cdn cdn ON s.cdn_id = cdn.id -JOIN phys_location pl ON s.phys_location = pl.id -JOIN profile p ON s.profile = p.id -JOIN status st ON s.status = st.id -JOIN type t ON s.type = t.id` +func createV2(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) { + var server tc.ServerNullableV2 + + tx := inf.Tx.Tx + + if err := json.NewDecoder(r.Body).Decode(&server); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + if err := validateV2(&server, tx); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + resultRows, err := inf.Tx.NamedQuery(insertQuery, server) + if err != nil { + userErr, sysErr, errCode := api.ParseDBError(err) + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + defer resultRows.Close() + + rowsAffected := 0 + for resultRows.Next() { + rowsAffected++ + if err := resultRows.StructScan(&server.CommonServerProperties); err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("server create scanning: %v", err)) + return + } + } + if rowsAffected == 0 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("server create: no server was inserted, no id was returned")) + return + } else if rowsAffected > 1 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("too many ids returned from server insert")) + } + + ifaces, err := server.LegacyInterfaceDetails.ToInterfaces(*server.IPIsService, *server.IP6IsService) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + } + + if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces, tx); err != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created") + api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server) + + changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created", *server.HostName, *server.DomainName, *server.ID) + api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx) } -func insertQuery() string { - query := `INSERT INTO server ( -cachegroup, -cdn_id, -domain_name, -host_name, -https_port, -ilo_ip_address, -ilo_ip_netmask, -ilo_ip_gateway, -ilo_username, -ilo_password, -interface_mtu, -interface_name, -ip6_address, -ip6_address_is_service, -ip6_gateway, -ip_address, -ip_address_is_service, -ip_netmask, -ip_gateway, -mgmt_ip_address, -mgmt_ip_netmask, -mgmt_ip_gateway, -offline_reason, -phys_location, -profile, -rack, -router_host_name, -router_port_name, -status, -tcp_port, -type, -upd_pending, -xmpp_id, -xmpp_passwd -) VALUES ( -:cachegroup_id, -:cdn_id, -:domain_name, -:host_name, -:https_port, -:ilo_ip_address, -:ilo_ip_netmask, -:ilo_ip_gateway, -:ilo_username, -:ilo_password, -:interface_mtu, -:interface_name, -:ip6_address, -:ip6_address_is_service, -:ip6_gateway, -:ip_address, -:ip_address_is_service, -:ip_netmask, -:ip_gateway, -:mgmt_ip_address, -:mgmt_ip_netmask, -:mgmt_ip_gateway, -:offline_reason, -:phys_location_id, -:profile_id, -:rack, -:router_host_name, -:router_port_name, -:status_id, -:tcp_port, -:server_type_id, -:upd_pending, -:xmpp_id, -:xmpp_passwd -) RETURNING id,last_updated` - return query +func createV3(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) { + var server tc.ServerNullable + + tx := inf.Tx.Tx + + if err := json.NewDecoder(r.Body).Decode(&server); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + serviceInterface, err := validateV3(&server, tx) + if err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + return + } + + v2Server, err := server.ToServerV2() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + return + } + + v2Server.InterfaceName = &serviceInterface + + resultRows, err := inf.Tx.NamedQuery(insertQuery, v2Server) + if err != nil { + userErr, sysErr, errCode := api.ParseDBError(err) + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + defer resultRows.Close() + + rowsAffected := 0 + for resultRows.Next() { + rowsAffected++ + if err := resultRows.StructScan(&server.CommonServerProperties); err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("server create scanning: %v", err)) + return + } + } + if rowsAffected == 0 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("server create: no server was inserted, no id was returned")) + return + } else if rowsAffected > 1 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("too many ids returned from server insert")) + return + } + + userErr, sysErr, errCode := createInterfaces(*server.ID, server.Interfaces, tx) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + alerts := tc.CreateAlerts(tc.SuccessLevel, "Server created") + api.WriteAlertsObj(w, r, http.StatusCreated, alerts, server) + + changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: created", *server.HostName, *server.DomainName, *server.ID) + api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx) } -func updateQuery() string { - query := `UPDATE -server SET -cachegroup=:cachegroup_id, -cdn_id=:cdn_id, -domain_name=:domain_name, -host_name=:host_name, -https_port=:https_port, -ilo_ip_address=:ilo_ip_address, -ilo_ip_netmask=:ilo_ip_netmask, -ilo_ip_gateway=:ilo_ip_gateway, -ilo_username=:ilo_username, -ilo_password=:ilo_password, -interface_mtu=:interface_mtu, -interface_name=:interface_name, -ip6_address=:ip6_address, -ip6_address_is_service=:ip6_address_is_service, -ip6_gateway=:ip6_gateway, -ip_address=:ip_address, -ip_address_is_service=:ip_address_is_service, -ip_netmask=:ip_netmask, -ip_gateway=:ip_gateway, -mgmt_ip_address=:mgmt_ip_address, -mgmt_ip_netmask=:mgmt_ip_netmask, -mgmt_ip_gateway=:mgmt_ip_gateway, -offline_reason=:offline_reason, -phys_location=:phys_location_id, -profile=:profile_id, -rack=:rack, -router_host_name=:router_host_name, -router_port_name=:router_port_name, -status=:status_id, -tcp_port=:tcp_port, -type=:server_type_id, -upd_pending=:upd_pending, -xmpp_id=:xmpp_id, -xmpp_passwd=:xmpp_passwd -WHERE id=:id RETURNING last_updated` - return query +func Create(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + switch { + case inf.Version.Major <= 1: + createV1(inf, w, r) + case inf.Version.Major == 2: + createV2(inf, w, r) + default: + createV3(inf, w, r) + } } -func deleteQuery() string { - return `DELETE FROM server WHERE id = :id` +func Delete(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"}) + tx := inf.Tx.Tx + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + id := inf.IntParams["id"] + + var servers []tc.ServerNullable + servers, _, userErr, sysErr, errCode = getServers(map[string]string{"id": inf.Params["id"]}, inf.Tx, inf.User) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + if len(servers) < 1 { + api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("no server exists by id #%d", id), nil) + return + } + if len(servers) > 1 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("there are somehow two servers with id %d - cannot delete", id)) + return + } + + userErr, sysErr, errCode = deleteInterfaces(id, tx) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + if result, err := tx.Exec(deleteServerQuery, id); err != nil { + log.Errorf("Raw error: %v", err) + userErr, sysErr, errCode = api.ParseDBError(err) + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } else if rowsAffected, err := result.RowsAffected(); err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("getting rows affected by server delete: %v", err)) + return + } else if rowsAffected != 1 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("incorrect number of rows affected: %d", rowsAffected)) + return + } + + server := servers[0] + + if inf.Version.Major >= 3 { + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server deleted", server) + } else { + + serverV2, err := server.ToServerV2() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + return + } + + if inf.Version.Major <= 1 { + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server deleted", serverV2.ServerNullableV11) + } else { + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server deleted", serverV2) + } + } + changeLogMsg := fmt.Sprintf("SERVER: %s.%s, ID: %d, ACTION: deleted", *server.HostName, *server.DomainName, *server.ID) + api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx) } diff --git a/traffic_ops/traffic_ops_golang/server/servers_test.go b/traffic_ops/traffic_ops_golang/server/servers_test.go index a8c91f2ec6..2f80a8a7eb 100644 --- a/traffic_ops/traffic_ops_golang/server/servers_test.go +++ b/traffic_ops/traffic_ops_golang/server/servers_test.go @@ -20,21 +20,27 @@ package server */ import ( - "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" "net/http" "testing" "time" "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/test" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" "gopkg.in/DATA-DOG/go-sqlmock.v1" ) -func getTestServers() []tc.Server { - servers := []tc.Server{} +type ServerAndInterfaces struct { + Server tc.Server + Interface tc.ServerInterfaceInfo +} + +func getTestServers() []ServerAndInterfaces { + servers := []ServerAndInterfaces{} testServer := tc.Server{ Cachegroup: "Cachegroup", CachegroupID: 1, @@ -82,17 +88,36 @@ func getTestServers() []tc.Server { XMPPID: "xmppId", XMPPPasswd: "xmppPasswd", } - servers = append(servers, testServer) + + mtu := uint64(testServer.InterfaceMtu) + + iface := tc.ServerInterfaceInfo{ + IPAddresses: []tc.ServerIPAddress{ + tc.ServerIPAddress{ + Address: testServer.IPAddress, + Gateway: nil, + ServiceAddress: true, + }, + }, + MaxBandwidth: nil, + Monitor: true, + MTU: &mtu, + Name: testServer.InterfaceName, + } + + servers = append(servers, ServerAndInterfaces{Server: testServer, Interface: iface}) testServer2 := testServer testServer2.Cachegroup = "cachegroup2" testServer2.HostName = "server2" - servers = append(servers, testServer2) + testServer2.ID = 2 + servers = append(servers, ServerAndInterfaces{Server: testServer2, Interface: iface}) testServer3 := testServer testServer3.Cachegroup = "cachegroup3" testServer3.HostName = "server3" - servers = append(servers, testServer2) + testServer3.ID = 3 + servers = append(servers, ServerAndInterfaces{Server: testServer3, Interface: iface}) return servers } @@ -112,7 +137,7 @@ func TestUpdateServer(t *testing.T) { rows := sqlmock.NewRows([]string{"type", "cdn_id"}) // note here that the cdnid is 5, which is not the same as the initial cdnid of the fist traffic server - rows.AddRow(testServers[0].TypeID, 5) + rows.AddRow(testServers[0].Server.TypeID, 5) // Make it return a list of atleast one associated ds dsrows := sqlmock.NewRows([]string{"array"}) dsrows.AddRow("{3}") @@ -120,33 +145,14 @@ func TestUpdateServer(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectQuery("SELECT ARRAY").WillReturnRows(dsrows) - snv := tc.ServerNullableV11{ - CDNID: &testServers[0].CDNID, + s := tc.CommonServerProperties{ + CDNID: &testServers[0].Server.CDNID, FqdnTime: time.Time{}, - TypeID: &testServers[0].TypeID, - } - sn := tc.ServerNullable{ - ServerNullableV11: snv, - IPIsService: nil, - IP6IsService: nil, - } - - s := &TOServer{ - APIInfoImpl: api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ - Params: nil, - IntParams: nil, - User: nil, - ReqID: 0, - Version: nil, - Tx: db.MustBegin(), - Config: nil, - }, - }, - ServerNullable: sn, + TypeID: &testServers[0].Server.TypeID, + ID: &testServers[0].Server.ID, } - userErr, _, errCode := s.Update() + userErr, _, errCode := checkTypeChangeSafety(s, db.MustBegin()) if errCode != 409 { t.Errorf("Update servers: Expected error code of %v, but got %v", 409, errCode) } @@ -167,12 +173,22 @@ func TestGetServersByCachegroup(t *testing.T) { defer db.Close() testServers := getTestServers() - cols := test.ColsFromStructByTag("db", tc.Server{}) + + unfilteredCols := []string{"count"} + unfilteredRows := sqlmock.NewRows(unfilteredCols).AddRow(len(testServers)) + + cols := test.ColsFromStructByTag("db", tc.CommonServerProperties{}) + interfaceCols := []string{"max_bandwidth", "monitor", "mtu", "name", "server"} rows := sqlmock.NewRows(cols) + interfaceRows := sqlmock.NewRows(interfaceCols) + + ipCols := []string{"address", "gateway", "service_address", "server", "interface"} + ipRows := sqlmock.NewRows(ipCols) //TODO: drichardson - build helper to add these Rows from the struct values // or by CSV if types get in the way - for _, ts := range testServers { + for _, srv := range testServers { + ts := srv.Server rows = rows.AddRow( ts.Cachegroup, ts.CachegroupID, @@ -188,15 +204,6 @@ func TestGetServersByCachegroup(t *testing.T) { ts.ILOIPNetmask, ts.ILOPassword, ts.ILOUsername, - ts.InterfaceMtu, - ts.InterfaceName, - ts.IP6Address, - ts.IP6IsService, - ts.IP6Gateway, - ts.IPAddress, - ts.IPIsService, - ts.IPNetmask, - ts.IPGateway, ts.LastUpdated, ts.MgmtIPAddress, ts.MgmtIPGateway, @@ -220,14 +227,36 @@ func TestGetServersByCachegroup(t *testing.T) { ts.XMPPID, ts.XMPPPasswd, ) + interfaceRows = interfaceRows.AddRow( + srv.Interface.MaxBandwidth, + srv.Interface.Monitor, + srv.Interface.MTU, + srv.Interface.Name, + ts.ID, + ) + + for _, ip := range srv.Interface.IPAddresses { + ipRows = ipRows.AddRow( + ip.Address, + ip.Gateway, + ip.ServiceAddress, + ts.ID, + srv.Interface.Name, + ) + } } + mock.ExpectBegin() + mock.ExpectQuery("SELECT COUNT\\(s.id\\) FROM s").WillReturnRows(unfilteredRows) mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectQuery("SELECT").WillReturnRows(interfaceRows) + mock.ExpectQuery("SELECT").WillReturnRows(ipRows) + v := map[string]string{"cachegroup": "2"} user := auth.CurrentUser{} - servers, userErr, sysErr, errCode := getServers(v, db.MustBegin(), &user) + servers, _, userErr, sysErr, errCode := getServers(v, db.MustBegin(), &user) if userErr != nil || sysErr != nil { t.Errorf("getServers expected: no errors, actual: %v %v with status: %s", userErr, sysErr, http.StatusText(errCode)) } @@ -235,7 +264,6 @@ func TestGetServersByCachegroup(t *testing.T) { if len(servers) != 3 { t.Errorf("getServers expected: len(servers) == 3, actual: %v", len(servers)) } - } func TestGetMidServers(t *testing.T) { @@ -251,14 +279,23 @@ func TestGetMidServers(t *testing.T) { testServers := getTestServers() testServers = testServers[0:2] - testServers[1].Cachegroup = "parentCacheGroup" - testServers[1].CachegroupID = 2 - testServers[1].Type = "MID" + testServers[1].Server.Cachegroup = "parentCacheGroup" + testServers[1].Server.CachegroupID = 2 + testServers[1].Server.Type = "MID" - cols := test.ColsFromStructByTag("db", tc.Server{}) + unfilteredCols := []string{"count"} + unfilteredRows := sqlmock.NewRows(unfilteredCols).AddRow(len(testServers)) + + cols := test.ColsFromStructByTag("db", tc.CommonServerProperties{}) + interfaceCols := []string{"max_bandwidth", "monitor", "mtu", "name", "server"} rows := sqlmock.NewRows(cols) + interfaceRows := sqlmock.NewRows(interfaceCols) + + ipCols := []string{"address", "gateway", "service_address", "server", "interface"} + ipRows := sqlmock.NewRows(ipCols) - for _, ts := range testServers { + for _, srv := range testServers { + ts := srv.Server rows = rows.AddRow( ts.Cachegroup, ts.CachegroupID, @@ -274,15 +311,6 @@ func TestGetMidServers(t *testing.T) { ts.ILOIPNetmask, ts.ILOPassword, ts.ILOUsername, - ts.InterfaceMtu, - ts.InterfaceName, - ts.IP6Address, - ts.IP6IsService, - ts.IP6Gateway, - ts.IPAddress, - ts.IPIsService, - ts.IPNetmask, - ts.IPGateway, ts.LastUpdated, ts.MgmtIPAddress, ts.MgmtIPGateway, @@ -306,20 +334,40 @@ func TestGetMidServers(t *testing.T) { ts.XMPPID, ts.XMPPPasswd, ) + interfaceRows = interfaceRows.AddRow( + srv.Interface.MaxBandwidth, + srv.Interface.Monitor, + srv.Interface.MTU, + srv.Interface.Name, + ts.ID, + ) + + for _, ip := range srv.Interface.IPAddresses { + ipRows = ipRows.AddRow( + ip.Address, + ip.Gateway, + ip.ServiceAddress, + ts.ID, + srv.Interface.Name, + ) + } } mock.ExpectBegin() + mock.ExpectQuery("SELECT COUNT\\(s.id\\) FROM s").WillReturnRows(unfilteredRows) mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectQuery("SELECT").WillReturnRows(interfaceRows) + mock.ExpectQuery("SELECT").WillReturnRows(ipRows) v := map[string]string{} user := auth.CurrentUser{} - servers, userErr, sysErr, errCode := getServers(v, db.MustBegin(), &user) + servers, _, userErr, sysErr, errCode := getServers(v, db.MustBegin(), &user) if userErr != nil || sysErr != nil { t.Errorf("getServers expected: no errors, actual: %v %v with status: %s", userErr, sysErr, http.StatusText(errCode)) } - cols2 := test.ColsFromStructByTag("db", tc.Server{}) + cols2 := test.ColsFromStructByTag("db", tc.CommonServerProperties{}) rows2 := sqlmock.NewRows(cols2) cgs := []tc.CacheGroup{} @@ -365,7 +413,18 @@ func TestGetMidServers(t *testing.T) { } cgs = append(cgs, testCG2) + serverMap := make(map[int]tc.ServerNullable, len(servers)) + serverIDs := make([]int, 0, len(servers)) + for _, server := range servers { + if server.ID == nil { + t.Fatal("Found server with nil ID") + } + serverIDs = append(serverIDs, *server.ID) + serverMap[*server.ID] = server + } + ts := servers[1] + *ts.ID = *ts.ID + 1 rows2 = rows2.AddRow( ts.Cachegroup, ts.CachegroupID, @@ -381,15 +440,6 @@ func TestGetMidServers(t *testing.T) { ts.ILOIPNetmask, ts.ILOPassword, ts.ILOUsername, - ts.InterfaceMtu, - ts.InterfaceName, - ts.IP6Address, - ts.IP6IsService, - ts.IP6Gateway, - ts.IPAddress, - ts.IPIsService, - ts.IPNetmask, - ts.IPGateway, ts.LastUpdated, ts.MgmtIPAddress, ts.MgmtIPGateway, @@ -416,7 +466,7 @@ func TestGetMidServers(t *testing.T) { mock.ExpectBegin() mock.ExpectQuery("SELECT").WillReturnRows(rows2) - mid, userErr, sysErr, errCode := getMidServers(servers, db.MustBegin()) + mid, userErr, sysErr, errCode := getMidServers(serverIDs, serverMap, db.MustBegin()) if userErr != nil || sysErr != nil { t.Fatalf("getMidServers expected: no errors, actual: %v %v with status: %s", userErr, sysErr, http.StatusText(errCode)) @@ -424,10 +474,191 @@ func TestGetMidServers(t *testing.T) { if len(mid) != 1 { t.Fatalf("getMidServers expected: len(mid) == 1, actual: %v", len(mid)) } - if mid[0].Type != "MID" || *(mid[0].CachegroupID) != 2 || *(mid[0].Cachegroup) != "parentCacheGroup" { - t.Fatalf("getMidServers expected: Type == MID, actual: %v", mid[0].Type) - t.Fatalf("getMidServers expected: CachegroupID == 2, actual: %v", *(mid[0].CachegroupID)) - t.Fatalf("getMidServers expected: Cachegroup == parentCacheGroup, actual: %v", *(mid[0].Cachegroup)) + if serverMap[mid[0]].Type != "MID" { + t.Errorf("getMidServers expected: Type == MID, actual: %v", serverMap[mid[0]].Type) + } + + if serverMap[mid[0]].Cachegroup == nil { + t.Error("getMidServers expected: Cachegroup == parentCacheGroup, actual: nil") + } else if *(serverMap[mid[0]].Cachegroup) != "parentCacheGroup" { + t.Errorf("getMidServers expected: Cachegroup == parentCacheGroup, actual: %v", *(serverMap[mid[0]].Cachegroup)) + } + + if serverMap[mid[0]].CachegroupID == nil { + t.Error("getMidServers expected: CachegroupID == 2, actual: nil") + } else if *(serverMap[mid[0]].CachegroupID) != 2 { + t.Errorf("getMidServers expected: CachegroupID == 2, actual: %v", *(serverMap[mid[0]].CachegroupID)) + } +} + +func TestV3Validations(t *testing.T) { + mockDB, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer mockDB.Close() + + db := sqlx.NewDb(mockDB, "sqlmock") + defer db.Close() + + goodInterface := tc.ServerInterfaceInfo{ + IPAddresses: []tc.ServerIPAddress{ + tc.ServerIPAddress{ + Address: "127.0.0.1/32", + Gateway: nil, + ServiceAddress: true, + }, + }, + MaxBandwidth: nil, + Monitor: true, + MTU: nil, + Name: "eth0", + } + + testServer := tc.ServerNullable{ + CommonServerProperties: tc.CommonServerProperties{ + CDNID: util.IntPtr(1), + HostName: util.StrPtr("test"), + DomainName: util.StrPtr("quest"), + PhysLocationID: new(int), + ProfileID: new(int), + StatusID: new(int), + TypeID: new(int), + UpdPending: new(bool), + CachegroupID: new(int), + }, + Interfaces: []tc.ServerInterfaceInfo{goodInterface}, + } + + typeCols := []string{"name", "use_in_table"} + cdnCols := []string{"cdn"} + typeRows := sqlmock.NewRows(typeCols).AddRow("EDGE", "server") + cdnRows := sqlmock.NewRows(cdnCols).AddRow(*testServer.CDNID) + + mock.ExpectBegin() + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + tx := db.MustBegin().Tx + + _, err = validateV3(&testServer, tx) + if err != nil { + t.Errorf("Unexpected error validating test server: %v", err) + } + + testServer.Interfaces = []tc.ServerInterfaceInfo{} + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server with no interfaces to be invalid") + } else { + t.Logf("Got expected error validating server with no interfaces: %v", err) + } + + testServer.Interfaces = nil + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server with nil interfaces to be invalid") + } else { + t.Logf("Got expected error validating server with nil interfaces: %v", err) + } + + badIface := goodInterface + var badMTU uint64 = 1279 + badIface.MTU = &badMTU + testServer.Interfaces = []tc.ServerInterfaceInfo{badIface} + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server an MTU < 1280 to be invalid") + } else { + t.Logf("Got expected error validating server with an MTU < 1280: %v", err) + } + + badIface.MTU = nil + badIface.IPAddresses = []tc.ServerIPAddress{} + testServer.Interfaces = []tc.ServerInterfaceInfo{badIface} + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server with no IP addresses to be invalid") + } else { + t.Logf("Got expected error validating server with no IP addresses: %v", err) + } + + badIface.IPAddresses = nil + testServer.Interfaces = []tc.ServerInterfaceInfo{badIface} + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server with nil IP addresses to be invalid") + } else { + t.Logf("Got expected error validating server with nil IP addresses: %v", err) + } + + badIface = goodInterface + badIP := tc.ServerIPAddress{ + Address: "127.0.0.1/32", + Gateway: nil, + ServiceAddress: false, + } + badIface.IPAddresses = []tc.ServerIPAddress{badIP} + testServer.Interfaces = []tc.ServerInterfaceInfo{badIface} + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server with no service addresses to be invalid") + } else { + t.Logf("Got expected error validating server with no service addresses: %v", err) + } + + testServer.Interfaces = []tc.ServerInterfaceInfo{goodInterface, goodInterface} + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server with too many interfaces with service addresses to be invalid") + } else { + t.Logf("Got expected error validating server with too many interfaces with service addresses: %v", err) + } + + badIface = goodInterface + badIface.IPAddresses = append(badIface.IPAddresses, tc.ServerIPAddress{ + Address: "1.2.3.4/1", + Gateway: nil, + ServiceAddress: true, + }) + testServer.Interfaces = []tc.ServerInterfaceInfo{badIface} + + mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows) + mock.ExpectQuery("SELECT").WillReturnRows(cdnRows) + + _, err = validateV3(&testServer, tx) + if err == nil { + t.Errorf("Expected a server with no service addresses to be invalid") + } else { + t.Logf("Got expected error validating server with no service addresses: %v", err) } } diff --git a/traffic_ops/v1-client/capability.go b/traffic_ops/v1-client/capability.go index 100c0fab29..d2ba6ed90c 100644 --- a/traffic_ops/v1-client/capability.go +++ b/traffic_ops/v1-client/capability.go @@ -62,7 +62,7 @@ func (to *Session) GetCapabilities() ([]tc.Capability, ReqInf, error) { // GetCapability retrieves only the capability named 'c' func (to *Session) GetCapability(c string) (tc.Capability, ReqInf, error) { - var v url.Values + v := url.Values{} v.Add("name", c) endpoint := API_v14_CAPABILITIES + "?" + v.Encode() resp, remoteAddr, err := to.request(http.MethodGet, endpoint, nil) diff --git a/traffic_ops/v2-client/capability.go b/traffic_ops/v2-client/capability.go index cb544a47f6..43bae247fe 100644 --- a/traffic_ops/v2-client/capability.go +++ b/traffic_ops/v2-client/capability.go @@ -39,7 +39,7 @@ func (to *Session) GetCapabilities() ([]tc.Capability, ReqInf, error) { // GetCapability retrieves only the capability named 'c'. func (to *Session) GetCapability(c string) (tc.Capability, ReqInf, error) { - var v url.Values + v := url.Values{} v.Add("name", c) endpoint := API_CAPABILITIES + "?" + v.Encode() resp, remoteAddr, err := to.request(http.MethodGet, endpoint, nil) diff --git a/traffic_stats/traffic_stats.go b/traffic_stats/traffic_stats.go index 0ee4076cb3..dc15582635 100644 --- a/traffic_stats/traffic_stats.go +++ b/traffic_stats/traffic_stats.go @@ -38,7 +38,7 @@ import ( "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/lib/go-util" - "github.com/apache/trafficcontrol/traffic_ops/client" + "github.com/apache/trafficcontrol/traffic_ops/v2-client" log "github.com/cihub/seelog" influx "github.com/influxdata/influxdb/client/v2" )