diff --git a/docs/json-schema/request.json b/docs/json-schema/request.json index b31318356..fcb157f0a 100644 --- a/docs/json-schema/request.json +++ b/docs/json-schema/request.json @@ -242,13 +242,14 @@ "$ref" : "common.json#/definitions/uuid" }, "vendor_name" : { - "$ref" : "common.json#/definitions/non_empty_string" + "$ref" : "common.json#/definitions/mojo_relaxed_placeholder" } }, "required" : [ "datacenter_id", "az", - "alias" + "alias", + "vendor_name" ], "type" : "object" }, @@ -266,7 +267,7 @@ "$ref" : "common.json#/definitions/uuid" }, "vendor_name" : { - "$ref" : "common.json#/definitions/non_empty_string" + "$ref" : "common.json#/definitions/mojo_relaxed_placeholder" } }, "type" : "object" diff --git a/docs/json-schema/response.json b/docs/json-schema/response.json index 08dc316ac..0194831f6 100644 --- a/docs/json-schema/response.json +++ b/docs/json-schema/response.json @@ -239,15 +239,7 @@ "type" : "string" }, "vendor_name" : { - "description" : "the vendor's name for the room", - "oneOf" : [ - { - "type" : "string" - }, - { - "type" : "null" - } - ] + "$ref" : "common.json#/definitions/mojo_relaxed_placeholder" } }, "required" : [ @@ -882,14 +874,14 @@ "DeviceLocation" : { "additionalProperties" : false, "properties" : { - "datacenter" : { - "$ref" : "/definitions/Datacenter" + "az" : { + "type" : "string" }, "datacenter_room" : { - "$ref" : "/definitions/DatacenterRoomDetailed" + "$ref" : "common.json#/definitions/mojo_standard_placeholder" }, "rack" : { - "$ref" : "/definitions/Rack" + "$ref" : "common.json#/definitions/mojo_relaxed_placeholder" }, "rack_unit_start" : { "$ref" : "common.json#/definitions/positive_integer" @@ -934,7 +926,7 @@ } }, "required" : [ - "datacenter", + "az", "datacenter_room", "rack", "rack_unit_start", @@ -1103,55 +1095,7 @@ "type" : "null" }, { - "additionalProperties" : false, - "properties" : { - "datacenter" : { - "additionalProperties" : false, - "properties" : { - "name" : { - "description" : "datacenter.region", - "type" : "string" - }, - "vendor_name" : { - "description" : "the vendor's name for the datacenter", - "oneOf" : [ - { - "type" : "null" - }, - { - "type" : "string" - } - ] - } - }, - "required" : [ - "name", - "vendor_name" - ], - "type" : "object" - }, - "rack" : { - "additionalProperties" : false, - "properties" : { - "name" : { - "type" : "string" - }, - "rack_unit_start" : { - "$ref" : "common.json#/definitions/positive_integer" - } - }, - "required" : [ - "name", - "rack_unit_start" - ], - "type" : "object" - } - }, - "required" : [ - "datacenter", - "rack" - ], - "type" : "object" + "$ref" : "/definitions/DeviceLocation" } ] }, @@ -2985,55 +2929,7 @@ ] }, "location" : { - "additionalProperties" : false, - "properties" : { - "datacenter" : { - "additionalProperties" : false, - "properties" : { - "name" : { - "description" : "datacenter.region", - "type" : "string" - }, - "vendor_name" : { - "description" : "the vendor's name for the datacenter", - "oneOf" : [ - { - "type" : "null" - }, - { - "type" : "string" - } - ] - } - }, - "required" : [ - "name", - "vendor_name" - ], - "type" : "object" - }, - "rack" : { - "additionalProperties" : false, - "properties" : { - "name" : { - "type" : "string" - }, - "rack_unit_start" : { - "$ref" : "common.json#/definitions/positive_integer" - } - }, - "required" : [ - "name", - "rack_unit_start" - ], - "type" : "object" - } - }, - "required" : [ - "datacenter", - "rack" - ], - "type" : "object" + "$ref" : "/definitions/DeviceLocation" }, "phase" : { "$ref" : "common.json#/definitions/device_phase" diff --git a/docs/modules/Conch::Controller::DatacenterRoom.md b/docs/modules/Conch::Controller::DatacenterRoom.md index 749539185..8602a1f69 100644 --- a/docs/modules/Conch::Controller::DatacenterRoom.md +++ b/docs/modules/Conch::Controller::DatacenterRoom.md @@ -7,8 +7,11 @@ Conch::Controller::DatacenterRoom ## find\_datacenter\_room Chainable action that uses the `datacenter_room_id_or_alias` value provided in the stash -(usually via the request URL) to look up a datacenter\_room, and stashes the result in -`datacenter_room`. +(usually via the request URL) to look up a datacenter\_room, and stashes the query to get to it +in `datacenter_room_rs`. + +If `require_role` is provided, it is used as the minimum required role for the user to +continue; otherwise the user must be a system admin. ## get\_all @@ -38,10 +41,6 @@ Permanently delete a datacenter room. Response uses the Racks json schema. -## find\_rack - -Response uses the Rack json schema. - # LICENSING Copyright Joyent, Inc. diff --git a/docs/modules/Conch::Controller::Rack.md b/docs/modules/Conch::Controller::Rack.md index 5214c8b46..a0f8ed0dc 100644 --- a/docs/modules/Conch::Controller::Rack.md +++ b/docs/modules/Conch::Controller::Rack.md @@ -6,8 +6,14 @@ Conch::Controller::Rack ## find\_rack -Chainable action that uses the `rack_id` value provided in the stash (usually via the -request URL) to look up a rack, and stashes the query to get to it in `rack_rs`. +Chainable action that uses the `rack_id_or_name` value provided in the stash (usually via the +request URL) to look up a rack (constraining to the datacenter\_room if `datacenter_room_rs` is +also provided) and stashes the query to get to it in `rack_rs`. + +When datacenter\_room information is **not** provided, `rack_id_or_name` must be either a uuid +or a "long" rack name (["vendor\_name" in Conch::DB::Result::DatacenterRoom](../modules/Conch%3A%3ADB%3A%3AResult%3A%3ADatacenterRoom#vendor_name)) plus +["name" in Conch::DB::Result::Rack](../modules/Conch%3A%3ADB%3A%3AResult%3A%3ARack#name)); otherwise, it can also be a short rack name +["name" in Conch::DB::Result::Rack](../modules/Conch%3A%3ADB%3A%3AResult%3A%3ARack#name)). If `require_role` is provided, it is used as the minimum required role for the user to continue; otherwise the user must be a system admin. @@ -22,12 +28,6 @@ Get a single rack Response uses the Rack json schema. -## get\_all - -Get all racks - -Response uses the Racks json schema. - ## get\_layouts Gets all the layouts for the specified rack. diff --git a/docs/modules/Conch::DB::Result::DatacenterRoom.md b/docs/modules/Conch::DB::Result::DatacenterRoom.md index b0a9bd4aa..88b658f15 100644 --- a/docs/modules/Conch::DB::Result::DatacenterRoom.md +++ b/docs/modules/Conch::DB::Result::DatacenterRoom.md @@ -44,7 +44,7 @@ is_nullable: 0 ``` data_type: 'text' -is_nullable: 1 +is_nullable: 0 ``` ## created @@ -75,6 +75,10 @@ original: {default_value => \"now()"} - ["alias"](#alias) +## `datacenter_room_vendor_name_key` + +- ["vendor\_name"](#vendor_name) + # RELATIONS ## datacenter diff --git a/docs/modules/Conch::DB::ResultSet::Device.md b/docs/modules/Conch::DB::ResultSet::Device.md index b30f29362..c19d397fe 100644 --- a/docs/modules/Conch::DB::ResultSet::Device.md +++ b/docs/modules/Conch::DB::ResultSet::Device.md @@ -70,6 +70,11 @@ Modifies the resultset to add columns `rack_id`, `rack_unit_start` and `rack_nam Modifies the resultset to add the `sku` column. +## location\_data + +Returns a resultset that provides location data ([response.json#/definitions/DeviceLocation](../json-schema/response.json#/definitions/DeviceLocation)), +optionally returned under a hash using the provided key name. + # LICENSING Copyright Joyent, Inc. diff --git a/docs/modules/Conch::DB::ResultSet::Rack.md b/docs/modules/Conch::DB::ResultSet::Rack.md index 6c78e5260..d8a0de3af 100644 --- a/docs/modules/Conch::DB::ResultSet::Rack.md +++ b/docs/modules/Conch::DB::ResultSet::Rack.md @@ -17,6 +17,12 @@ you want, so don't do that.) This is used for identifying potential conflicts when adjusting layouts. +## with\_user\_role + +Constrains the resultset to those where the provided user\_id has (at least) the specified role +in at least one workspace or build associated with the specified rack(s), including parent +workspaces. + ## user\_has\_role Checks that the provided user\_id has (at least) the specified role in at least one workspace diff --git a/docs/modules/Conch::Route::Build.md b/docs/modules/Conch::Route::Build.md index 9565d29eb..f9fef2be1 100644 --- a/docs/modules/Conch::Route::Build.md +++ b/docs/modules/Conch::Route::Build.md @@ -117,7 +117,7 @@ read-write role on the device (via a workspace or build; see ["routes" in Conch: - Requires system admin authorization or the read-only role on the build - Response: response.yaml#/Racks -### `POST /build/:build_id_or_name/rack/:rack_id` +### `POST /build/:build_id_or_name/rack/:rack_id_or_name` - Requires system admin authorization, or the read/write role on the build and the read-write role on a workspace or build that contains the rack diff --git a/docs/modules/Conch::Route::DatacenterRoom.md b/docs/modules/Conch::Route::DatacenterRoom.md index 626c3f068..36fc2c64d 100644 --- a/docs/modules/Conch::Route::DatacenterRoom.md +++ b/docs/modules/Conch::Route::DatacenterRoom.md @@ -23,7 +23,8 @@ All routes require authentication. ### `GET /room/:datacenter_room_id_or_alias` -- Requires system admin authorization +- User requires system admin authorization, or the read-only role on a rack located in +the room - Response: [response.json#/definitions/DatacenterRoomDetailed](../json-schema/response.json#/definitions/DatacenterRoomDetailed) ### `POST /room/:datacenter_room_id_or_alias` @@ -37,16 +38,67 @@ All routes require authentication. - Requires system admin authorization - Response: `204 NO CONTENT` -### `GET /room/:datacenter_room_id_or_alias/racks` +### `GET /room/:datacenter_room_id_or_alias/rack` -- Requires system admin authorization +- User requires system admin authorization, or the read-only role on a rack located in +the room (in which case data returned is restricted to those racks) - Response: [response.json#/definitions/Racks](../json-schema/response.json#/definitions/Racks) -### `GET /room/:datacenter_room_id_or_alias/rack/:rack_name` +### `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` -- Requires system admin authorization +- User requires the read-only role on the rack - Response: [response.json#/definitions/Rack](../json-schema/response.json#/definitions/Rack) +### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` + +- User requires the read/write role on the rack +- Request: [request.json#/definitions/RackUpdate](../json-schema/request.json#/definitions/RackUpdate) +- Response: Redirect to the updated rack + +### `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` + +- Requires system admin authorization +- Response: `204 NO CONTENT` + +### `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts` + +- User requires the read-only role on the rack +- Response: [response.json#/definitions/RackLayouts](../json-schema/response.json#/definitions/RackLayouts) + +### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts` + +- User requires the read/write role on the rack +- Request: [request.json#/definitions/RackLayouts](../json-schema/request.json#/definitions/RackLayouts) +- Response: Redirect to the rack's layouts + +### `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` + +- User requires the read-only role on the rack +- Response: [response.json#/definitions/RackAssignments](../json-schema/response.json#/definitions/RackAssignments) + +### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` + +- User requires the read/write role on the rack +- Request: [request.json#/definitions/RackAssignmentUpdates](../json-schema/request.json#/definitions/RackAssignmentUpdates) +- Response: Redirect to the updated rack assignment + +### `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` + +This method requires a request body. + +- User requires the read/write role on the rack +- Request: [request.json#/definitions/RackAssignmentDeletes](../json-schema/request.json#/definitions/RackAssignmentDeletes) +- Response: `204 NO CONTENT` + +### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/phase?rack_only=<0|1>` + +The query parameter `rack_only` (defaults to `0`) specifies whether to update +only the rack's phase, or all the rack's devices' phases as well. + +- User requires the read/write role on the rack +- Request: [request.json#/definitions/RackPhase](../json-schema/request.json#/definitions/RackPhase) +- Response: Redirect to the updated rack + # LICENSING Copyright Joyent, Inc. diff --git a/docs/modules/Conch::Route::Rack.md b/docs/modules/Conch::Route::Rack.md index 6d94796a3..fef3942b9 100644 --- a/docs/modules/Conch::Route::Rack.md +++ b/docs/modules/Conch::Route::Rack.md @@ -8,12 +8,15 @@ Conch::Route::Rack Sets up the routes for /rack: -All routes require authentication. +## one\_rack\_routes -### `GET /rack` +Sets up the routes for working with just one rack, mounted under a provided route prefix. -- Requires system admin authorization -- Response: [response.json#/definitions/Racks](../json-schema/response.json#/definitions/Racks) +All routes require authentication. + +Take note: All routes that reference a specific rack (prefix `/rack/:rack_id`) are also +available under `/rack/:rack_id_or_long_name` as well as +`/room/datacenter_room_id_or_alias/rack/:rack_id_or_name`. ### `POST /rack` @@ -21,45 +24,45 @@ All routes require authentication. - Request: [request.json#/definitions/RackCreate](../json-schema/request.json#/definitions/RackCreate) - Response: Redirect to the created rack -### `GET /rack/:rack_id` +### `GET /rack/:rack_id_or_name` - User requires the read-only role on the rack - Response: [response.json#/definitions/Rack](../json-schema/response.json#/definitions/Rack) -### `POST /rack/:rack_id` +### `POST /rack/:rack_id_or_name` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackUpdate](../json-schema/request.json#/definitions/RackUpdate) - Response: Redirect to the updated rack -### `DELETE /rack/:rack_id` +### `DELETE /rack/:rack_id_or_name` - Requires system admin authorization - Response: `204 NO CONTENT` -### `GET /rack/:rack_id/layouts` +### `GET /rack/:rack_id_or_name/layout` - User requires the read-only role on the rack - Response: [response.json#/definitions/RackLayouts](../json-schema/response.json#/definitions/RackLayouts) -### `POST /rack/:rack_id/layouts` +### `POST /rack/:rack_id_or_name/layout` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackLayouts](../json-schema/request.json#/definitions/RackLayouts) - Response: Redirect to the rack's layouts -### `GET /rack/:rack_id/assignment` +### `GET /rack/:rack_id_or_name/assignment` - User requires the read-only role on the rack - Response: [response.json#/definitions/RackAssignments](../json-schema/response.json#/definitions/RackAssignments) -### `POST /rack/:rack_id/assignment` +### `POST /rack/:rack_id_or_name/assignment` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackAssignmentUpdates](../json-schema/request.json#/definitions/RackAssignmentUpdates) - Response: Redirect to the updated rack assignment -### `DELETE /rack/:rack_id/assignment` +### `DELETE /rack/:rack_id_or_name/assignment` This method requires a request body. @@ -67,7 +70,7 @@ This method requires a request body. - Request: [request.json#/definitions/RackAssignmentDeletes](../json-schema/request.json#/definitions/RackAssignmentDeletes) - Response: `204 NO CONTENT` -### `POST /rack/:rack_id/phase?rack_only=<0|1>` +### `POST /rack/:rack_id_or_name/phase?rack_only=<0|1>` The query parameter `rack_only` (defaults to `0`) specifies whether to update only the rack's phase, or all the rack's devices' phases as well. diff --git a/docs/modules/Conch::Route::Workspace.md b/docs/modules/Conch::Route::Workspace.md index b3e9f6568..51fb86d00 100644 --- a/docs/modules/Conch::Route::Workspace.md +++ b/docs/modules/Conch::Route::Workspace.md @@ -68,7 +68,7 @@ Accepts the following optional query parameters: - Request: [request.json#/definitions/WorkspaceAddRack](../json-schema/request.json#/definitions/WorkspaceAddRack) - Response: Redirect to the workspace's racks -### `DELETE /workspace/:workspace_id_or_name/rack/:rack_id` +### `DELETE /workspace/:workspace_id_or_name/rack/:rack_id_or_name` - User requires the admin role - Response: `204 NO CONTENT` diff --git a/json-schema/request.yaml b/json-schema/request.yaml index ee5979b0f..da7a3529c 100644 --- a/json-schema/request.yaml +++ b/json-schema/request.yaml @@ -39,6 +39,7 @@ definitions: - datacenter_id - az - alias + - vendor_name properties: datacenter_id: $ref: common.yaml#/definitions/uuid @@ -47,7 +48,7 @@ definitions: alias: $ref: common.yaml#/definitions/mojo_standard_placeholder vendor_name: - $ref: common.yaml#/definitions/non_empty_string + $ref: common.yaml#/definitions/mojo_relaxed_placeholder DatacenterRoomUpdate: type: object additionalProperties: false @@ -60,7 +61,7 @@ definitions: alias: $ref: common.yaml#/definitions/mojo_standard_placeholder vendor_name: - $ref: common.yaml#/definitions/non_empty_string + $ref: common.yaml#/definitions/mojo_relaxed_placeholder DeviceReport: $ref: device_report.yaml#/definitions/DeviceReport_v3.0.0 RackCreate: diff --git a/json-schema/response.yaml b/json-schema/response.yaml index 47654fdd6..1428fc987 100644 --- a/json-schema/response.yaml +++ b/json-schema/response.yaml @@ -509,38 +509,7 @@ definitions: location: oneOf: - type: 'null' - - type: object - additionalProperties: false - required: - - datacenter - - rack - properties: - datacenter: - type: object - additionalProperties: false - required: - - name - - vendor_name - properties: - name: - description: datacenter.region - type: string - vendor_name: - description: the vendor's name for the datacenter - oneOf: - - type: 'null' - - type: string - rack: - type: object - additionalProperties: false - required: - - name - - rack_unit_start - properties: - name: - type: string - rack_unit_start: - $ref: common.yaml#/definitions/positive_integer + - $ref: /definitions/DeviceLocation ipmi: oneOf: - type: 'null' @@ -622,38 +591,7 @@ definitions: phase: $ref: common.yaml#/definitions/device_phase location: - type: object - additionalProperties: false - required: - - datacenter - - rack - properties: - datacenter: - type: object - additionalProperties: false - required: - - name - - vendor_name - properties: - name: - description: datacenter.region - type: string - vendor_name: - description: the vendor's name for the datacenter - oneOf: - - type: 'null' - - type: string - rack: - type: object - additionalProperties: false - required: - - name - - rack_unit_start - properties: - name: - type: string - rack_unit_start: - $ref: common.yaml#/definitions/positive_integer + $ref: /definitions/DeviceLocation ipmi: oneOf: - type: 'null' @@ -697,18 +635,20 @@ definitions: type: object additionalProperties: false required: - - datacenter + - az - datacenter_room - rack - rack_unit_start - target_hardware_product properties: - datacenter: - $ref: /definitions/Datacenter + az: + type: string datacenter_room: - $ref: /definitions/DatacenterRoomDetailed + # description: datacenter_room.alias + $ref: common.yaml#/definitions/mojo_standard_placeholder rack: - $ref: /definitions/Rack + # description: rack.full_name (datacenter_room.vendor_name + ':' + rack.name) + $ref: common.yaml#/definitions/mojo_relaxed_placeholder rack_unit_start: $ref: common.yaml#/definitions/positive_integer target_hardware_product: @@ -1318,10 +1258,8 @@ definitions: alias: $ref: common.yaml#/definitions/mojo_standard_placeholder vendor_name: - description: the vendor's name for the room - oneOf: - - type: string - - type: 'null' + # description: the vendor's name for the room + $ref: common.yaml#/definitions/mojo_relaxed_placeholder datacenter_id: $ref: common.yaml#/definitions/uuid created: diff --git a/lib/Conch/Controller/DatacenterRoom.pm b/lib/Conch/Controller/DatacenterRoom.pm index dad1dadda..99fd1528a 100644 --- a/lib/Conch/Controller/DatacenterRoom.pm +++ b/lib/Conch/Controller/DatacenterRoom.pm @@ -3,6 +3,7 @@ package Conch::Controller::DatacenterRoom; use Mojo::Base 'Mojolicious::Controller', -signatures; use Conch::UUID 'is_uuid'; +use List::Util 'any'; =pod @@ -15,33 +16,36 @@ Conch::Controller::DatacenterRoom =head2 find_datacenter_room Chainable action that uses the C value provided in the stash -(usually via the request URL) to look up a datacenter_room, and stashes the result in -C. +(usually via the request URL) to look up a datacenter_room, and stashes the query to get to it +in C. + +If C is provided, it is used as the minimum required role for the user to +continue; otherwise the user must be a system admin. =cut sub find_datacenter_room ($c) { my $identifier = $c->stash('datacenter_room_id_or_alias'); - my $rs = $c->db_datacenter_rooms; - if (is_uuid($identifier)) { - $c->stash('datacenter_room_id', $identifier); - $rs = $rs->search({ 'datacenter_room.id' => $identifier }); - } - else { - $c->stash('datacenter_room_alias', $identifier); - $rs = $rs->search({ 'datacenter_room.alias' => $identifier }); - } + + my $rs = $c->db_datacenter_rooms->search({ + 'datacenter_room.'.(is_uuid($identifier) ? 'id' : 'alias') => $identifier, + }); $c->log->debug('Looking up datacenter room '.$identifier); - my $room = $rs->single; - if (not $room) { + if (not $rs->exists) { $c->log->debug('Could not find datacenter room '.$identifier); return $c->status(404); } + if (not $c->is_system_admin + and not $rs->related_resultset('racks')->user_has_role($c->stash('user_id'), $c->stash('require_role'))) { + $c->log->debug('User lacks the required role ('.$c->stash('require_role').') for datacenter room '.$identifier); + return $c->status(403); + } + $c->log->debug('Found datacenter room'); - $c->stash('datacenter_room', $room); + $c->stash('datacenter_room_rs', $rs); return 1; } @@ -69,7 +73,9 @@ Response uses the DatacenterRoomDetailed json schema. =cut sub get_one ($c) { - $c->status(200, $c->stash('datacenter_room')); + my $room = $c->stash('datacenter_room_rs')->single; + $c->res->headers->location('/room/'.$room->id); + $c->status(200, $room); } =head2 create @@ -82,6 +88,15 @@ sub create ($c) { my $input = $c->validate_request('DatacenterRoomCreate'); return if not $input; + return $c->status(409, { error => 'Datacenter does not exist' }) + if not $c->db_datacenters->search({ id => $input->{datacenter_id} })->exists; + + return $c->status(409, { error => 'a room already exists with that alias' }) + if $c->db_datacenter_rooms->search({ alias => $input->{alias} })->exists; + + return $c->status(409, { error => 'a room already exists with that vendor_name' }) + if $c->db_datacenter_rooms->search({ vendor_name => $input->{vendor_name} })->exists; + my $room = $c->db_datacenter_rooms->create($input); $c->log->debug('Created datacenter room '.$room->id); $c->status(303, '/room/'.$room->id); @@ -97,9 +112,23 @@ sub update ($c) { my $input = $c->validate_request('DatacenterRoomUpdate'); return if not $input; - $c->stash('datacenter_room')->update({ $input->%*, updated => \'now()' }); + return $c->status(409, { error => 'Datacenter does not exist' }) + if $input->{datacenter_id} + and not $c->db_datacenters->search({ id => $input->{datacenter_id} })->exists; + + my $room = $c->stash('datacenter_room_rs')->single; + + return $c->status(409, { error => 'a room already exists with that alias' }) + if $input->{alias} and $input->{alias} ne $room->alias + and $c->db_datacenter_rooms->search({ alias => $input->{alias} })->exists; + + return $c->status(409, { error => 'a room already exists with that vendor_name' }) + if $input->{vendor_name} and $input->{vendor_name} ne $room->vendor_name + and $c->db_datacenter_rooms->search({ vendor_name => $input->{vendor_name} })->exists; + + $room->update({ $input->%*, updated => \'now()' }); $c->log->debug('Updated datacenter room '.$c->stash('datacenter_room_id_or_alias')); - $c->status(303, '/room/'.$c->stash('datacenter_room')->id); + $c->status(303, '/room/'.$room->id); } =head2 delete @@ -109,13 +138,13 @@ Permanently delete a datacenter room. =cut sub delete ($c) { - if ($c->stash('datacenter_room')->related_resultset('racks')->exists) { + if ($c->stash('datacenter_room_rs')->related_resultset('racks')->exists) { $c->log->debug('Cannot delete datacenter_room: in use by one or more racks'); return $c->status(409, { error => 'cannot delete a datacenter_room when a rack is referencing it' }); } - $c->stash('datacenter_room')->delete; - $c->log->debug('Deleted datacenter room '.$c->stash('datacenter_room')->id); + $c->stash('datacenter_room_rs')->delete; + $c->log->debug('Deleted datacenter room '.$c->stash('datacenter_room_id_or_alias')); return $c->status(204); } @@ -126,38 +155,15 @@ Response uses the Racks json schema. =cut sub racks ($c) { - my @racks = $c->stash('datacenter_room')->related_resultset('racks')->all; - $c->log->debug('Found '.scalar(@racks).' racks for datacenter room '.$c->stash('datacenter_room_id_or_alias')); - return $c->status(200, \@racks); -} - -=head2 find_rack - -Response uses the Rack json schema. + my $rs = $c->stash('datacenter_room_rs')->related_resultset('racks'); -=cut - -sub find_rack ($c) { - my $rack_rs = $c->stash('datacenter_room') - ->related_resultset('racks') - ->search({ name => $c->stash('rack_name') }); + # filter the results by what the user is permitted to see. Depending on the size of the + # initial resultset, this could be slow! + $rs = $rs->with_user_role($c->stash('user_id'), 'ro') if not $c->is_system_admin; - if (not $rack_rs->exists) { - $c->log->debug('Could not find rack '.$c->stash('rack_name') - .' in room '.$c->stash('datacenter_room_id_or_alias')); - return $c->status(404); - } - - if (not $c->is_system_admin and not $rack_rs->user_has_role($c->stash('user_id'), 'ro')) { - $c->log->debug('User lacks the required role (ro) for rack '.$c->stash('rack_name') - .' in room'.$c->stash('datacenter_room_id_or_alias')); - return $c->status(403); - } - - my $rack = $rack_rs->single; - $c->log->debug('Found rack '.$rack->id); - - $c->status(200, $rack); + my @racks = $rs->all; + $c->log->debug('Found '.scalar(@racks).' racks for datacenter room '.$c->stash('datacenter_room_id_or_alias')); + return $c->status(200, \@racks); } 1; diff --git a/lib/Conch/Controller/Device.pm b/lib/Conch/Controller/Device.pm index d3616653f..74a56ae2c 100644 --- a/lib/Conch/Controller/Device.pm +++ b/lib/Conch/Controller/Device.pm @@ -146,24 +146,8 @@ sub get ($c) { }; if ($device->phase_cmp('production') < 0) { - my $location = $c->stash('device_rs') - ->related_resultset('device_location') - ->prefetch({ - rack_layout => 'hardware_product', - rack => { datacenter_room => 'datacenter' }, - }) - ->single; - - $detailed_device->{location} = $location ? +{ - rack => $location->rack, - rack_unit_start => $location->get_column('rack_unit_start'), - datacenter_room => $location->rack->datacenter_room, - datacenter => $location->rack->datacenter_room->datacenter, - target_hardware_product => +{ do { - my $hardware_product = $location->rack_layout->hardware_product; - map +($_ => $hardware_product->$_), qw(id name alias sku hardware_vendor_id); - } }, - } : undef; + $detailed_device->{location} = $c->stash('device_rs')->location_data->single; + undef $detailed_device->{location} if not $detailed_device->{location}{rack}; $detailed_device->{nics} = [ map { my $device_nic = $_; @@ -282,29 +266,23 @@ Response uses the DevicePXE json schema. sub get_pxe ($c) { my $device_rs = $c->stash('device_rs'); - my ($device) = $device_rs->search( - undef, - { - columns => { - id => 'device.id', - phase => 'device.phase', - 'location.datacenter.name' => 'datacenter.region', - 'location.datacenter.vendor_name' => 'datacenter.vendor_name', - 'location.rack.name' => 'rack.name', - 'location.rack.rack_unit_start' => 'device_location.rack_unit_start', - # pxe = the first (sorted by name) interface that is status=up - 'pxe.mac' => $device_rs->correlate('device_nics')->nic_pxe->as_query, - # ipmi = the (newest) interface named ipmi1. - ipmi_mac_ip => $device_rs->correlate('device_nics')->nic_ipmi->as_query, - }, - join => { device_location => { rack => { datacenter_room => 'datacenter' } } }, - }) - ->hri - ->all; - if (Conch::DB::Result::Device->phase_cmp($device->{phase}, 'production') >= 0) { - delete $device->{location}; - } + $device_rs = $device_rs + ->location_data('location') + ->add_columns({ + id => 'device.id', + phase => 'device.phase', + # pxe = the first (sorted by name) interface that is status=up + 'pxe.mac' => $device_rs->correlate('device_nics')->nic_pxe->as_query, + # ipmi = the (newest) interface named ipmi1. + ipmi_mac_ip => $device_rs->correlate('device_nics')->nic_ipmi->as_query, + }); + + my ($device) = $device_rs->all; + undef $device->{location} if not $device->{location}{rack}; + + delete $device->{location} + if Conch::DB::Result::Device->phase_cmp($device->{phase}, 'production') >= 0; my $ipmi = delete $device->{ipmi_mac_ip}; $device->{ipmi} = $ipmi ? { mac => $ipmi->[0], ip => $ipmi->[1] } : undef; diff --git a/lib/Conch/Controller/DeviceLocation.pm b/lib/Conch/Controller/DeviceLocation.pm index 29c907aa6..5fb3449aa 100644 --- a/lib/Conch/Controller/DeviceLocation.pm +++ b/lib/Conch/Controller/DeviceLocation.pm @@ -20,29 +20,11 @@ Response uses the DeviceLocation json schema. =cut sub get ($c) { - my $location = $c->stash('device_rs') - ->related_resultset('device_location') - ->prefetch({ - rack_layout => 'hardware_product', - rack => { datacenter_room => 'datacenter' }, - }) - ->single; + my $rs = $c->stash('device_rs')->related_resultset('device_location'); + return $c->status(404, { error => 'Device '.$c->stash('device_id').' is not assigned to a rack' }) + if not $rs->exists; - return $c->status(409, { error => 'Device '.$c->stash('device_id').' is not assigned to a rack' }) - if not $location; - - my $rack = $location->rack; - my $hardware_product = $location->rack_layout->hardware_product; - - my $location_data = +{ - rack => $rack, - rack_unit_start => $location->get_column('rack_unit_start'), - datacenter_room => $rack->datacenter_room, - datacenter => $rack->datacenter_room->datacenter, - target_hardware_product => { map +($_ => $hardware_product->$_), qw(id name alias sku hardware_vendor_id) }, - }; - - $c->status(200, $location_data); + $c->status(200, $c->stash('device_rs')->location_data->single); } =head2 set diff --git a/lib/Conch/Controller/Rack.pm b/lib/Conch/Controller/Rack.pm index 8e556031c..180b59dce 100644 --- a/lib/Conch/Controller/Rack.pm +++ b/lib/Conch/Controller/Rack.pm @@ -3,6 +3,7 @@ package Conch::Controller::Rack; use Mojo::Base 'Mojolicious::Controller', -signatures; use List::Util qw(any none first uniq max); +use Conch::UUID 'is_uuid'; =pod @@ -14,8 +15,14 @@ Conch::Controller::Rack =head2 find_rack -Chainable action that uses the C value provided in the stash (usually via the -request URL) to look up a rack, and stashes the query to get to it in C. +Chainable action that uses the C value provided in the stash (usually via the +request URL) to look up a rack (constraining to the datacenter_room if C is +also provided) and stashes the query to get to it in C. + +When datacenter_room information is B provided, C must be either a uuid +or a "long" rack name (L) plus +L); otherwise, it can also be a short rack name +L). If C is provided, it is used as the minimum required role for the user to continue; otherwise the user must be a system admin. @@ -23,12 +30,41 @@ continue; otherwise the user must be a system admin. =cut sub find_rack ($c) { - $c->log->debug('Looking for rack by id: '.$c->stash('rack_id')); - my $rack_rs = $c->db_racks - ->search({ 'rack.id' => $c->stash('rack_id') }); + my $identifier = $c->stash('rack_id_or_name'); + my $rack_rs; + + # /room/:id_or_alias/rack/:id -- ok + # /room/:id_or_alias/rack/:longname -- ok + # /room/:id_or_alias/rack/:shortname -- ok + # /rack/:id -- ok + # /rack/:longname -- ok + # /rack/:shortname -- not ok + if (is_uuid($identifier)) { + $rack_rs = ($c->stash('datacenter_room_rs') + ? $c->stash('datacenter_room_rs')->related_resultset('racks') + : $c->db_racks); + $rack_rs = $rack_rs->search({ $rack_rs->current_source_alias.'.id' => $identifier }); + } + elsif (my ($room_vendor_name, $rack_name) = ($identifier =~ /(.+):([^:]+)$/)) { + # search up by long rack name + my $room_rs = ($c->stash('datacenter_room_rs') ? $c->stash('datacenter_room_rs') : $c->db_datacenter_rooms)->search({ 'datacenter_room.vendor_name' => $room_vendor_name }); + $rack_rs = $room_rs->search_related('racks', { 'racks.name' => $rack_name }); + } + else { + # search by short rack name (requires room qualifier)l + return $c->status(400, { error => 'cannot look up rack by short name without qualifying by room' }) + if not $c->stash('datacenter_room_rs'); + + $rack_rs = $c->stash('datacenter_room_rs') + ->search_related('racks', { 'racks.name' => $identifier }); + } + + $c->log->debug('Looking for rack '.$identifier + .($c->stash('datacenter_room_rs') ? ' in room '.$c->stash('datacenter_room_id_or_alias') : '')); if (not $rack_rs->exists) { - $c->log->debug('Could not find rack '.$c->stash('rack_id')); + $c->log->debug('Could not find rack '.$identifier + .($c->stash('datacenter_room_rs') ? (' in room '.$c->stash('datacenter_room_id_or_alias')) : '')); return $c->status(404); } @@ -41,12 +77,15 @@ sub find_rack ($c) { : die 'need handling for '.$method.' method'); if (not $c->is_system_admin and not $rack_rs->user_has_role($c->stash('user_id'), $requires_role)) { - $c->log->debug('User lacks the required role ('.$requires_role.') for rack '.$c->stash('rack_id')); + $c->log->debug('User lacks the required role ('.$requires_role.') for rack '.$c->stash('rack_id_or_name')); return $c->status(403); } - $c->log->debug('Found rack '.$c->stash('rack_id')); - $c->stash('rack_rs', $rack_rs); + my $rack_id = $rack_rs->get_column($rack_rs->current_source_alias.'.id')->single; + $c->log->debug('Found rack '.$rack_id); + $c->stash('rack_id', $rack_id); + + $c->stash('rack_rs', $c->db_racks->search_rs({ 'rack.id' => $rack_id })); return 1; } @@ -91,24 +130,10 @@ Response uses the Rack json schema. =cut sub get ($c) { + $c->res->headers->location('/rack/'.$c->stash('rack_id')); $c->status(200, $c->stash('rack_rs')->single); } -=head2 get_all - -Get all racks - -Response uses the Racks json schema. - -=cut - -sub get_all ($c) { - my @racks = $c->db_racks->order_by('name')->all; - $c->log->debug('Found '.scalar(@racks).' racks'); - - $c->status(200, \@racks); -} - =head2 get_layouts Gets all the layouts for the specified rack. @@ -125,6 +150,7 @@ sub get_layouts ($c) { ->all; $c->log->debug('Found '.scalar(@layouts).' rack layouts'); + $c->res->headers->location('/rack/'.$c->stash('rack_id').'/layout'); $c->status(200, \@layouts); } @@ -206,7 +232,7 @@ sub overwrite_layouts ($c) { }) or return $c->status(400); - $c->status(303, '/rack/'.$c->stash('rack_id').'/layouts'); + $c->status(303, '/rack/'.$c->stash('rack_id').'/layout'); } =head2 update @@ -251,7 +277,7 @@ sub update ($c) { my @assigned_rack_units = $rack_rs->assigned_rack_units; if (my @out_of_range = grep $_ > $rack_role->rack_size, @assigned_rack_units) { - $c->log->debug('found layout used by rack id '.$c->stash('rack_id') + $c->log->debug('found layout used by rack id '.$rack->id .' that has assigned rack_units greater requested new rack_size of ' .$rack_role->rack_size.': ', join(', ', @out_of_range)); return $c->status(409, { error => 'cannot resize rack: found an assigned rack layout that extends beyond the new rack_size' }); @@ -301,13 +327,13 @@ sub get_assignment ($c) { hardware_product_name => 'hardware_product.name', rack_unit_size => 'hardware_product.rack_unit_size', }, - collapse => 1, }) ->order_by('rack_unit_start') ->hri ->all; $c->log->debug('Found '.scalar(@assignments).' device-rack assignments'); + $c->res->headers->location('/rack/'.$c->stash('rack_id').'/assignment'); $c->status(200, \@assignments); } @@ -489,8 +515,9 @@ sub set_phase ($c) { my $input = $c->validate_request('RackPhase'); return if not $input; - $c->stash('rack_rs')->update({ phase => $input->{phase}, updated => \'now()' }); - $c->log->debug('set the phase for rack '.$c->stash('rack_id').' to '.$input->{phase}); + my $rack = $c->stash('rack_rs')->single; + $rack->update({ phase => $input->{phase}, updated => \'now()' }); + $c->log->debug('set the phase for rack '.$rack->id.' to '.$input->{phase}); if (not $params->{rack_only} // 0) { $c->stash('rack_rs') diff --git a/lib/Conch/Controller/RackRole.pm b/lib/Conch/Controller/RackRole.pm index 1e2c9f3a3..fd66a8690 100644 --- a/lib/Conch/Controller/RackRole.pm +++ b/lib/Conch/Controller/RackRole.pm @@ -2,6 +2,8 @@ package Conch::Controller::RackRole; use Mojo::Base 'Mojolicious::Controller', -signatures; +use Conch::UUID 'is_uuid'; + =pod =head1 NAME @@ -18,22 +20,18 @@ the request URL) to look up a build, and stashes the result in C. =cut sub find_rack_role ($c) { - my $rack_role; - if ($c->stash('rack_role_id_or_name') =~ /^(.+?)\=(.+)$/) { - my ($key, $value) = ($1, $2); - if ($key ne 'name') { - $c->log->error("Unknown identifier '$key'"); - return $c->status(404); - } - - $c->log->debug("Looking up rack role using identifier '$key'"); - $rack_role = $c->db_rack_roles->find({ name => $value }, { key => 'rack_role_name_key' }); + my $rs; + if (is_uuid($c->stash('rack_role_id_or_name'))) { + $c->log->debug('looking up rack role by id'); + $rs = $c->db_rack_roles->search({ id => $c->stash('rack_role_id_or_name') }); } else { - $c->log->debug('looking up rack role by id'); - $rack_role = $c->db_rack_roles->find($c->stash('rack_role_id_or_name')); + $c->log->debug('Looking up rack role by name'); + $rs = $c->db_rack_roles->search({ name => $c->stash('rack_role_id_or_name') }); } + my $rack_role = $rs->single; + if (not $rack_role) { $c->log->debug('Could not find rack_role '.$c->stash('rack_role_id_or_name')); return $c->status(404); diff --git a/lib/Conch/Controller/WorkspaceDevice.pm b/lib/Conch/Controller/WorkspaceDevice.pm index a1832281c..80d99d449 100644 --- a/lib/Conch/Controller/WorkspaceDevice.pm +++ b/lib/Conch/Controller/WorkspaceDevice.pm @@ -73,38 +73,32 @@ sub get_pxe_devices ($c) { # production devices do not consider location, interface data to be canonical my $bad_phase = $c->req->query_params->param('phase_earlier_than') // 'production'; - my @devices = $c->stash('workspace_rs') + my $rack_ids_rs = $c->stash('workspace_rs') ->related_resultset('workspace_racks') - ->search_related('rack', undef, { join => { datacenter_room => 'datacenter' } }) - ->related_resultset('device_locations') - # production devices do not consider location data to be canonical - ->search_related('device', + ->get_column('rack_id') + ->as_query; + + my @devices = $c->db_devices + ->search( + # production devices do not consider location data to be canonical $bad_phase ? { 'device.phase' => { '<' => \[ '?::device_phase_enum', $bad_phase ] } } : ()) - ->columns({ + ->location_data('location') + ->add_columns({ id => 'device.id', phase => 'device.phase', - 'location.datacenter.name' => 'datacenter.region', - 'location.datacenter.vendor_name' => 'datacenter.vendor_name', - 'location.rack.name' => 'rack.name', - 'location.rack.rack_unit_start' => 'device_locations.rack_unit_start', # pxe = the first (sorted by name) interface that is status=up 'pxe.mac' => $c->db_devices->correlate('device_nics')->nic_pxe->as_query, # ipmi = the (newest) interface named ipmi1. ipmi_mac_ip => $c->db_devices->correlate('device_nics')->nic_ipmi->as_query, }) + ->search({ 'rack.id' => { -in => $rack_ids_rs } }) ->order_by('device.created') ->hri ->all; foreach my $device (@devices) { - if (Conch::DB::Result::Device->phase_cmp($device->{phase}, 'production') >= 0) { - delete $device->{location}; - } - else { - # DBIC collapse is inconsistent here with handling the lack of a datacenter_room->datacenter - $device->{location}{datacenter} = undef - if $device->{location} and not defined $device->{location}{datacenter}{name}; - } + delete $device->{location} + if Conch::DB::Result::Device->phase_cmp($device->{phase}, 'production') >= 0; my $ipmi = delete $device->{ipmi_mac_ip}; $device->{ipmi} = $ipmi ? { mac => $ipmi->[0], ip => $ipmi->[1] } : undef; diff --git a/lib/Conch/Controller/WorkspaceRack.pm b/lib/Conch/Controller/WorkspaceRack.pm index 3dbc85070..3087300b4 100644 --- a/lib/Conch/Controller/WorkspaceRack.pm +++ b/lib/Conch/Controller/WorkspaceRack.pm @@ -92,7 +92,7 @@ sub find_workspace_rack ($c) { }); if (not $rs->exists) { - $c->log->debug('Could not find rack '.$c->stash('rack_id').' in or beneath workspace '.$c->stash('workspace_id')); + $c->log->debug('Could not find rack '.$c->stash('rack_id_or_name').' in or beneath workspace '.$c->stash('workspace_id')); return $c->status(404); } @@ -156,7 +156,7 @@ sub remove ($c) { my $row = $c->stash('workspace_rack_rs')->single; $row->delete; - $c->log->debug('deleted workspace_rack entry for workspace_id '.$row->workspace_id.' and rack_id '.$c->stash('rack_id')); + $c->log->debug('deleted workspace_rack entry for workspace_id '.$row->workspace_id.' and rack_id '.$c->stash('rack_id_or_name')); return $c->status(204); } diff --git a/lib/Conch/DB/Result/DatacenterRoom.pm b/lib/Conch/DB/Result/DatacenterRoom.pm index 68011e9d5..dcc88daf5 100644 --- a/lib/Conch/DB/Result/DatacenterRoom.pm +++ b/lib/Conch/DB/Result/DatacenterRoom.pm @@ -55,7 +55,7 @@ __PACKAGE__->table("datacenter_room"); =head2 vendor_name data_type: 'text' - is_nullable: 1 + is_nullable: 0 =head2 created @@ -88,7 +88,7 @@ __PACKAGE__->add_columns( "alias", { data_type => "text", is_nullable => 0 }, "vendor_name", - { data_type => "text", is_nullable => 1 }, + { data_type => "text", is_nullable => 0 }, "created", { data_type => "timestamp with time zone", @@ -131,6 +131,18 @@ __PACKAGE__->set_primary_key("id"); __PACKAGE__->add_unique_constraint("datacenter_room_alias_key", ["alias"]); +=head2 C + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint("datacenter_room_vendor_name_key", ["vendor_name"]); + =head1 RELATIONS =head2 datacenter @@ -165,7 +177,7 @@ __PACKAGE__->has_many( # Created by DBIx::Class::Schema::Loader v0.07049 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:mcbeYZ4W6x5TQUuxkzhy5A +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/HdkgTQ3OY7FN8f1enwONg 1; __END__ diff --git a/lib/Conch/DB/ResultSet/Device.pm b/lib/Conch/DB/ResultSet/Device.pm index 0933ad65e..e31c5775f 100644 --- a/lib/Conch/DB/ResultSet/Device.pm +++ b/lib/Conch/DB/ResultSet/Device.pm @@ -211,6 +211,34 @@ sub with_sku ($self) { ->add_columns({ sku => 'hardware_product.sku' }); } +=head2 location_data + +Returns a resultset that provides location data (F), +optionally returned under a hash using the provided key name. + +=cut + +sub location_data ($self, $under_key = '') { + $under_key .= '.' if $under_key; + $self + ->search(undef, { + join => { device_location => [ + { rack_layout => 'hardware_product' }, + { rack => 'datacenter_room' }, + ] }, + columns => { + $under_key.'az' => 'datacenter_room.az', + $under_key.'datacenter_room' => 'datacenter_room.alias', + $under_key.'rack' => \q{datacenter_room.vendor_name || ':' || rack.name}, + $under_key.'rack_unit_start' => 'device_location.rack_unit_start', + map +($under_key.'target_hardware_product.'.$_ => 'hardware_product.'.$_), + qw(id name alias sku hardware_vendor_id), + }, + collapse => 1, + }) + ->hri; +} + 1; __END__ diff --git a/lib/Conch/DB/ResultSet/Rack.pm b/lib/Conch/DB/ResultSet/Rack.pm index b49e2efb2..3601f9c4f 100644 --- a/lib/Conch/DB/ResultSet/Rack.pm +++ b/lib/Conch/DB/ResultSet/Rack.pm @@ -43,6 +43,40 @@ sub assigned_rack_units ($self) { @layout_data; } +=head2 with_user_role + +Constrains the resultset to those where the provided user_id has (at least) the specified role +in at least one workspace or build associated with the specified rack(s), including parent +workspaces. + +=cut + +sub with_user_role ($self, $user_id, $role) { + return $self if $role eq 'none'; + + my $workspace_ids_rs = $self->result_source->schema->resultset('workspace') + ->with_user_role($user_id, $role) + ->get_column('id'); + + # since every workspace_rack entry has an equivalent entry in the parent workspace, we do + # not need to search the workspace heirarchy here, but simply look for a role entry for any + # workspace the rack is associated with. + my $racks_in_ws = $self->search( + { 'workspace_racks.workspace_id' => { -in => $workspace_ids_rs->as_query } }, + { join => 'workspace_racks' }, + ); + + my $build_ids_rs = $self->result_source->schema->resultset('build') + ->with_user_role($user_id, $role) + ->get_column('id'); + + my $racks_in_builds = $self->search({ + $self->current_source_alias.'.build_id' => { -in => $build_ids_rs->as_query }, + }); + + return $racks_in_ws->union($racks_in_builds); +} + =head2 user_has_role Checks that the provided user_id has (at least) the specified role in at least one workspace diff --git a/lib/Conch/Route/Build.pm b/lib/Conch/Route/Build.pm index 356373c3b..8f9d29309 100644 --- a/lib/Conch/Route/Build.pm +++ b/lib/Conch/Route/Build.pm @@ -91,8 +91,8 @@ sub routes { # GET /build/:build_id_or_name/rack $with_build_ro->get('/rack')->to('#get_racks'); - # POST /build/:build_id_or_name/rack/:rack_id - $with_build_rw->under('/rack/') + # POST /build/:build_id_or_name/rack/:rack_id_or_name + $with_build_rw->under('/rack/:rack_id_or_name') ->to('rack#find_rack', require_role => 'rw') ->post('/')->to('build#add_rack'); } @@ -297,7 +297,7 @@ read-write role on the device (via a workspace or build; see L +=head3 C =over 4 diff --git a/lib/Conch/Route/DatacenterRoom.pm b/lib/Conch/Route/DatacenterRoom.pm index 283eb191a..3781ce65c 100644 --- a/lib/Conch/Route/DatacenterRoom.pm +++ b/lib/Conch/Route/DatacenterRoom.pm @@ -20,28 +20,46 @@ sub routes { my $class = shift; my $room = shift; # secured, under /room - $room = $room->require_system_admin->to({ controller => 'datacenter_room' }); + $room = $room->to({ controller => 'datacenter_room' }); + + my $room_with_system_admin = $room->require_system_admin; # GET /room - $room->get('/')->to('#get_all'); + $room_with_system_admin->get('/')->to('#get_all'); # POST /room - $room->post('/')->to('#create'); + $room_with_system_admin->post('/')->to('#create'); + + my $with_datacenter_room_ro = $room->under('/:datacenter_room_id_or_alias') + ->to('#find_datacenter_room', require_role => 'ro'); - my $with_datacenter_room = $room->under('/:datacenter_room_id_or_alias') - ->to('#find_datacenter_room'); + my $with_datacenter_room_system_admin = + $room_with_system_admin->under('/:datacenter_room_id_or_alias') + ->to('#find_datacenter_room'); # GET /room/:datacenter_room_id_or_alias - $with_datacenter_room->get('/')->to('#get_one'); + $with_datacenter_room_ro->get('/')->to('#get_one'); # POST /room/:datacenter_room_id_or_alias - $with_datacenter_room->post('/')->to('#update'); + $with_datacenter_room_system_admin->post('/')->to('#update'); # DELETE /room/:datacenter_room_id_or_alias - $with_datacenter_room->delete('/')->to('#delete'); - - # GET /room/:datacenter_room_id_or_alias/racks - $with_datacenter_room->get('/racks')->to('#racks'); - - # GET /room/:datacenter_room_id_or_alias/rack/:rack_name - $with_datacenter_room->get('/rack/#rack_name')->to('#find_rack'); + $with_datacenter_room_system_admin->delete('/')->to('#delete'); + + # GET /room/:datacenter_room_id_or_alias/rack + $with_datacenter_room_ro->get('/rack')->to('#racks'); + + # GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name + # POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name + # DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name + # GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts + # POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts + # GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment + # POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment + # DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment + # POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/phase?rack_only=<0|1> + Conch::Route::Rack->one_rack_routes( + $room->under('/:datacenter_room_id_or_alias') + ->to('#find_datacenter_room', require_role => 'none') + ->any('/rack')->to(require_role => undef) + ); } 1; @@ -75,10 +93,10 @@ All routes require authentication. =head3 C - =over 4 -=item * Requires system admin authorization +=item * User requires system admin authorization, or the read-only role on a rack located in +the room =item * Response: F @@ -106,26 +124,122 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 -=item * Requires system admin authorization +=item * User requires system admin authorization, or the read-only role on a rack located in +the room (in which case data returned is restricted to those racks) =item * Response: F =back -=head3 C +=head3 C =over 4 -=item * Requires system admin authorization +=item * User requires the read-only role on the rack =item * Response: F =back +=head3 C + +=over 4 + +=item * User requires the read/write role on the rack + +=item * Request: F + +=item * Response: Redirect to the updated rack + +=back + +=head3 C + +=over 4 + +=item * Requires system admin authorization + +=item * Response: C<204 NO CONTENT> + +=back + +=head3 C + +=over 4 + +=item * User requires the read-only role on the rack + +=item * Response: F + +=back + +=head3 C + +=over 4 + +=item * User requires the read/write role on the rack + +=item * Request: F + +=item * Response: Redirect to the rack's layouts + +=back + +=head3 C + +=over 4 + +=item * User requires the read-only role on the rack + +=item * Response: F + +=back + +=head3 C + +=over 4 + +=item * User requires the read/write role on the rack + +=item * Request: F + +=item * Response: Redirect to the updated rack assignment + +=back + +=head3 C + +This method requires a request body. + +=over 4 + +=item * User requires the read/write role on the rack + +=item * Request: F + +=item * Response: C<204 NO CONTENT> + +=back + +=head3 C<< POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/phase?rack_only=<0|1> >> + +The query parameter C (defaults to C<0>) specifies whether to update +only the rack's phase, or all the rack's devices' phases as well. + +=over 4 + +=item * User requires the read/write role on the rack + +=item * Request: F + +=item * Response: Redirect to the updated rack + +=back + =head1 LICENSING Copyright Joyent, Inc. diff --git a/lib/Conch/Route/Rack.pm b/lib/Conch/Route/Rack.pm index 9f5d8e3b0..ec05ee648 100644 --- a/lib/Conch/Route/Rack.pm +++ b/lib/Conch/Route/Rack.pm @@ -1,6 +1,6 @@ package Conch::Route::Rack; -use Mojo::Base -strict; +use Mojo::Base -strict, -signatures; =pod @@ -22,52 +22,65 @@ sub routes { $rack->to({ controller => 'rack' }); - # GET /rack - $rack->require_system_admin->get('/')->to('#get_all'); + my $rack_with_system_admin = $rack->require_system_admin; + # POST /rack - $rack->require_system_admin->post('/')->to('#create'); - - my $with_rack = $rack->under('/')->to('#find_rack'); - - # GET /rack/:rack_id - $with_rack->get('/')->to('#get'); - # POST /rack/:rack_id - $with_rack->post('/')->to('#update'); - # DELETE /rack/:rack_id - $with_rack->require_system_admin->delete('/')->to('#delete'); - - # GET /rack/:rack_id/layouts - $with_rack->get('/layouts')->to('#get_layouts'); - # POST /rack/:rack_id/layouts - $with_rack->post('/layouts')->to('#overwrite_layouts'); - - # GET /rack/:rack_id/assignment - $with_rack->get('/assignment')->to('#get_assignment'); - # POST /rack/:rack_id/assignment - $with_rack->post('/assignment')->to('#set_assignment'); - # DELETE /rack/:rack_id/assignment - $with_rack->delete('/assignment')->to('#delete_assignment'); - - # POST /rack/:rack_id/phase?rack_only=<0|1> - $with_rack->post('/phase')->to('#set_phase'); + $rack_with_system_admin->post('/')->to('#create'); + + # GET /rack/:rack_id_or_name + # POST /rack/:rack_id_or_name + # DELETE /rack/:rack_id_or_name + # GET /rack/:rack_id_or_name/layout + # POST /rack/:rack_id_or_name/layout + # GET /rack/:rack_id_or_name/assignment + # POST /rack/:rack_id_or_name/assignment + # DELETE /rack/:rack_id_or_name/assignment + # POST /rack/:rack_id_or_name/phase?rack_only=<0|1> + $class->one_rack_routes($rack); } -1; -__END__ +=head2 one_rack_routes -=pod +Sets up the routes for working with just one rack, mounted under a provided route prefix. -All routes require authentication. +=cut -=head3 C +sub one_rack_routes ($class, $r) { + my $one_rack = $r->under('/#rack_id_or_name')->to('#find_rack', controller => 'rack'); + + # GET .../rack/:rack_id_or_name + $one_rack->get('/')->to('#get'); + # POST .../rack/:rack_id_or_name + $one_rack->post('/')->to('#update'); + # DELETE .../rack/:rack_id_or_name + $one_rack->require_system_admin->delete('/')->to('#delete'); + + # GET .../rack/:rack_id_or_name/layout + $one_rack->get('/layout')->to('#get_layouts'); + # POST .../rack/:rack_id_or_name/layout + $one_rack->post('/layout')->to('#overwrite_layouts'); + + # GET .../rack/:rack_id_or_name/assignment + $one_rack->get('/assignment')->to('#get_assignment'); + # POST .../rack/:rack_id_or_name/assignment + $one_rack->post('/assignment')->to('#set_assignment'); + # DELETE .../rack/:rack_id_or_name/assignment + $one_rack->delete('/assignment')->to('#delete_assignment'); + + # POST .../rack/:rack_id_or_name/phase?rack_only=<0|1> + $one_rack->post('/phase')->to('#set_phase'); +} -=over 4 +1; +__END__ -=item * Requires system admin authorization +=pod -=item * Response: F +All routes require authentication. -=back +Take note: All routes that reference a specific rack (prefix C) are also +available under C as well as +C. =head3 C @@ -81,7 +94,7 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 @@ -91,7 +104,7 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 @@ -103,7 +116,7 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 @@ -113,7 +126,7 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 @@ -123,7 +136,7 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 @@ -135,7 +148,7 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 @@ -145,7 +158,7 @@ All routes require authentication. =back -=head3 C +=head3 C =over 4 @@ -157,7 +170,7 @@ All routes require authentication. =back -=head3 C +=head3 C This method requires a request body. @@ -171,7 +184,7 @@ This method requires a request body. =back -=head3 C<< POST /rack/:rack_id/phase?rack_only=<0|1> >> +=head3 C<< POST /rack/:rack_id_or_name/phase?rack_only=<0|1> >> The query parameter C (defaults to C<0>) specifies whether to update only the rack's phase, or all the rack's devices' phases as well. diff --git a/lib/Conch/Route/Workspace.pm b/lib/Conch/Route/Workspace.pm index 8be0741ee..72f7e369e 100644 --- a/lib/Conch/Route/Workspace.pm +++ b/lib/Conch/Route/Workspace.pm @@ -55,7 +55,7 @@ sub routes { { my $with_workspace_rack = - $with_workspace->under('/rack/')->to('rack#find_rack') + $with_workspace->under('/rack/:rack_id_or_name')->to('rack#find_rack') ->under('/')->to('workspace_rack#find_workspace_rack'); # DELETE /workspace/:workspace_id_or_name/rack/:rack_id @@ -200,7 +200,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head3 C =over 4 diff --git a/lib/Test/Conch/Fixtures.pm b/lib/Test/Conch/Fixtures.pm index eff5d1790..a44cea5ba 100644 --- a/lib/Test/Conch/Fixtures.pm +++ b/lib/Test/Conch/Fixtures.pm @@ -321,11 +321,10 @@ sub generate_set ($self, $set_name, @args) { using => { az => "room-${num}a", alias => "room ${num}a", + vendor_name => "ROOM:${num}.A", }, requires => { "datacenter_$num" => { our => 'datacenter_id', their => 'id' }, - # this is a hack: should be able to specify requirements without copying values. - global_workspace => { our => 'vendor_name', their => 'name' }, }, }, rack_role_42u => { @@ -601,6 +600,7 @@ sub _generate_definition ($self, $fixture_type, $num, $specification) { using => { az => "datacenter_room_az_$num", alias => "room alias $num", + vendor_name => "ROOM:$num", ($specification // {})->%*, }, requires => { diff --git a/sql/migrations/0138-rack-unique-name-in-room.sql b/sql/migrations/0138-rack-unique-name-in-room.sql index faff0a71d..e4dd8305f 100644 --- a/sql/migrations/0138-rack-unique-name-in-room.sql +++ b/sql/migrations/0138-rack-unique-name-in-room.sql @@ -1,6 +1,6 @@ SELECT run_migration(138, $$ - alter table rack drop constraint rack_datacenter_room_id_name_key; + alter table rack drop constraint if exists rack_datacenter_room_id_name_key; alter table rack add constraint rack_datacenter_room_id_name_key unique (datacenter_room_id, name); $$); diff --git a/sql/migrations/0142-datacenter_room-vendor_name.sql b/sql/migrations/0142-datacenter_room-vendor_name.sql new file mode 100644 index 000000000..3db93c1c5 --- /dev/null +++ b/sql/migrations/0142-datacenter_room-vendor_name.sql @@ -0,0 +1,7 @@ +SELECT run_migration(142, $$ + + alter table datacenter_room drop constraint if exists datacenter_room_vendor_name_key; + alter table datacenter_room alter column vendor_name set not null; + alter table datacenter_room add constraint datacenter_room_vendor_name_key unique (vendor_name); + +$$); diff --git a/sql/schema.sql b/sql/schema.sql index 4379cff95..e1b2dd685 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -200,7 +200,7 @@ CREATE TABLE public.datacenter_room ( datacenter_id uuid NOT NULL, az text NOT NULL, alias text NOT NULL, - vendor_name text, + vendor_name text NOT NULL, created timestamp with time zone DEFAULT now() NOT NULL, updated timestamp with time zone DEFAULT now() NOT NULL ); @@ -815,6 +815,14 @@ ALTER TABLE ONLY public.datacenter_room ADD CONSTRAINT datacenter_room_pkey PRIMARY KEY (id); +-- +-- Name: datacenter_room datacenter_room_vendor_name_key; Type: CONSTRAINT; Schema: public; Owner: conch +-- + +ALTER TABLE ONLY public.datacenter_room + ADD CONSTRAINT datacenter_room_vendor_name_key UNIQUE (vendor_name); + + -- -- Name: datacenter datacenter_vendor_region_location_key; Type: CONSTRAINT; Schema: public; Owner: conch -- diff --git a/t/conch-rollbar.t b/t/conch-rollbar.t index 53a9ad649..753924ef0 100644 --- a/t/conch-rollbar.t +++ b/t/conch-rollbar.t @@ -336,7 +336,7 @@ $t->do_and_wait_for_event( $t->do_and_wait_for_event( $rollbar_app->plugins, 'rollbar_sent', sub ($t) { - $t->get_ok('/rack/i_do_not_exist') + $t->get_ok('/rack/foo/bar/i_do_not_exist') ->status_is(404) ->json_is({ error => 'Not Found' }); }, @@ -351,7 +351,7 @@ $t->do_and_wait_for_event( $payload->{data}{request}, superhashof({ method => 'GET', - url => re(qr{/rack/i_do_not_exist}), + url => re(qr{/rack/foo/bar/i_do_not_exist}), query_string => '', body => '', }), @@ -362,7 +362,7 @@ $t->do_and_wait_for_event( $payload->{data}{body}, { message => { - body => 'no endpoint found for: GET /rack/i_do_not_exist', + body => 'no endpoint found for: GET /rack/foo/bar/i_do_not_exist', }, }, 'message for endpoint-not-found', diff --git a/t/integration/crud/build.t b/t/integration/crud/build.t index de34b66fd..f42d8c0d2 100644 --- a/t/integration/crud/build.t +++ b/t/integration/crud/build.t @@ -858,6 +858,7 @@ $t->get_ok('/build/my first build/device') my $device1 = first { $_->isa('Conch::DB::Result::Device') } $t->generate_fixtures('device'); my $rack_layout1 = first { $_->isa('Conch::DB::Result::RackLayout') } $t->generate_fixtures('rack_layouts'); my $rack1 = $rack_layout1->rack; +my $room1 = $rack1->datacenter_room; $t2->post_ok('/build/my first build/rack/'.$rack1->id) ->status_is(403) @@ -871,6 +872,14 @@ $t->post_ok('/build/my first build/rack/'.$rack1->id) ->status_is(204) ->log_debug_is('adding rack '.$rack1->id.' to build my first build'); +$t->post_ok($_) + ->status_is(204) + foreach + '/build/'.$build->{id}.'/rack/'.$rack1->id, + '/build/'.$build->{id}.'/rack/'.$room1->vendor_name.':'.$rack1->name, + '/build/my first build/rack/'.$rack1->id, + '/build/my first build/rack/'.$room1->vendor_name.':'.$rack1->name; + $t->get_ok('/build/my first build/rack') ->status_is(200) ->json_schema_is('Racks') diff --git a/t/integration/crud/devices.t b/t/integration/crud/devices.t index b189b52aa..4cb0adb75 100644 --- a/t/integration/crud/devices.t +++ b/t/integration/crud/devices.t @@ -369,13 +369,10 @@ subtest 'located device' => sub { hardware_product_id => $hardware_product->id, sku => $hardware_product->sku, location => { - rack => { - (map +($_ => $rack->$_), qw(id name datacenter_room_id serial_number asset_tag phase rack_role_id build_id)), - (map +($_ => re(qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}Z$/)), qw(created updated)), - }, + az => 'room-0a', + datacenter_room => 'room 0a', + rack => 'ROOM:0.A:rack.0a', rack_unit_start => 1, - datacenter => ignore, - datacenter_room => superhashof({ az => 'room-0a' }), target_hardware_product => superhashof({ alias => 'Test Compute' }), }, latest_report => undef, @@ -1091,18 +1088,15 @@ subtest 'Device PXE' => sub { $t_ro->get_ok('/device/PXE_TEST/pxe') ->status_is(200) ->json_schema_is('DevicePXE') - ->json_is({ + ->json_cmp_deeply({ id => $device_pxe->id, phase => 'integration', location => { - datacenter => { - name => $datacenter->region, - vendor_name => $datacenter->vendor_name, - }, - rack => { - name => $layout->rack->name, - rack_unit_start => $layout->rack_unit_start, - }, + az => 'room-0a', + datacenter_room => 'room 0a', + rack => 'ROOM:0.A:rack.0a', + rack_unit_start => 3, + target_hardware_product => superhashof({ alias => $layout->hardware_product->alias }), }, ipmi => { mac => '00:00:00:00:00:cc', @@ -1183,9 +1177,9 @@ subtest 'Device location' => sub { ->status_is(200) ->json_schema_is('DeviceLocation') ->json_cmp_deeply({ - datacenter => superhashof({ id => $rack->datacenter_room->datacenter_id }), - datacenter_room => superhashof({ datacenter_id => $rack->datacenter_room->datacenter_id }), - rack => superhashof({ id => $rack_id, name => $rack->name }), + az => $rack->datacenter_room->az, + datacenter_room => $rack->datacenter_room->alias, + rack => $rack->datacenter_room->vendor_name.':'.$rack->name, rack_unit_start => 3, target_hardware_product => { (map +($_ => $layout->hardware_product->$_), qw(id name alias sku hardware_vendor_id)), diff --git a/t/integration/crud/rack-layouts.t b/t/integration/crud/rack-layouts.t index 6da57895b..17b15a684 100644 --- a/t/integration/crud/rack-layouts.t +++ b/t/integration/crud/rack-layouts.t @@ -59,8 +59,9 @@ $t->post_ok('/layout', json => { wat => 'wat' }) ->json_schema_is('RequestValidationError') ->json_cmp_deeply('/details', [ { path => '/', message => re(qr/properties not allowed/i) } ]); -$t->get_ok("/rack/$rack_id/layouts") +$t->get_ok("/rack/$rack_id/layout") ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_cmp_deeply([ map +{ @@ -163,9 +164,6 @@ $t->post_ok('/layout', json => { ->json_schema_is('Error') ->json_is({ error => 'rack_unit_start conflict' }); -$t->get_ok("/rack/$rack_id/layouts") - ->status_is(200) - ->json_schema_is('RackLayouts'); # at the moment, we have these assigned slots: # start 1, width 2 @@ -173,8 +171,12 @@ $t->get_ok("/rack/$rack_id/layouts") # start 11, width 4 # start 42, width 1 -$t->get_ok("/rack/$rack_id/layouts") +my $rack = $t->app->db_racks->search({ 'rack.id' => $rack_id })->prefetch('datacenter_room')->single; +my $room = $rack->datacenter_room; + +$t->get_ok($_) ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_cmp_deeply([ map +{ @@ -188,7 +190,16 @@ $t->get_ok("/rack/$rack_id/layouts") { rack_unit_start => 3, rack_unit_size => 4, hardware_product_id => $hw_product_storage->id }, { rack_unit_start => 11, rack_unit_size => 4, hardware_product_id => $hw_product_storage->id }, { rack_unit_start => 42, rack_unit_size => 1, hardware_product_id => $hw_product_switch->id }, - ]); + ]) + foreach + '/rack/'.$rack_id.'/layout', + '/rack/'.$room->vendor_name.':'.$rack->name.'/layout', + '/room/'.$room->id.'/rack/'.$rack->id.'/layout', + '/room/'.$room->id.'/rack/'.$room->vendor_name.':'.$rack->name.'/layout', + '/room/'.$room->id.'/rack/'.$rack->name.'/layout', + '/room/'.$room->alias.'/rack/'.$rack->id.'/layout', + '/room/'.$room->alias.'/rack/'.$room->vendor_name.':'.$rack->name.'/layout', + '/room/'.$room->alias.'/rack/'.$rack->name.'/layout'; my $layout_3_6 = $t->load_fixture('rack_0a_layout_3_6'); @@ -233,8 +244,9 @@ $t->get_ok($t->tx->res->headers->location) # start 19, width 4 originally start 1, width 2 # start 42, width 1 -$t->get_ok("/rack/$rack_id/layouts") +$t->get_ok("/rack/$rack_id/layout") ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_cmp_deeply([ superhashof({ rack_id => $rack_id, rack_unit_start => 3, hardware_product_id => $hw_product_storage->id }), @@ -263,8 +275,9 @@ $t->post_ok('/layout', json => { # start 19, width 4 originally start 1, width 2 # start 42, width 1 -$t->get_ok("/rack/$rack_id/layouts") +$t->get_ok("/rack/$rack_id/layout") ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_cmp_deeply([ superhashof({ rack_id => $rack_id, rack_unit_start => 1, hardware_product_id => $hw_product_compute->id }), @@ -289,8 +302,9 @@ undef $layout_19_22; # start 20, width 4 originally start 1, width 2 # start 42, width 1 -$t->get_ok("/rack/$rack_id/layouts") +$t->get_ok("/rack/$rack_id/layout") ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_cmp_deeply([ superhashof({ rack_unit_start => 1, hardware_product_id => $hw_product_compute->id }), @@ -330,17 +344,17 @@ $t->get_ok('/layout/'.$layout_3_6->id) # start 20, width 4 # occupied by 'my device' # start 42, width 1 -$t->post_ok('/rack/'.$rack_id.'/layouts', +$t->post_ok('/rack/'.$rack_id.'/layout', json => [{ rack_unit_start => 1, hardware_product_id => create_uuid_str }]) ->status_is(409) ->json_cmp_deeply({ error => re(qr/^hardware_product_id ${\Conch::UUID::UUID_FORMAT} does not exist$/) }); -$t->post_ok('/rack/'.$rack_id.'/layouts', +$t->post_ok('/rack/'.$rack_id.'/layout', json => [{ rack_unit_start => 42, hardware_product_id => $hw_product_compute->id }]) ->status_is(409) ->json_is({ error => 'layout starting at rack_unit 42 will extend beyond the end of the rack' }); -$t->post_ok('/rack/'.$rack_id.'/layouts', +$t->post_ok('/rack/'.$rack_id.'/layout', json => [ { rack_unit_start => 1, hardware_product_id => $hw_product_compute->id }, { rack_unit_start => 2, hardware_product_id => $hw_product_compute->id }, @@ -348,7 +362,7 @@ $t->post_ok('/rack/'.$rack_id.'/layouts', ->status_is(409) ->json_is({ error => 'layouts starting at rack_units 1 and 2 overlap' }); -$t->post_ok('/rack/'.$rack_id.'/layouts', +$t->post_ok('/rack/'.$rack_id.'/layout', json => [ # unchanged { rack_unit_start => 1, hardware_product_id => $hw_product_compute->id }, @@ -358,11 +372,12 @@ $t->post_ok('/rack/'.$rack_id.'/layouts', { rack_unit_start => 26, hardware_product_id => $hw_product_compute->id }, ]) ->status_is(303) - ->location_is('/rack/'.$rack_id.'/layouts') + ->location_is('/rack/'.$rack_id.'/layout') ->log_debug_is('deleted 2 rack layouts, created 1 rack layouts for rack '.$rack_id); -$t->get_ok('/rack/'.$rack_id.'/layouts') +$t->get_ok('/rack/'.$rack_id.'/layout') ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_cmp_deeply([ superhashof({ rack_unit_start => 1, hardware_product_id => $hw_product_compute->id }), @@ -379,16 +394,17 @@ $t->get_ok('/rack/'.$rack_id.'/assignment') { rack_unit_start => 26, rack_unit_size => 2, hardware_product_name => $hw_product_compute->name, device_id => undef, device_asset_tag => undef }, ]); -$t->post_ok('/rack/'.$rack_id.'/layouts', +$t->post_ok('/rack/'.$rack_id.'/layout', json => [ { rack_unit_start => 3, hardware_product_id => $hw_product_compute->id }, ]) ->status_is(303) - ->location_is('/rack/'.$rack_id.'/layouts') + ->location_is('/rack/'.$rack_id.'/layout') ->log_debug_is('unlocated 1 devices, deleted 3 rack layouts, created 1 rack layouts for rack '.$rack_id); -$t->get_ok('/rack/'.$rack_id.'/layouts') +$t->get_ok('/rack/'.$rack_id.'/layout') ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_cmp_deeply([ superhashof({ rack_unit_start => 3, hardware_product_id => $hw_product_compute->id }), @@ -401,13 +417,14 @@ $t->get_ok('/rack/'.$rack_id.'/assignment') { rack_unit_start => 3, rack_unit_size => 2, hardware_product_name => $hw_product_compute->name, device_id => undef, device_asset_tag => undef }, ]); -$t->post_ok('/rack/'.$rack_id.'/layouts', json => []) +$t->post_ok('/rack/'.$rack_id.'/layout', json => []) ->status_is(303) - ->location_is('/rack/'.$rack_id.'/layouts') + ->location_is('/rack/'.$rack_id.'/layout') ->log_debug_is('deleted 1 rack layouts for rack '.$rack_id); -$t->get_ok('/rack/'.$rack_id.'/layouts') +$t->get_ok('/rack/'.$rack_id.'/layout') ->status_is(200) + ->location_is('/rack/'.$rack_id.'/layout') ->json_schema_is('RackLayouts') ->json_is([]); diff --git a/t/integration/crud/rack-roles.t b/t/integration/crud/rack-roles.t index b98aaa971..a3585ffde 100644 --- a/t/integration/crud/rack-roles.t +++ b/t/integration/crud/rack-roles.t @@ -29,7 +29,7 @@ $t->get_ok('/rack_role/'.$role->id) ->json_schema_is('RackRole') ->json_cmp_deeply(superhashof({ name => 'rack_role 42U', rack_size => 42 })); -$t->get_ok('/rack_role/name=rack_role 42U') +$t->get_ok('/rack_role/rack_role 42U') ->status_is(200) ->json_schema_is('RackRole') ->json_cmp_deeply(superhashof({ name => 'rack_role 42U', rack_size => 42 })); diff --git a/t/integration/crud/racks.t b/t/integration/crud/racks.t index ba1dbe0f0..418b083a2 100644 --- a/t/integration/crud/racks.t +++ b/t/integration/crud/racks.t @@ -11,27 +11,49 @@ $t->load_fixture('super_user'); $t->authenticate; -$t->get_ok('/rack') - ->status_is(200) - ->json_schema_is('Racks') - ->json_is([]); - $t->load_fixture_set('workspace_room_rack_layout', 0); my $build = $t->generate_fixtures('build'); my $fake_id = create_uuid_str(); my $rack = $t->load_fixture('rack_0a'); +my $room = $rack->datacenter_room; + +$t->get_ok('/room/'.$room->id) + ->status_is(200) + ->json_schema_is('DatacenterRoomDetailed') + ->json_cmp_deeply( + superhashof({ az => 'room-0a', alias => 'room 0a', vendor_name => 'ROOM:0.A' }), + ); -$t->get_ok('/rack') +$t->get_ok('/room/room 0a/rack') ->status_is(200) ->json_schema_is('Racks') ->json_cmp_deeply([ superhashof({ name => 'rack.0a' }) ]); -$t->get_ok('/rack/'.$rack->id) +$t->get_ok($_) ->status_is(200) + ->location_is('/rack/'.$rack->id) ->json_schema_is('Rack') - ->json_cmp_deeply(superhashof({ name => 'rack.0a' })); + ->json_cmp_deeply(superhashof({ + id => $rack->id, + name => 'rack.0a', + datacenter_room_id => $room->id, + rack_role_id => re(Conch::UUID::UUID_FORMAT), + })) + foreach + '/rack/'.$rack->id, + '/rack/ROOM:0.A:rack.0a', + '/room/'.$room->id.'/rack/'.$rack->id, + '/room/'.$room->id.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->id.'/rack/rack.0a', + '/room/'.$room->alias.'/rack/'.$rack->id, + '/room/'.$room->alias.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->alias.'/rack/rack.0a'; + +$t->get_ok('/rack/rack.0a') + ->status_is(400) + ->json_is({ error => 'cannot look up rack by short name without qualifying by room' }); $t->post_ok('/rack', json => { wat => 'wat' }) ->status_is(400) @@ -104,7 +126,7 @@ $t->get_ok($t->tx->res->headers->location) ->json_cmp_deeply({ id => re(Conch::UUID::UUID_FORMAT), name => 'r4ck', - datacenter_room_id => $rack->datacenter_room_id, + datacenter_room_id => $room->id, rack_role_id => $rack->rack_role_id, serial_number => 'abc', asset_tag => undef, @@ -113,7 +135,22 @@ $t->get_ok($t->tx->res->headers->location) updated => re(qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}Z$/), build_id => $build->id, }); -my $new_rack_id = $t->tx->res->json->{id}; +my $new_rack = $t->tx->res->json; + +$t->get_ok($_) + ->status_is(200) + ->location_is('/rack/'.$new_rack->{id}) + ->json_schema_is('Rack') + ->json_is($new_rack) + foreach + '/rack/'.$new_rack->{id}, + '/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->id.'/rack/'.$new_rack->{id}, + '/room/'.$room->id.'/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->id.'/rack/'.$new_rack->{name}, + '/room/'.$room->alias.'/rack/'.$new_rack->{id}, + '/room/'.$room->alias.'/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->alias.'/rack/'.$new_rack->{name}; my $small_rack_role = $t->app->db_rack_roles->create({ name => '10U', rack_size => 10 }); @@ -152,31 +189,52 @@ $t->post_ok('/rack/'.$rack->id, json => { rack_role_id => $small_rack_role->id } ->json_schema_is('Error') ->json_is({ error => 'cannot resize rack: found an assigned rack layout that extends beyond the new rack_size' }); -$t->post_ok("/rack/$new_rack_id", json => { +$t->post_ok("/rack/$new_rack->{id}", json => { name => 'rack', serial_number => 'abc', asset_tag => 'deadbeef', }) ->status_is(303) - ->location_is('/rack/'.$new_rack_id); + ->location_is('/rack/'.$new_rack->{id}); +$new_rack->@{qw(name serial_number asset_tag)} = qw(rack abc deadbeef); -$t->post_ok("/rack/$new_rack_id", json => { rack_role_id => $small_rack_role->id }) +$t->post_ok($_, json => { rack_role_id => $small_rack_role->id }) ->status_is(303) - ->location_is('/rack/'.$new_rack_id); - -$t->post_ok("/rack/$new_rack_id", json => { rack_role_id => $small_rack_role->id }) - ->status_is(303) - ->location_is('/rack/'.$new_rack_id); + ->location_is('/rack/'.$new_rack->{id}) + foreach + '/rack/'.$new_rack->{id}, + '/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->id.'/rack/'.$new_rack->{id}, + '/room/'.$room->id.'/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->id.'/rack/'.$new_rack->{name}, + '/room/'.$room->alias.'/rack/'.$new_rack->{id}, + '/room/'.$room->alias.'/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->alias.'/rack/'.$new_rack->{name}; $t->get_ok($t->tx->res->headers->location) ->status_is(200) ->json_schema_is('Rack') - ->json_cmp_deeply(superhashof({ name => 'rack', serial_number => 'abc', asset_tag => 'deadbeef' })); + ->json_cmp_deeply(superhashof({ + name => 'rack', + serial_number => 'abc', + asset_tag => 'deadbeef', + rack_role_id => $small_rack_role->id, + })); -$t->get_ok("/rack/$new_rack_id/assignment") +$t->get_ok($_) ->status_is(200) + ->location_is('/rack/'.$new_rack->{id}.'/assignment') ->json_schema_is('RackAssignments') - ->json_is([]); + ->json_is([]) + foreach + '/rack/'.$new_rack->{id}.'/assignment', + '/rack/'.$room->vendor_name.':'.$new_rack->{name}.'/assignment', + '/room/'.$room->id.'/rack/'.$new_rack->{id}.'/assignment', + '/room/'.$room->id.'/rack/'.$room->vendor_name.':'.$new_rack->{name}.'/assignment', + '/room/'.$room->id.'/rack/'.$new_rack->{name}.'/assignment', + '/room/'.$room->alias.'/rack/'.$new_rack->{id}.'/assignment', + '/room/'.$room->alias.'/rack/'.$room->vendor_name.':'.$new_rack->{name}.'/assignment', + '/room/'.$room->alias.'/rack/'.$new_rack->{name}.'/assignment'; $t->delete_ok('/rack/'.$rack->id) ->status_is(409) @@ -186,16 +244,31 @@ $t->delete_ok('/rack/'.$rack->id) my $null_user = $t->generate_fixtures('user_account'); my $t2 = Test::Conch->new(pg => $t->pg); $t2->authenticate(email => $null_user->email); -$t2->delete_ok("/rack/$new_rack_id") +$t2->delete_ok("/rack/$new_rack->{id}") ->status_is(403) - ->log_debug_is('User lacks the required role (rw) for rack '.$new_rack_id); + ->log_debug_is('User lacks the required role (rw) for rack '.$new_rack->{id}); -$t->delete_ok("/rack/$new_rack_id") - ->status_is(204); +$t->delete_ok('/rack/'.$room->vendor_name.':'.$new_rack->{name}) + ->status_is(204) + ->log_debug_is('Deleted rack '.$new_rack->{id}); -$t->get_ok("/rack/$new_rack_id") +$t->get_ok($_) ->status_is(404) - ->log_debug_is('Could not find rack '.$new_rack_id); + ->log_debug_is('Could not find rack '.(split('/',$_))[-1]) + foreach + '/rack/'.$new_rack->{id}, + '/rack/'.$room->vendor_name.':'.$new_rack->{name}; + +$t->get_ok($_) + ->status_is(404) + ->log_debug_is('Could not find rack '.(split('/',$_))[-1].' in room '.(split('/',$_))[2]) + foreach + '/room/'.$room->id.'/rack/'.$new_rack->{id}, + '/room/'.$room->id.'/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->id.'/rack/'.$new_rack->{name}, + '/room/'.$room->alias.'/rack/'.$new_rack->{id}, + '/room/'.$room->alias.'/rack/'.$room->vendor_name.':'.$new_rack->{name}, + '/room/'.$room->alias.'/rack/'.$new_rack->{name}; my $hardware_product_compute = $t->load_fixture('hardware_product_compute'); my $hardware_product_storage = $t->load_fixture('hardware_product_storage'); @@ -203,6 +276,7 @@ my $hardware_product_storage = $t->load_fixture('hardware_product_storage'); $t->get_ok('/rack/'.$rack->id.'/assignment') ->status_is(200) + ->location_is('/rack/'.$rack->id.'/assignment') ->json_schema_is('RackAssignments') ->json_is([ { @@ -261,10 +335,20 @@ my $foo = $t->app->db_devices->find({ serial_number => 'FOO' }); $assignments->[0]->@{qw(device_id device_asset_tag)} = ($foo->id,'ohhai'); $assignments->[1]->@{qw(device_id device_asset_tag)} = ($bar->id,'hello'); -$t->get_ok($t->tx->res->headers->location) +$t->get_ok($_) ->status_is(200) + ->location_is('/rack/'.$rack->id.'/assignment') ->json_schema_is('RackAssignments') - ->json_is($assignments); + ->json_is($assignments) + foreach + '/rack/'.$rack->id.'/assignment', + '/rack/'.$room->vendor_name.':'.$rack->name.'/assignment', + '/room/'.$room->id.'/rack/'.$rack->id.'/assignment', + '/room/'.$room->id.'/rack/'.$room->vendor_name.':'.$rack->name.'/assignment', + '/room/'.$room->id.'/rack/'.$rack->name.'/assignment', + '/room/'.$room->alias.'/rack/'.$rack->id.'/assignment', + '/room/'.$room->alias.'/rack/'.$room->vendor_name.':'.$rack->name.'/assignment', + '/room/'.$room->alias.'/rack/'.$rack->name.'/assignment'; subtest 'rack phases' => sub { my $device_phase_rs = $t->app->db_devices @@ -286,6 +370,7 @@ subtest 'rack phases' => sub { $t->get_ok('/rack/'.$rack->id) ->status_is(200) + ->location_is('/rack/'.$rack->id) ->json_schema_is('Rack') ->json_is('/phase', 'production'); @@ -391,6 +476,7 @@ $t->app->schema->txn_do(sub { $t->get_ok('/rack/'.$rack->id.'/assignment') ->status_is(200) + ->location_is('/rack/'.$rack->id.'/assignment') ->json_schema_is('RackAssignments') ->json_is($assignments); @@ -418,6 +504,7 @@ $assignments->@[0,1] = ( $t->get_ok('/rack/'.$rack->id.'/assignment') ->status_is(200) + ->location_is('/rack/'.$rack->id.'/assignment') ->json_schema_is('RackAssignments') ->json_is($assignments); @@ -492,10 +579,20 @@ $t->delete_ok('/rack/'.$rack->id.'/assignment', json => [ $assignments->[0]->@{qw(device_id device_asset_tag)} = (); -$t->get_ok('/rack/'.$rack->id.'/assignment') +$t->get_ok($_) ->status_is(200) + ->location_is('/rack/'.$rack->id.'/assignment') ->json_schema_is('RackAssignments') - ->json_is($assignments); + ->json_is($assignments) + foreach + '/rack/'.$rack->id.'/assignment', + '/rack/'.$room->vendor_name.':'.$rack->name.'/assignment', + '/room/'.$room->id.'/rack/'.$rack->id.'/assignment', + '/room/'.$room->id.'/rack/'.$room->vendor_name.':'.$rack->name.'/assignment', + '/room/'.$room->id.'/rack/'.$rack->name.'/assignment', + '/room/'.$room->alias.'/rack/'.$rack->id.'/assignment', + '/room/'.$room->alias.'/rack/'.$room->vendor_name.':'.$rack->name.'/assignment', + '/room/'.$room->alias.'/rack/'.$rack->name.'/assignment'; done_testing; # vim: set ts=4 sts=4 sw=4 et : diff --git a/t/integration/crud/rooms.t b/t/integration/crud/rooms.t index adb79728e..dbd619476 100644 --- a/t/integration/crud/rooms.t +++ b/t/integration/crud/rooms.t @@ -3,6 +3,7 @@ use Test::More; use Test::Warnings; use Test::Deep; use Test::Conch; +use Conch::UUID 'create_uuid_str'; my $t = Test::Conch->new; $t->load_fixture('super_user'); @@ -20,43 +21,153 @@ $t->get_ok('/room') ->status_is(200) ->json_schema_is('DatacenterRoomsDetailed') ->json_cmp_deeply([ - superhashof({ az => 'room-0a', alias => 'room 0a' }), + superhashof({ az => 'room-0a', alias => 'room 0a', vendor_name => 'ROOM:0.A' }), ]); +my $rooms = $t->tx->res->json; +my $room = $rooms->[0]; my $datacenter = $t->load_fixture('datacenter_0'); -my $room = $t->load_fixture('datacenter_room_0a'); -$t->get_ok('/room/'.$room->id) +$t->get_ok($_) ->status_is(200) + ->location_is('/room/'.$room->{id}) ->json_schema_is('DatacenterRoomDetailed') - ->json_cmp_deeply(superhashof({ az => 'room-0a', alias => 'room 0a' })); + ->json_is($room) + foreach + '/room/'.$room->{id}, + '/room/'.$room->{alias}; -$t->get_ok('/room/'.$room->alias) +$t->get_ok('/room/'.$room->{id}.'/rack') ->status_is(200) - ->json_schema_is('DatacenterRoomDetailed') - ->json_cmp_deeply(superhashof({ az => 'room-0a', alias => 'room 0a' })); + ->json_schema_is('Racks') + ->json_cmp_deeply([ superhashof({ name => 'rack.0a' }) ]); +my $rack = $t->tx->res->json->[0]; -$t->get_ok('/room/'.$room->id.'/racks') +$t->get_ok('/room/'.$room->{alias}.'/rack') ->status_is(200) ->json_schema_is('Racks') - ->json_cmp_deeply([ superhashof({ name => 'rack.0a' }) ]); + ->json_is([ $rack ]); + +$t->get_ok($_) + ->status_is(200) + ->location_is('/rack/'.$rack->{id}) + ->json_schema_is('Rack') + ->json_is($rack) + foreach + '/room/'.$room->{id}.'/rack/'.$rack->{id}, + '/room/'.$room->{id}.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->{id}.'/rack/rack.0a', + '/room/'.$room->{alias}.'/rack/'.$rack->{id}, + '/room/'.$room->{alias}.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->{alias}.'/rack/rack.0a'; + +my $build_user = $t->generate_fixtures('user_account', { name => 'build_user' }); +my $build = $t->generate_fixtures('build'); +$build->create_related('user_build_roles', { user_id => $build_user->id, role => 'admin' }); + +my $t2 = Test::Conch->new(pg => $t->pg); +$t2->authenticate(email => $build_user->email); + +$t2->get_ok($_) + ->status_is(403) + foreach + '/room', + '/room/'.$room->{id}, + '/room/'.$room->{alias}, + '/room/'.$room->{id}.'/rack', + '/room/'.$room->{alias}.'/rack', + '/room/'.$room->{id}.'/rack/'.$rack->{id}, + '/room/'.$room->{id}.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->{id}.'/rack/rack.0a', + '/room/'.$room->{alias}.'/rack/'.$rack->{id}, + '/room/'.$room->{alias}.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->{alias}.'/rack/rack.0a'; + +$t->app->db_racks->search({ id => $rack->{id} })->update({ build_id => $build->id }); +$rack->{build_id} = $build->id; + +$t2->get_ok('/room') + ->status_is(403); + +$t2->get_ok($_) + ->status_is(200) + ->location_is('/room/'.$room->{id}) + ->json_schema_is('DatacenterRoomDetailed') + ->json_is($room) + foreach + '/room/'.$room->{id}, + '/room/'.$room->{alias}; -$t->get_ok('/room/'.$room->alias.'/racks') +$t2->get_ok($_) ->status_is(200) ->json_schema_is('Racks') - ->json_cmp_deeply([ superhashof({ name => 'rack.0a' }) ]); + ->json_is([ $rack ]) + foreach + '/room/'.$room->{id}.'/rack', + '/room/'.$room->{alias}.'/rack'; -$t->get_ok('/room/'.$room->alias.'/rack/rack.0a') +$t2->get_ok($_) + ->status_is(200) + ->location_is('/rack/'.$rack->{id}) + ->json_schema_is('Rack') + ->json_is($rack) + foreach + '/room/'.$room->{id}.'/rack/'.$rack->{id}, + '/room/'.$room->{id}.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->{id}.'/rack/rack.0a', + '/room/'.$room->{alias}.'/rack/'.$rack->{id}, + '/room/'.$room->{alias}.'/rack/ROOM:0.A:rack.0a', + '/room/'.$room->{alias}.'/rack/rack.0a'; + +my $rack2_id = $t->app->db_racks->create({ + datacenter_room_id => $room->{id}, + name => 'rack2', + rack_role_id => $rack->{rack_role_id}, +})->id; + +$t->get_ok('/room/'.$room->{id}.'/rack/rack2') ->status_is(200) + ->location_is('/rack/'.$rack2_id) ->json_schema_is('Rack') - ->json_cmp_deeply(superhashof({ name => 'rack.0a' })); + ->json_cmp_deeply(superhashof({ id => $rack2_id, name => 'rack2' })); +my $rack2 = $t->tx->res->json; + +$t->get_ok('/room/'.$room->{id}.'/rack') + ->status_is(200) + ->json_schema_is('Racks') + ->json_is([ $rack, $rack2 ]); + +$t2->get_ok($_) + ->status_is(403) + foreach + '/room/'.$room->{id}.'/rack/'.$rack2->{id}, + '/room/'.$room->{id}.'/rack/ROOM:0.A:rack2', + '/room/'.$room->{id}.'/rack/rack2', + '/room/'.$room->{alias}.'/rack/'.$rack2->{id}, + '/room/'.$room->{alias}.'/rack/ROOM:0.A:rack2', + '/room/'.$room->{alias}.'/rack/rack2'; + +$t2->get_ok($_) + ->status_is(200) + ->json_schema_is('Racks') + ->json_is([ $rack ]) + foreach + '/room/'.$room->{id}.'/rack', + '/room/'.$room->{alias}.'/rack'; $t->post_ok('/room', json => { wat => 'wat' }) ->status_is(400) ->json_schema_is('RequestValidationError') ->json_cmp_deeply('/details', [ { path => '/', message => re(qr/properties not allowed/i) } ]); -$t->post_ok('/room', json => { datacenter_id => $datacenter->id, az => 'sungo-test-1', alias => 'me' }) +$t->post_ok('/room', json => { datacenter_id => create_uuid_str, az => 'sungo-test-1', alias => 'me', vendor_name => 'A:B' }) + ->status_is(409) + ->json_is({ error => 'Datacenter does not exist' }); + +$t2->post_ok('/room', json => { datacenter_id => $datacenter->id }) + ->status_is(403); + +$t->post_ok('/room', json => { datacenter_id => $datacenter->id, az => 'sungo-test-1', alias => 'me', vendor_name => 'A:B' }) ->status_is(303); $t->get_ok($t->tx->res->headers->location) @@ -65,11 +176,33 @@ $t->get_ok($t->tx->res->headers->location) ->json_cmp_deeply(superhashof({ az => 'sungo-test-1', alias => 'me', - vendor_name => undef, + vendor_name => 'A:B', })); - my $idr = $t->tx->res->json->{id}; +$t->post_ok('/room', json => { datacenter_id => $datacenter->id, az => 'sungo-test-1', alias => 'me', vendor_name => 'C:D' }) + ->status_is(409) + ->json_is({ error => 'a room already exists with that alias' }); + +$t->post_ok('/room', json => { datacenter_id => $datacenter->id, az => 'sungo-test-1', alias => 'not me', vendor_name => 'A:B' }) + ->status_is(409) + ->json_is({ error => 'a room already exists with that vendor_name' }); + +$t->post_ok("/room/$idr", json => { datacenter_id => create_uuid_str }) + ->status_is(409) + ->json_is({ error => 'Datacenter does not exist' }); + +$t->post_ok("/room/$idr", json => { alias => $room->{alias} }) + ->status_is(409) + ->json_is({ error => 'a room already exists with that alias' }); + +$t->post_ok("/room/$idr", json => { vendor_name => $room->{vendor_name} }) + ->status_is(409) + ->json_is({ error => 'a room already exists with that vendor_name' }); + +$t2->post_ok("/room/$idr", json => { vendor_name => 'sungo' }) + ->status_is(403); + $t->post_ok("/room/$idr", json => { vendor_name => 'sungo', alias => 'you' }) ->status_is(303); @@ -82,11 +215,14 @@ $t->get_ok($t->tx->res->headers->location) vendor_name => 'sungo', })); -$t->delete_ok('/room/'.$room->id) +$t->delete_ok('/room/'.$room->{id}) ->status_is(409) ->json_schema_is('Error') ->json_is({ error => 'cannot delete a datacenter_room when a rack is referencing it' }); +$t2->delete_ok("/room/$idr") + ->status_is(403); + $t->delete_ok("/room/$idr") ->status_is(204); diff --git a/t/integration/crud/workspace-devices.t b/t/integration/crud/workspace-devices.t index 9f05e6a42..de3c9e45b 100644 --- a/t/integration/crud/workspace-devices.t +++ b/t/integration/crud/workspace-devices.t @@ -176,8 +176,6 @@ subtest 'Devices with PXE data' => sub { }, ); - my $datacenter = $t->load_fixture('datacenter_0'); - $t->get_ok('/workspace/'.$global_ws_id.'/device/pxe') ->status_is(200) ->json_schema_is('WorkspaceDevicePXEs') @@ -186,14 +184,11 @@ subtest 'Devices with PXE data' => sub { id => $devices[0]->id, phase => 'integration', location => { - datacenter => { - name => $datacenter->region, - vendor_name => $datacenter->vendor_name, - }, - rack => { - name => $layouts[0]->rack->name, - rack_unit_start => $layouts[0]->rack_unit_start, - }, + az => 'room-0a', + datacenter_room => 'room 0a', + rack => 'ROOM:0.A:rack.0a', + rack_unit_start => $layouts[0]->rack_unit_start, + target_hardware_product => superhashof({ alias => $layouts[0]->hardware_product->alias }), }, ipmi => { mac => '00:00:00:00:00:cc', @@ -207,14 +202,11 @@ subtest 'Devices with PXE data' => sub { id => $devices[1]->id, phase => 'integration', location => { - datacenter => { - name => $datacenter->region, - vendor_name => $datacenter->vendor_name, - }, - rack => { - name => $layouts[1]->rack->name, - rack_unit_start => $layouts[1]->rack_unit_start, - }, + az => 'room-0a', + datacenter_room => 'room 0a', + rack => 'ROOM:0.A:rack.0a', + rack_unit_start => $layouts[1]->rack_unit_start, + target_hardware_product => superhashof({ alias => $layouts[1]->hardware_product->alias }), }, ipmi => undef, pxe => undef, diff --git a/t/integration/crud/workspace-rack.t b/t/integration/crud/workspace-rack.t index 011606db0..6ae9be097 100644 --- a/t/integration/crud/workspace-rack.t +++ b/t/integration/crud/workspace-rack.t @@ -114,10 +114,10 @@ subtest 'Assign device to a location' => sub { ->status_is(200) ->json_schema_is('DeviceLocation') ->json_cmp_deeply({ - rack => superhashof({ id => $rack_id }), + az => 'room-0a', + datacenter_room => 'room 0a', + rack => 'ROOM:0.A:rack.0a', rack_unit_start => 1, - datacenter_room => superhashof({ datacenter_id => $rack->datacenter_room->datacenter_id }), - datacenter => superhashof({ id => $rack->datacenter_room->datacenter_id }), target_hardware_product => { (map +($_ => $hardware_product_compute->$_), qw(id name alias sku hardware_vendor_id)), },